Skip to content

Commit 47a41ff

Browse files
authored
fix: redirect EOL versions to /download (#9232)
* Redirect EOL versions to /download * Add test coverage for redirects
1 parent 358cc10 commit 47a41ff

3 files changed

Lines changed: 140 additions & 1 deletion

File tree

‎apps/site/components/Downloads/Release/VersionDropdown.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const VersionDropdown: FC = () => {
2828
);
2929

3030
if (
31-
release?.status === 'LTS' &&
31+
(release?.status === 'LTS' || release?.status === 'EOL') &&
3232
(pathname.includes('current') || pathname.includes('alpha'))
3333
) {
3434
redirect({ href: '/download', locale });
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import assert from 'node:assert/strict';
2+
import { beforeEach, describe, it, mock } from 'node:test';
3+
4+
import { render } from '@testing-library/react';
5+
6+
import {
7+
ReleaseContext,
8+
ReleasesContext,
9+
} from '#site/providers/releaseProvider';
10+
11+
const redirect = mock.fn();
12+
const setVersion = mock.fn();
13+
const Select = mock.fn(() => null);
14+
let pathname;
15+
16+
mock.module('@node-core/ui-components/Common/Select/NoScriptSelect', {
17+
defaultExport: Select,
18+
});
19+
20+
mock.module('#site/navigation.mjs', {
21+
namedExports: {
22+
redirect,
23+
usePathname: () => pathname,
24+
},
25+
});
26+
27+
const { default: VersionDropdown } = await import('../VersionDropdown');
28+
29+
const statuses = [
30+
{
31+
status: 'EOL',
32+
path: '/download',
33+
badgeKind: 'warning',
34+
versions: ['v18.0.0', 'v16.0.0'],
35+
},
36+
{
37+
status: 'Current',
38+
path: '/download/current',
39+
badgeKind: 'default',
40+
versions: ['v25.0.0', 'v25.1.0'],
41+
},
42+
{
43+
status: 'LTS',
44+
path: '/download',
45+
badgeKind: 'info',
46+
versions: ['v24.0.0', 'v22.0.0'],
47+
},
48+
{
49+
status: 'Alpha',
50+
path: '/download/alpha',
51+
badgeKind: 'default',
52+
versions: ['v26.0.0-alpha.1', 'v26.0.0-alpha.2'],
53+
},
54+
];
55+
56+
const releases = statuses.flatMap(({ status, versions }) =>
57+
versions.map(versionWithPrefix => ({ versionWithPrefix, status }))
58+
);
59+
60+
const renderDropdown = (release = releases[0]) => {
61+
render(
62+
<ReleasesContext value={{ releases, snippets: [] }}>
63+
<ReleaseContext value={{ release, setVersion }}>
64+
<VersionDropdown />
65+
</ReleaseContext>
66+
</ReleasesContext>
67+
);
68+
69+
return Select.mock.calls.at(-1).arguments[0];
70+
};
71+
72+
describe('VersionDropdown', () => {
73+
beforeEach(() => {
74+
pathname = '/download';
75+
redirect.mock.resetCalls();
76+
setVersion.mock.resetCalls();
77+
Select.mock.resetCalls();
78+
});
79+
80+
for (const { status, path, badgeKind, versions } of statuses) {
81+
describe(status, () => {
82+
const release = releases.find(candidate => candidate.status === status);
83+
84+
beforeEach(() => {
85+
pathname = path;
86+
});
87+
88+
it('uses the active release as the default selection', () => {
89+
const { defaultValue, ariaLabel } = renderDropdown(release);
90+
91+
assert.equal(defaultValue, release.versionWithPrefix);
92+
assert.equal(ariaLabel, 'layouts.download.dropdown.version');
93+
});
94+
95+
it(`provides the ${status} badge for each version`, () => {
96+
const { values } = renderDropdown(release);
97+
98+
assert.equal(values.length, releases.length);
99+
for (const version of versions) {
100+
assert.deepEqual(
101+
values.find(({ value }) => value === version),
102+
{
103+
value: version,
104+
label: version,
105+
badge: { label: status, kind: badgeKind },
106+
}
107+
);
108+
}
109+
});
110+
111+
for (const target of statuses) {
112+
const description =
113+
target.status === status ? `another ${status}` : target.status;
114+
115+
it(`switches to ${description}`, () => {
116+
const { onChange } = renderDropdown(release);
117+
const version = target.versions.find(
118+
candidate => candidate !== release.versionWithPrefix
119+
);
120+
121+
onChange(version);
122+
123+
if (target.path !== path) {
124+
assert.equal(redirect.mock.callCount(), 1);
125+
assert.deepEqual(redirect.mock.calls[0].arguments, [
126+
{ href: target.path, locale: 'en' },
127+
]);
128+
assert.equal(setVersion.mock.callCount(), 0);
129+
} else {
130+
assert.equal(redirect.mock.callCount(), 0);
131+
assert.equal(setVersion.mock.callCount(), 1);
132+
assert.deepEqual(setVersion.mock.calls[0].arguments, [version]);
133+
}
134+
});
135+
}
136+
});
137+
}
138+
});

‎apps/site/tests/setup.jsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import '../../../tests/setup.mjs';
88
mock.module('next-intl', {
99
namedExports: {
1010
useTranslations: () => key => key,
11+
useLocale: () => 'en',
1112

1213
useFormatter: () => {
1314
const formatWith = format => new Intl.DateTimeFormat('en-US', format);

0 commit comments

Comments
 (0)