@bleedingdev/modern-js-plugin-i18n 3.9.0-ultramodern.4 → 3.9.0-ultramodern.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/index.js +0 -22
- package/dist/cjs/runtime/Link.js +19 -32
- package/dist/cjs/runtime/context.js +26 -61
- package/dist/cjs/runtime/contextHelpers.js +5 -5
- package/dist/cjs/runtime/core.js +17 -14
- package/dist/cjs/runtime/hooks.js +7 -8
- package/dist/cjs/runtime/localizedPaths.js +11 -9
- package/dist/cjs/runtime/providerComposition.js +37 -13
- package/dist/cjs/runtime/routerAdapter.js +35 -140
- package/dist/cjs/runtime/utils.js +17 -19
- package/dist/cjs/server/index.js +10 -7
- package/dist/cjs/server/redirectPolicy.js +36 -31
- package/dist/cjs/shared/urlStrategy.js +18 -0
- package/dist/esm/cli/index.mjs +0 -22
- package/dist/esm/runtime/Link.mjs +19 -32
- package/dist/esm/runtime/context.mjs +18 -62
- package/dist/esm/runtime/contextHelpers.mjs +5 -5
- package/dist/esm/runtime/core.mjs +8 -5
- package/dist/esm/runtime/hooks.mjs +7 -8
- package/dist/esm/runtime/localizedPaths.mjs +11 -9
- package/dist/esm/runtime/providerComposition.mjs +38 -14
- package/dist/esm/runtime/routerAdapter.mjs +30 -141
- package/dist/esm/runtime/utils.mjs +15 -17
- package/dist/esm/server/index.mjs +10 -7
- package/dist/esm/server/redirectPolicy.mjs +29 -24
- package/dist/esm/shared/urlStrategy.mjs +0 -0
- package/dist/esm-node/cli/index.mjs +0 -22
- package/dist/esm-node/runtime/Link.mjs +19 -32
- package/dist/esm-node/runtime/context.mjs +18 -62
- package/dist/esm-node/runtime/contextHelpers.mjs +5 -5
- package/dist/esm-node/runtime/core.mjs +8 -5
- package/dist/esm-node/runtime/hooks.mjs +7 -8
- package/dist/esm-node/runtime/localizedPaths.mjs +11 -9
- package/dist/esm-node/runtime/providerComposition.mjs +38 -14
- package/dist/esm-node/runtime/routerAdapter.mjs +30 -141
- package/dist/esm-node/runtime/utils.mjs +15 -17
- package/dist/esm-node/server/index.mjs +10 -7
- package/dist/esm-node/server/redirectPolicy.mjs +29 -24
- package/dist/esm-node/shared/urlStrategy.mjs +1 -0
- package/dist/types/runtime/context.d.ts +8 -11
- package/dist/types/runtime/contextHelpers.d.ts +2 -2
- package/dist/types/runtime/core.d.ts +11 -2
- package/dist/types/runtime/hooks.d.ts +4 -5
- package/dist/types/runtime/localizedPaths.d.ts +4 -4
- package/dist/types/runtime/providerComposition.d.ts +13 -1
- package/dist/types/runtime/routerAdapter.d.ts +23 -6
- package/dist/types/runtime/utils.d.ts +4 -12
- package/dist/types/server/index.d.ts +2 -0
- package/dist/types/server/redirectPolicy.d.ts +5 -13
- package/dist/types/shared/type.d.ts +0 -14
- package/dist/types/shared/urlStrategy.d.ts +7 -0
- package/package.json +21 -11
- package/rstest.config.mts +2 -8
- package/src/cli/index.ts +1 -43
- package/src/runtime/Link.tsx +20 -40
- package/src/runtime/context.tsx +34 -133
- package/src/runtime/contextHelpers.ts +7 -5
- package/src/runtime/core.tsx +26 -6
- package/src/runtime/hooks.ts +14 -9
- package/src/runtime/localizedPaths.ts +24 -19
- package/src/runtime/providerComposition.tsx +71 -23
- package/src/runtime/routerAdapter.tsx +73 -338
- package/src/runtime/utils.ts +40 -31
- package/src/server/index.ts +44 -9
- package/src/server/redirectPolicy.ts +70 -70
- package/src/shared/type.ts +0 -14
- package/src/shared/urlStrategy.ts +11 -0
- package/tests/federatedI18nBoundaryResolver.test.tsx +71 -0
- package/tests/i18nUtils.test.ts +31 -65
- package/tests/link.test.tsx +13 -511
- package/tests/linkTypes.test.ts +12 -3
- package/tests/localisedUrlRewriteMatrix.fork.test.ts +4 -96
- package/tests/localisedUrls.test.ts +7 -216
- package/tests/reactI18nextRuntimeBoundary.test.ts +1 -82
- package/tests/redirectPolicy.test.ts +39 -30
- package/tests/routerAdapter.test.tsx +95 -1055
- package/tests/federatedI18nBoundary.client.test.tsx +0 -129
- package/tests/federatedI18nBoundary.test.tsx +0 -132
- package/tests/peerCohort.test.ts +0 -25
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { NestedRouteForCli } from '@modern-js/types';
|
|
2
1
|
import { describe, expect, test } from '@rstest/core';
|
|
3
2
|
import { i18nPlugin as i18nCliPlugin } from '../src/cli';
|
|
4
3
|
import {
|
|
@@ -7,19 +6,6 @@ import {
|
|
|
7
6
|
matchesApiPrefix,
|
|
8
7
|
} from '../src/server';
|
|
9
8
|
|
|
10
|
-
const createRoute = (
|
|
11
|
-
path: string,
|
|
12
|
-
children?: NestedRouteForCli[],
|
|
13
|
-
): NestedRouteForCli => ({
|
|
14
|
-
id: path,
|
|
15
|
-
path,
|
|
16
|
-
type: 'nested',
|
|
17
|
-
origin: 'file-system',
|
|
18
|
-
routeType: children ? 'layout' : 'page',
|
|
19
|
-
_component: `${path}.tsx`,
|
|
20
|
-
children,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
9
|
const createRequestContext = (pathname: string) =>
|
|
24
10
|
({
|
|
25
11
|
req: {
|
|
@@ -63,149 +49,16 @@ describe('cli modifyFileSystemRoutes', () => {
|
|
|
63
49
|
expect(runtimePlugin?.config.reactI18next).toBe(false);
|
|
64
50
|
});
|
|
65
51
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
})
|
|
72
|
-
| undefined;
|
|
73
|
-
|
|
74
|
-
i18nCliPlugin({ localeDetection }).setup({
|
|
52
|
+
test('upstream-style configs without a map keep routes untouched', () => {
|
|
53
|
+
const modifyRoutes = rstest.fn();
|
|
54
|
+
i18nCliPlugin({
|
|
55
|
+
localeDetection: { localePathRedirect: true, languages: ['en', 'cs'] },
|
|
56
|
+
}).setup({
|
|
75
57
|
_internalRuntimePlugins: () => {},
|
|
76
|
-
modifyFileSystemRoutes: (fn: any) => {
|
|
77
|
-
modifyRoutes = fn;
|
|
78
|
-
},
|
|
79
58
|
_internalServerPlugins: () => {},
|
|
59
|
+
modifyFileSystemRoutes: modifyRoutes,
|
|
80
60
|
} as any);
|
|
81
|
-
|
|
82
|
-
expect(modifyRoutes).toBeDefined();
|
|
83
|
-
return modifyRoutes!;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const generateLocalisedRoutes = () => {
|
|
87
|
-
const modifyRoutes = setupModifyRoutes({
|
|
88
|
-
localePathRedirect: true,
|
|
89
|
-
languages: ['en', 'cs', 'de'],
|
|
90
|
-
localisedUrls: {
|
|
91
|
-
'/about': {
|
|
92
|
-
en: '/about',
|
|
93
|
-
cs: '/o-nas',
|
|
94
|
-
de: '/ueber-uns',
|
|
95
|
-
},
|
|
96
|
-
'/products': {
|
|
97
|
-
en: '/products',
|
|
98
|
-
cs: '/produkty',
|
|
99
|
-
de: '/produkte',
|
|
100
|
-
},
|
|
101
|
-
'/products/:slug': {
|
|
102
|
-
en: '/products/:slug',
|
|
103
|
-
cs: '/produkty/:slug',
|
|
104
|
-
de: '/produkte/:slug',
|
|
105
|
-
},
|
|
106
|
-
'/docs': {
|
|
107
|
-
en: '/docs',
|
|
108
|
-
cs: '/dokumenty',
|
|
109
|
-
de: '/dokumente',
|
|
110
|
-
},
|
|
111
|
-
'/docs/*': {
|
|
112
|
-
en: '/docs/*',
|
|
113
|
-
cs: '/dokumenty/*',
|
|
114
|
-
de: '/dokumente/*',
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
const routes = [
|
|
119
|
-
createRoute(':lang', [
|
|
120
|
-
createRoute('about'),
|
|
121
|
-
createRoute('products', [createRoute(':slug')]),
|
|
122
|
-
createRoute('docs', [createRoute('*')]),
|
|
123
|
-
]),
|
|
124
|
-
];
|
|
125
|
-
|
|
126
|
-
const result = modifyRoutes({
|
|
127
|
-
entrypoint: { entryName: 'main' },
|
|
128
|
-
routes,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
return result.routes;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
test('creates deterministic localized aliases with canonical route identity', () => {
|
|
135
|
-
const localisedRoutes = generateLocalisedRoutes();
|
|
136
|
-
expect(generateLocalisedRoutes()).toEqual(localisedRoutes);
|
|
137
|
-
|
|
138
|
-
const [localeLayout] = localisedRoutes;
|
|
139
|
-
expect(localeLayout.children?.map(route => route.path)).toEqual([
|
|
140
|
-
'about',
|
|
141
|
-
'o-nas',
|
|
142
|
-
'ueber-uns',
|
|
143
|
-
'products',
|
|
144
|
-
'produkty',
|
|
145
|
-
'produkte',
|
|
146
|
-
'docs',
|
|
147
|
-
'dokumenty',
|
|
148
|
-
'dokumente',
|
|
149
|
-
]);
|
|
150
|
-
expect(localeLayout.children?.slice(0, 3)).toMatchObject([
|
|
151
|
-
{
|
|
152
|
-
_component: 'about.tsx',
|
|
153
|
-
modernCanonicalPath: '/about',
|
|
154
|
-
path: 'about',
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
_component: 'about.tsx',
|
|
158
|
-
modernCanonicalPath: '/about',
|
|
159
|
-
path: 'o-nas',
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
_component: 'about.tsx',
|
|
163
|
-
modernCanonicalPath: '/about',
|
|
164
|
-
path: 'ueber-uns',
|
|
165
|
-
},
|
|
166
|
-
]);
|
|
167
|
-
for (const productRoute of localeLayout.children?.slice(3, 6) ?? []) {
|
|
168
|
-
expect(productRoute).toMatchObject({
|
|
169
|
-
_component: 'products.tsx',
|
|
170
|
-
modernCanonicalPath: '/products',
|
|
171
|
-
});
|
|
172
|
-
expect(productRoute.children?.[0]).toMatchObject({
|
|
173
|
-
_component: ':slug.tsx',
|
|
174
|
-
modernCanonicalPath: '/products/:slug',
|
|
175
|
-
path: ':slug',
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test('upstream-style configs without a map keep routes untouched', () => {
|
|
181
|
-
const modifyRoutes = setupModifyRoutes({
|
|
182
|
-
localePathRedirect: true,
|
|
183
|
-
languages: ['en', 'cs'],
|
|
184
|
-
});
|
|
185
|
-
const routes = [createRoute(':lang', [createRoute('about')])];
|
|
186
|
-
|
|
187
|
-
const result = modifyRoutes({ entrypoint: { entryName: 'main' }, routes });
|
|
188
|
-
|
|
189
|
-
expect(result.routes).toBe(routes);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test('a configured map still expands localised route aliases', () => {
|
|
193
|
-
const modifyRoutes = setupModifyRoutes({
|
|
194
|
-
localePathRedirect: true,
|
|
195
|
-
languages: ['en', 'cs'],
|
|
196
|
-
localisedUrls: {
|
|
197
|
-
'/about': { en: '/about', cs: '/o-nas' },
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
const routes = [createRoute(':lang', [createRoute('about')])];
|
|
201
|
-
|
|
202
|
-
const result = modifyRoutes({ entrypoint: { entryName: 'main' }, routes });
|
|
203
|
-
|
|
204
|
-
const localeRoute = result.routes[0] as NestedRouteForCli;
|
|
205
|
-
expect(localeRoute.children?.map(route => route.path)).toEqual([
|
|
206
|
-
'about',
|
|
207
|
-
'o-nas',
|
|
208
|
-
]);
|
|
61
|
+
expect(modifyRoutes).not.toHaveBeenCalled();
|
|
209
62
|
});
|
|
210
63
|
});
|
|
211
64
|
|
|
@@ -283,68 +136,6 @@ describe('i18n server API prefix skips', () => {
|
|
|
283
136
|
}
|
|
284
137
|
});
|
|
285
138
|
|
|
286
|
-
test('canonical redirect survives malformed percent-encoding', async () => {
|
|
287
|
-
const middlewares: any[] = [];
|
|
288
|
-
const routes = [{ entryName: 'main', entryPath: '', urlPath: '/' }];
|
|
289
|
-
let prepare: (() => void) | undefined;
|
|
290
|
-
|
|
291
|
-
i18nServerPlugin({
|
|
292
|
-
localeDetection: {
|
|
293
|
-
fallbackLanguage: 'en',
|
|
294
|
-
languages: ['en', 'cs'],
|
|
295
|
-
localePathRedirect: true,
|
|
296
|
-
localisedUrls: {
|
|
297
|
-
'/products/:slug': {
|
|
298
|
-
en: '/products/:slug',
|
|
299
|
-
cs: '/produkty/:slug',
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
staticRoutePrefixes: [],
|
|
304
|
-
}).setup({
|
|
305
|
-
getServerConfig: () => ({}),
|
|
306
|
-
getServerContext: () => ({ middlewares, routes }),
|
|
307
|
-
onPrepare: fn => {
|
|
308
|
-
prepare = fn;
|
|
309
|
-
},
|
|
310
|
-
} as any);
|
|
311
|
-
|
|
312
|
-
prepare?.();
|
|
313
|
-
|
|
314
|
-
const redirectMiddleware = middlewares.find(
|
|
315
|
-
middleware => middleware.name === 'i18n-server-middleware',
|
|
316
|
-
);
|
|
317
|
-
const createContext = (pathname: string) =>
|
|
318
|
-
({
|
|
319
|
-
req: {
|
|
320
|
-
url: `http://localhost${pathname}`,
|
|
321
|
-
header: () => ({ host: 'localhost' }),
|
|
322
|
-
},
|
|
323
|
-
get: () => null,
|
|
324
|
-
}) as any;
|
|
325
|
-
|
|
326
|
-
// Sanity: well-formed non-canonical slugs still redirect.
|
|
327
|
-
const redirected = await redirectMiddleware.handler(
|
|
328
|
-
createContext('/cs/products/bota'),
|
|
329
|
-
async () => {},
|
|
330
|
-
);
|
|
331
|
-
expect(redirected.status).toBe(302);
|
|
332
|
-
expect(redirected.headers.get('location')).toBe('/cs/produkty/bota');
|
|
333
|
-
expect(redirected.headers.get('cache-control')).toBe('private, no-store');
|
|
334
|
-
expect(redirected.headers.get('vary')).toBe('Accept-Language, Cookie');
|
|
335
|
-
|
|
336
|
-
// Malformed encoding must fall through to next() instead of throwing.
|
|
337
|
-
let nextCalls = 0;
|
|
338
|
-
const response = await redirectMiddleware.handler(
|
|
339
|
-
createContext('/cs/produkty/%E0%A4%A'),
|
|
340
|
-
async () => {
|
|
341
|
-
nextCalls++;
|
|
342
|
-
},
|
|
343
|
-
);
|
|
344
|
-
expect(response).toBeUndefined();
|
|
345
|
-
expect(nextCalls).toBe(1);
|
|
346
|
-
});
|
|
347
|
-
|
|
348
139
|
test('uses /api as the BFF prefix when BFF config is present without prefix', async () => {
|
|
349
140
|
const middlewares: any[] = [];
|
|
350
141
|
const routes = [{ entryName: 'main', entryPath: '', urlPath: '/' }];
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
|
-
import {
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
import { describe, expect, test } from '@rstest/core';
|
|
6
6
|
import { build } from 'esbuild';
|
|
7
7
|
import i18next from 'i18next';
|
|
8
8
|
import { createElement } from 'react';
|
|
9
9
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
10
|
-
import { i18nPlugin as noReactI18nextPlugin } from '../src/runtime/no-react-i18next';
|
|
11
10
|
|
|
12
11
|
const runtimeRoot = resolve(__dirname, '../src/runtime');
|
|
13
12
|
|
|
@@ -37,31 +36,6 @@ describe('react-i18next runtime boundary', () => {
|
|
|
37
36
|
).resolves.toBeDefined();
|
|
38
37
|
});
|
|
39
38
|
|
|
40
|
-
test('bundles the default runtime entry with the react-i18next adapter', async () => {
|
|
41
|
-
let adapterResolutions = 0;
|
|
42
|
-
await build({
|
|
43
|
-
bundle: true,
|
|
44
|
-
entryPoints: [resolve(runtimeRoot, 'index.tsx')],
|
|
45
|
-
format: 'esm',
|
|
46
|
-
packages: 'external',
|
|
47
|
-
platform: 'node',
|
|
48
|
-
plugins: [
|
|
49
|
-
{
|
|
50
|
-
name: 'observe-react-i18next',
|
|
51
|
-
setup(buildApi) {
|
|
52
|
-
buildApi.onResolve({ filter: /^react-i18next$/ }, args => {
|
|
53
|
-
adapterResolutions += 1;
|
|
54
|
-
return { external: true, path: args.path };
|
|
55
|
-
});
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
write: false,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
expect(adapterResolutions).toBeGreaterThan(0);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
39
|
test('gets router capabilities from the selected runtime provider', async () => {
|
|
66
40
|
await expect(
|
|
67
41
|
build({
|
|
@@ -90,61 +64,6 @@ describe('react-i18next runtime boundary', () => {
|
|
|
90
64
|
).resolves.toBeDefined();
|
|
91
65
|
});
|
|
92
66
|
|
|
93
|
-
test('keeps expensive lifecycle helpers behind asynchronous bundle edges', async () => {
|
|
94
|
-
const result = await build({
|
|
95
|
-
bundle: true,
|
|
96
|
-
entryPoints: [resolve(runtimeRoot, 'pluginSetup.ts')],
|
|
97
|
-
format: 'esm',
|
|
98
|
-
metafile: true,
|
|
99
|
-
outdir: 'out',
|
|
100
|
-
packages: 'external',
|
|
101
|
-
platform: 'node',
|
|
102
|
-
splitting: true,
|
|
103
|
-
write: false,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const entryOutput = Object.values(result.metafile.outputs).find(output =>
|
|
107
|
-
output.entryPoint?.endsWith('/pluginSetup.ts'),
|
|
108
|
-
);
|
|
109
|
-
if (!entryOutput) {
|
|
110
|
-
throw new Error('pluginSetup entry output was not generated');
|
|
111
|
-
}
|
|
112
|
-
const dynamicOutputNames = entryOutput.imports
|
|
113
|
-
.filter(moduleImport => moduleImport.kind === 'dynamic-import')
|
|
114
|
-
.map(moduleImport => basename(moduleImport.path));
|
|
115
|
-
const dynamicOutputs = Object.entries(result.metafile.outputs)
|
|
116
|
-
.filter(([outputPath]) =>
|
|
117
|
-
dynamicOutputNames.includes(basename(outputPath)),
|
|
118
|
-
)
|
|
119
|
-
.map(([, output]) => output);
|
|
120
|
-
const dynamicEntries = dynamicOutputs.map(output =>
|
|
121
|
-
basename(output.entryPoint ?? ''),
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
expect(dynamicEntries).toEqual(
|
|
125
|
-
expect.arrayContaining(['middleware.ts', 'utils.ts']),
|
|
126
|
-
);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('registers plugin lifecycle hooks synchronously', () => {
|
|
130
|
-
let beforeRender: unknown;
|
|
131
|
-
let rootWrapper: unknown;
|
|
132
|
-
const plugin = noReactI18nextPlugin({ reactI18next: false });
|
|
133
|
-
const setupResult = plugin.setup?.({
|
|
134
|
-
getRuntimeConfig: () => ({}),
|
|
135
|
-
onBeforeRender: hook => {
|
|
136
|
-
beforeRender = hook;
|
|
137
|
-
},
|
|
138
|
-
wrapRoot: wrapper => {
|
|
139
|
-
rootWrapper = wrapper;
|
|
140
|
-
},
|
|
141
|
-
} as any);
|
|
142
|
-
|
|
143
|
-
expect(setupResult).toBeUndefined();
|
|
144
|
-
expect(typeof beforeRender).toBe('function');
|
|
145
|
-
expect(typeof rootWrapper).toBe('function');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
67
|
test('shares Modern i18n context across independently bundled runtime copies', async () => {
|
|
149
68
|
const tempDir = await mkdtemp(
|
|
150
69
|
resolve(__dirname, '.modern-i18n-runtime-boundary-'),
|
|
@@ -5,41 +5,50 @@ import {
|
|
|
5
5
|
shouldIgnoreRedirect as shouldIgnoreServerRedirect,
|
|
6
6
|
} from '../src/server/redirectPolicy';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
'/backend-mf-manifest.json',
|
|
12
|
-
'/backendRemoteEntry.cjs',
|
|
13
|
-
'/mf-manifest.json',
|
|
14
|
-
'/mf-stats.json',
|
|
15
|
-
'/remoteEntry.js',
|
|
16
|
-
'/static/app.js',
|
|
17
|
-
'/upload/avatar.png',
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
describe('locale redirect default skip policy', () => {
|
|
21
|
-
test('skips ADR-0002 Module Federation and static endpoints on the server', () => {
|
|
22
|
-
for (const pathname of defaultSkippedPaths) {
|
|
23
|
-
expect(isStaticResourceRequest(pathname, [], languages)).toBe(true);
|
|
24
|
-
expect(shouldIgnoreServerRedirect(pathname, '/', undefined)).toBe(true);
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('skips language-prefixed static and upload endpoints', () => {
|
|
29
|
-
expect(isStaticResourceRequest('/cs/static/app.js', [], languages)).toBe(
|
|
30
|
-
true,
|
|
31
|
-
);
|
|
8
|
+
describe('native locale redirect safeguards', () => {
|
|
9
|
+
test('retains configured ignores across runtime and server entry prefixes', () => {
|
|
10
|
+
const rules = ['/private'];
|
|
32
11
|
expect(
|
|
33
|
-
|
|
12
|
+
shouldIgnoreRuntimeRedirect('/cs/private/data', ['en', 'cs'], rules),
|
|
34
13
|
).toBe(true);
|
|
14
|
+
expect(
|
|
15
|
+
shouldIgnoreRuntimeRedirect('/cs/private-data', ['en', 'cs'], rules),
|
|
16
|
+
).toBe(false);
|
|
17
|
+
expect(
|
|
18
|
+
shouldIgnoreServerRedirect(
|
|
19
|
+
'/app/cs/private/data',
|
|
20
|
+
'/app/*',
|
|
21
|
+
rules,
|
|
22
|
+
undefined,
|
|
23
|
+
['en', 'cs'],
|
|
24
|
+
),
|
|
25
|
+
).toBe(true);
|
|
26
|
+
expect(
|
|
27
|
+
shouldIgnoreServerRedirect(
|
|
28
|
+
'/app/cs/private-data',
|
|
29
|
+
'/app/*',
|
|
30
|
+
rules,
|
|
31
|
+
undefined,
|
|
32
|
+
['en', 'cs'],
|
|
33
|
+
),
|
|
34
|
+
).toBe(false);
|
|
35
35
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
test('retains configured and native static paths without fork policy', () => {
|
|
37
|
+
for (const pathname of [
|
|
38
|
+
'/static/app.js',
|
|
39
|
+
'/upload/a.png',
|
|
40
|
+
'/cs/assets/main.js',
|
|
41
|
+
]) {
|
|
42
|
+
expect(isStaticResourceRequest(pathname, ['/assets'], ['en', 'cs'])).toBe(
|
|
41
43
|
true,
|
|
42
44
|
);
|
|
43
45
|
}
|
|
46
|
+
expect(
|
|
47
|
+
isStaticResourceRequest(
|
|
48
|
+
'/assets-other/main.js',
|
|
49
|
+
['/assets'],
|
|
50
|
+
['en', 'cs'],
|
|
51
|
+
),
|
|
52
|
+
).toBe(false);
|
|
44
53
|
});
|
|
45
54
|
});
|