diff --git a/apps/site/components/Downloads/Release/VersionDropdown.tsx b/apps/site/components/Downloads/Release/VersionDropdown.tsx index e29f41952581f..c966d0bee6be0 100644 --- a/apps/site/components/Downloads/Release/VersionDropdown.tsx +++ b/apps/site/components/Downloads/Release/VersionDropdown.tsx @@ -28,7 +28,7 @@ const VersionDropdown: FC = () => { ); if ( - release?.status === 'LTS' && + (release?.status === 'LTS' || release?.status === 'EOL') && (pathname.includes('current') || pathname.includes('alpha')) ) { redirect({ href: '/download', locale }); diff --git a/apps/site/components/Downloads/Release/__tests__/VersionDropdown.test.jsx b/apps/site/components/Downloads/Release/__tests__/VersionDropdown.test.jsx new file mode 100644 index 0000000000000..94c1b10f36554 --- /dev/null +++ b/apps/site/components/Downloads/Release/__tests__/VersionDropdown.test.jsx @@ -0,0 +1,138 @@ +import assert from 'node:assert/strict'; +import { beforeEach, describe, it, mock } from 'node:test'; + +import { render } from '@testing-library/react'; + +import { + ReleaseContext, + ReleasesContext, +} from '#site/providers/releaseProvider'; + +const redirect = mock.fn(); +const setVersion = mock.fn(); +const Select = mock.fn(() => null); +let pathname; + +mock.module('@node-core/ui-components/Common/Select/NoScriptSelect', { + defaultExport: Select, +}); + +mock.module('#site/navigation.mjs', { + namedExports: { + redirect, + usePathname: () => pathname, + }, +}); + +const { default: VersionDropdown } = await import('../VersionDropdown'); + +const statuses = [ + { + status: 'EOL', + path: '/download', + badgeKind: 'warning', + versions: ['v18.0.0', 'v16.0.0'], + }, + { + status: 'Current', + path: '/download/current', + badgeKind: 'default', + versions: ['v25.0.0', 'v25.1.0'], + }, + { + status: 'LTS', + path: '/download', + badgeKind: 'info', + versions: ['v24.0.0', 'v22.0.0'], + }, + { + status: 'Alpha', + path: '/download/alpha', + badgeKind: 'default', + versions: ['v26.0.0-alpha.1', 'v26.0.0-alpha.2'], + }, +]; + +const releases = statuses.flatMap(({ status, versions }) => + versions.map(versionWithPrefix => ({ versionWithPrefix, status })) +); + +const renderDropdown = (release = releases[0]) => { + render( + + + + + + ); + + return Select.mock.calls.at(-1).arguments[0]; +}; + +describe('VersionDropdown', () => { + beforeEach(() => { + pathname = '/download'; + redirect.mock.resetCalls(); + setVersion.mock.resetCalls(); + Select.mock.resetCalls(); + }); + + for (const { status, path, badgeKind, versions } of statuses) { + describe(status, () => { + const release = releases.find(candidate => candidate.status === status); + + beforeEach(() => { + pathname = path; + }); + + it('uses the active release as the default selection', () => { + const { defaultValue, ariaLabel } = renderDropdown(release); + + assert.equal(defaultValue, release.versionWithPrefix); + assert.equal(ariaLabel, 'layouts.download.dropdown.version'); + }); + + it(`provides the ${status} badge for each version`, () => { + const { values } = renderDropdown(release); + + assert.equal(values.length, releases.length); + for (const version of versions) { + assert.deepEqual( + values.find(({ value }) => value === version), + { + value: version, + label: version, + badge: { label: status, kind: badgeKind }, + } + ); + } + }); + + for (const target of statuses) { + const description = + target.status === status ? `another ${status}` : target.status; + + it(`switches to ${description}`, () => { + const { onChange } = renderDropdown(release); + const version = target.versions.find( + candidate => candidate !== release.versionWithPrefix + ); + + onChange(version); + + if (target.path !== path) { + assert.equal(redirect.mock.callCount(), 1); + assert.deepEqual(redirect.mock.calls[0].arguments, [ + { href: target.path, locale: 'en' }, + ]); + assert.equal(setVersion.mock.callCount(), 0); + } else { + assert.equal(redirect.mock.callCount(), 0); + assert.equal(setVersion.mock.callCount(), 1); + assert.deepEqual(setVersion.mock.calls[0].arguments, [version]); + } + }); + } + }); + } +}); diff --git a/apps/site/tests/setup.jsx b/apps/site/tests/setup.jsx index da7511bbf0495..1957e523fb63a 100644 --- a/apps/site/tests/setup.jsx +++ b/apps/site/tests/setup.jsx @@ -8,6 +8,7 @@ import '../../../tests/setup.mjs'; mock.module('next-intl', { namedExports: { useTranslations: () => key => key, + useLocale: () => 'en', useFormatter: () => { const formatWith = format => new Intl.DateTimeFormat('en-US', format);