@bleedingdev/modern-js-i18n-runtime-extensions 3.9.0-ultramodern.3 → 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/index.js +9 -2
- package/dist/cjs/localeRedirectPolicy.js +96 -0
- package/dist/cjs/localisedUrls/index.js +7 -0
- package/dist/cjs/localisedUrls/routeIdentity.js +118 -0
- package/dist/cjs/localisedUrls/routes.js +8 -0
- package/dist/cjs/paths.js +56 -0
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/localeRedirectPolicy.mjs +46 -0
- package/dist/esm/localisedUrls/index.mjs +1 -0
- package/dist/esm/localisedUrls/routeIdentity.mjs +77 -0
- package/dist/esm/localisedUrls/routes.mjs +8 -0
- package/dist/esm/paths.mjs +12 -0
- package/dist/esm-node/index.mjs +1 -0
- package/dist/esm-node/localeRedirectPolicy.mjs +47 -0
- package/dist/esm-node/localisedUrls/index.mjs +1 -0
- package/dist/esm-node/localisedUrls/routeIdentity.mjs +78 -0
- package/dist/esm-node/localisedUrls/routes.mjs +8 -0
- package/dist/esm-node/paths.mjs +13 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/localeRedirectPolicy.d.ts +22 -0
- package/dist/types/localisedUrls/index.d.ts +1 -0
- package/dist/types/localisedUrls/routeIdentity.d.ts +24 -0
- package/dist/types/paths.d.ts +9 -0
- package/package.json +16 -4
- package/src/index.ts +1 -0
- package/src/localeRedirectPolicy.ts +127 -0
- package/src/localisedUrls/index.ts +4 -0
- package/src/localisedUrls/routeIdentity.ts +107 -0
- package/src/localisedUrls/routes.ts +10 -0
- package/src/paths.ts +37 -0
- package/tests/localeRedirectPolicy.test.ts +205 -0
- package/tests/localisedUrls.test.ts +3 -113
- package/tests/paths.test.ts +189 -0
- package/tests/reactLanguageSync.test.tsx +20 -1
- package/tests/clientRedirectIntegration.test.tsx +0 -74
|
@@ -25,13 +25,11 @@ const createRoute = (
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
describe('resolveLocalisedUrlsConfig', () => {
|
|
28
|
-
test('
|
|
28
|
+
test('enables the feature only for a non-empty map', () => {
|
|
29
29
|
const map = { '/about': { en: '/about', cs: '/o-nas' } };
|
|
30
|
-
expect(resolveLocalisedUrlsConfig(map)).toEqual({ enabled: true, map });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('absent, boolean and empty-map options resolve to disabled', () => {
|
|
34
30
|
const disabled = { enabled: false, map: {} };
|
|
31
|
+
|
|
32
|
+
expect(resolveLocalisedUrlsConfig(map)).toEqual({ enabled: true, map });
|
|
35
33
|
expect(resolveLocalisedUrlsConfig(undefined)).toEqual(disabled);
|
|
36
34
|
expect(resolveLocalisedUrlsConfig(false)).toEqual(disabled);
|
|
37
35
|
expect(resolveLocalisedUrlsConfig(true)).toEqual(disabled);
|
|
@@ -207,9 +205,6 @@ describe('localisedUrls', () => {
|
|
|
207
205
|
localisedUrls,
|
|
208
206
|
);
|
|
209
207
|
|
|
210
|
-
expect(
|
|
211
|
-
applyLocalisedUrlsToRoutes(routes, ['en', 'cs', 'de'], localisedUrls),
|
|
212
|
-
).toEqual(localisedRoutes);
|
|
213
208
|
expect(localisedRoutes.map(route => route.path)).toEqual([
|
|
214
209
|
':lang/about',
|
|
215
210
|
':lang/o.nas',
|
|
@@ -304,62 +299,6 @@ describe('localisedUrls', () => {
|
|
|
304
299
|
]);
|
|
305
300
|
});
|
|
306
301
|
|
|
307
|
-
test('expands flat locale-prefixed route paths with canonical keys', () => {
|
|
308
|
-
const routes = [
|
|
309
|
-
createRoute('/:lang/about'),
|
|
310
|
-
createRoute('/:lang/products/:slug'),
|
|
311
|
-
];
|
|
312
|
-
|
|
313
|
-
const localisedRoutes = applyLocalisedUrlsToRoutes(routes, ['en', 'cs'], {
|
|
314
|
-
'/about': {
|
|
315
|
-
en: '/about',
|
|
316
|
-
cs: '/o-nas',
|
|
317
|
-
},
|
|
318
|
-
'/products/:slug': {
|
|
319
|
-
en: '/products/:slug',
|
|
320
|
-
cs: '/produkty/:slug',
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
expect(localisedRoutes.map(route => route.path)).toEqual([
|
|
325
|
-
':lang/about',
|
|
326
|
-
':lang/o-nas',
|
|
327
|
-
':lang/products/:slug',
|
|
328
|
-
':lang/produkty/:slug',
|
|
329
|
-
]);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
test('resolves current localized path to the target language', () => {
|
|
333
|
-
const localisedUrls = {
|
|
334
|
-
'/terms-of-service': {
|
|
335
|
-
en: '/terms-of-service',
|
|
336
|
-
cs: '/podminky-pouzivani',
|
|
337
|
-
},
|
|
338
|
-
'/products/:slug': {
|
|
339
|
-
en: '/products/:slug',
|
|
340
|
-
cs: '/produkty/:slug',
|
|
341
|
-
},
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
expect(
|
|
345
|
-
resolveLocalisedPath(
|
|
346
|
-
'/terms-of-service',
|
|
347
|
-
'cs',
|
|
348
|
-
['en', 'cs'],
|
|
349
|
-
localisedUrls,
|
|
350
|
-
),
|
|
351
|
-
).toBe('/podminky-pouzivani');
|
|
352
|
-
expect(
|
|
353
|
-
resolveLocalisedPath('/produkty/cervena-bota', 'en', ['en', 'cs'], {
|
|
354
|
-
...localisedUrls,
|
|
355
|
-
'/products/:slug': {
|
|
356
|
-
en: '/products/:slug',
|
|
357
|
-
cs: '/produkty/:slug',
|
|
358
|
-
},
|
|
359
|
-
}),
|
|
360
|
-
).toBe('/products/cervena-bota');
|
|
361
|
-
});
|
|
362
|
-
|
|
363
302
|
test('resolves optional route params', () => {
|
|
364
303
|
const localisedUrls = {
|
|
365
304
|
'/products/:slug?': {
|
|
@@ -455,30 +394,6 @@ describe('localisedUrls', () => {
|
|
|
455
394
|
).toBe('/products/new');
|
|
456
395
|
});
|
|
457
396
|
|
|
458
|
-
test('reverse resolution is deterministic for equally specific matches', () => {
|
|
459
|
-
const firstOrder = {
|
|
460
|
-
'/z/:slug': {
|
|
461
|
-
en: '/x/:slug',
|
|
462
|
-
cs: '/z/:slug',
|
|
463
|
-
},
|
|
464
|
-
'/a/:section': {
|
|
465
|
-
en: '/:section/y',
|
|
466
|
-
cs: '/a/:section',
|
|
467
|
-
},
|
|
468
|
-
};
|
|
469
|
-
const reverseOrder = {
|
|
470
|
-
'/a/:section': firstOrder['/a/:section'],
|
|
471
|
-
'/z/:slug': firstOrder['/z/:slug'],
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
expect(
|
|
475
|
-
resolveCanonicalLocalisedPath('/x/y', ['en', 'cs'], firstOrder),
|
|
476
|
-
).toBe('/a/x');
|
|
477
|
-
expect(
|
|
478
|
-
resolveCanonicalLocalisedPath('/x/y', ['en', 'cs'], reverseOrder),
|
|
479
|
-
).toBe('/a/x');
|
|
480
|
-
});
|
|
481
|
-
|
|
482
397
|
test('reverse resolution prefers the most specific localised source pattern', () => {
|
|
483
398
|
const localisedUrls = {
|
|
484
399
|
'/catalog/:section/:item/:detail': {
|
|
@@ -537,31 +452,6 @@ describe('localisedUrls', () => {
|
|
|
537
452
|
).toThrow('Missing required path parameter "toString"');
|
|
538
453
|
});
|
|
539
454
|
|
|
540
|
-
test('localises and canonicalises full target pathnames through one helper', () => {
|
|
541
|
-
const localisedUrls = {
|
|
542
|
-
'/products/:slug': {
|
|
543
|
-
en: '/products/:slug',
|
|
544
|
-
cs: '/produkty/:slug',
|
|
545
|
-
},
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
expect(
|
|
549
|
-
localiseTargetPathname(
|
|
550
|
-
'/en/products/cervena-bota',
|
|
551
|
-
'cs',
|
|
552
|
-
['en', 'cs'],
|
|
553
|
-
localisedUrls,
|
|
554
|
-
),
|
|
555
|
-
).toBe('/cs/produkty/cervena-bota');
|
|
556
|
-
expect(
|
|
557
|
-
canonicalTargetPathname(
|
|
558
|
-
'/cs/produkty/cervena-bota',
|
|
559
|
-
['en', 'cs'],
|
|
560
|
-
localisedUrls,
|
|
561
|
-
),
|
|
562
|
-
).toBe('/products/cervena-bota');
|
|
563
|
-
});
|
|
564
|
-
|
|
565
455
|
test('strips case-insensitive locale prefixes before relocalising full pathnames', () => {
|
|
566
456
|
const localisedUrls = {
|
|
567
457
|
'/products/:slug': {
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { describe, expect, test } from '@rstest/core';
|
|
2
|
+
import type { LocalisedUrlsMap } from '../src/localisedUrls';
|
|
3
|
+
import { buildLocalizedUrl, canonicalPath, localizePath } from '../src/paths';
|
|
4
|
+
|
|
5
|
+
const languages = ['en', 'cs'];
|
|
6
|
+
|
|
7
|
+
const localisedUrls = {
|
|
8
|
+
'/products/:slug': {
|
|
9
|
+
en: '/products/:slug',
|
|
10
|
+
cs: '/produkty/:slug',
|
|
11
|
+
},
|
|
12
|
+
'/files/*': {
|
|
13
|
+
en: '/files/*',
|
|
14
|
+
cs: '/soubory/*',
|
|
15
|
+
},
|
|
16
|
+
'/app/products/:slug': {
|
|
17
|
+
en: '/app/products/:slug',
|
|
18
|
+
cs: '/app/produkty/:slug',
|
|
19
|
+
},
|
|
20
|
+
} satisfies LocalisedUrlsMap;
|
|
21
|
+
|
|
22
|
+
const pathsConfig = {
|
|
23
|
+
languages,
|
|
24
|
+
localisedUrls,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('fork-owned localised URL rewrite matrix', () => {
|
|
28
|
+
const rewriteScenarios = [
|
|
29
|
+
{
|
|
30
|
+
name: 'adds the target locale prefix and localises canonical segments',
|
|
31
|
+
target: '/products/red-shoe',
|
|
32
|
+
language: 'cs',
|
|
33
|
+
expected: '/cs/produkty/red-shoe',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'strips a case-insensitive source locale before relocalising',
|
|
37
|
+
target: '/CS/produkty/red%20shoe?tag=boots#details',
|
|
38
|
+
language: 'en',
|
|
39
|
+
expected: '/en/products/red%20shoe?tag=boots#details',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'preserves repeated query keys and the fragment',
|
|
43
|
+
target: '/products/red-shoe?tag=boots&tag=sale#details',
|
|
44
|
+
language: 'cs',
|
|
45
|
+
expected: '/cs/produkty/red-shoe?tag=boots&tag=sale#details',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'keeps a base-path segment inside the localised route map',
|
|
49
|
+
target: '/app/products/red-shoe?ref=nav#details',
|
|
50
|
+
language: 'cs',
|
|
51
|
+
expected: '/cs/app/produkty/red-shoe?ref=nav#details',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'keeps the default locale explicitly prefixed',
|
|
55
|
+
target: '/products/red-shoe',
|
|
56
|
+
language: 'en',
|
|
57
|
+
expected: '/en/products/red-shoe',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'does not double-prefix an already-localised URL',
|
|
61
|
+
target: '/cs/produkty/red-shoe?tag=boots#details',
|
|
62
|
+
language: 'cs',
|
|
63
|
+
expected: '/cs/produkty/red-shoe?tag=boots#details',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
for (const scenario of rewriteScenarios) {
|
|
68
|
+
test(scenario.name, () => {
|
|
69
|
+
expect(
|
|
70
|
+
buildLocalizedUrl(
|
|
71
|
+
scenario.target,
|
|
72
|
+
scenario.language,
|
|
73
|
+
languages,
|
|
74
|
+
localisedUrls,
|
|
75
|
+
),
|
|
76
|
+
).toBe(scenario.expected);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
test('canonical and localized path helpers strip and add prefixes exactly', () => {
|
|
81
|
+
expect(
|
|
82
|
+
canonicalPath('/CS/produkty/red-shoe?tag=boots#details', pathsConfig),
|
|
83
|
+
).toBe('/products/red-shoe?tag=boots#details');
|
|
84
|
+
expect(
|
|
85
|
+
localizePath('/products/red-shoe?tag=boots#details', 'cs', pathsConfig),
|
|
86
|
+
).toBe('/cs/produkty/red-shoe?tag=boots#details');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('splat params preserve separators and percent-encode each segment', () => {
|
|
90
|
+
expect(
|
|
91
|
+
buildLocalizedUrl(
|
|
92
|
+
'/files/resume%20drafts/Q1%20deck.pdf',
|
|
93
|
+
'cs',
|
|
94
|
+
languages,
|
|
95
|
+
localisedUrls,
|
|
96
|
+
),
|
|
97
|
+
).toBe('/cs/soubory/resume%20drafts/Q1%20deck.pdf');
|
|
98
|
+
});
|
|
99
|
+
test('explicit maps work without plugin registration and keep suffixes verbatim', () => {
|
|
100
|
+
const config = {
|
|
101
|
+
languages,
|
|
102
|
+
localisedUrls: {
|
|
103
|
+
'/docs/:slug?': { en: '/docs/:slug?', cs: '/dokumenty/:slug?' },
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
expect(localizePath('/docs?x=%2f&x=2#literal?query', 'cs', config)).toBe(
|
|
107
|
+
'/cs/dokumenty?x=%2f&x=2#literal?query',
|
|
108
|
+
);
|
|
109
|
+
expect(canonicalPath('/cs/dokumenty/%E0%A4%A', config)).toBe(
|
|
110
|
+
'/dokumenty/%E0%A4%A',
|
|
111
|
+
);
|
|
112
|
+
expect(localizePath('/[literal]?x=1#x', 'cs', { languages })).toBe(
|
|
113
|
+
'/cs/[literal]?x=1#x',
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('migrated public mapped helper contract', () => {
|
|
119
|
+
const localisedUrls = {
|
|
120
|
+
'/terms-of-service': {
|
|
121
|
+
en: '/terms-of-service',
|
|
122
|
+
cs: '/podminky-pouzivani',
|
|
123
|
+
},
|
|
124
|
+
'/products': {
|
|
125
|
+
en: '/products',
|
|
126
|
+
cs: '/produkty',
|
|
127
|
+
},
|
|
128
|
+
'/products/:slug': {
|
|
129
|
+
en: '/products/:slug',
|
|
130
|
+
cs: '/produkty/:slug',
|
|
131
|
+
},
|
|
132
|
+
// Canonical key that matches no language pattern.
|
|
133
|
+
'/talks/:slug': {
|
|
134
|
+
en: '/lectures/:slug',
|
|
135
|
+
cs: '/prednasky/:slug',
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const languages = ['en', 'cs'];
|
|
140
|
+
const pathsConfig = { languages, localisedUrls };
|
|
141
|
+
|
|
142
|
+
test('query and hash survive localized slug mapping', () => {
|
|
143
|
+
expect(
|
|
144
|
+
buildLocalizedUrl(
|
|
145
|
+
'/products/bota?tag=x#detail',
|
|
146
|
+
'cs',
|
|
147
|
+
languages,
|
|
148
|
+
localisedUrls,
|
|
149
|
+
),
|
|
150
|
+
).toBe('/cs/produkty/bota?tag=x#detail');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('localizes canonical keys that match no language pattern', () => {
|
|
154
|
+
expect(
|
|
155
|
+
buildLocalizedUrl(
|
|
156
|
+
'/talks/ai-slop#abstract',
|
|
157
|
+
'cs',
|
|
158
|
+
languages,
|
|
159
|
+
localisedUrls,
|
|
160
|
+
),
|
|
161
|
+
).toBe('/cs/prednasky/ai-slop#abstract');
|
|
162
|
+
expect(
|
|
163
|
+
buildLocalizedUrl('/talks/ai-slop', 'en', languages, localisedUrls),
|
|
164
|
+
).toBe('/en/lectures/ai-slop');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('re-localizes already-localized paths', () => {
|
|
168
|
+
expect(
|
|
169
|
+
buildLocalizedUrl('/cs/produkty/bota#x', 'en', languages, localisedUrls),
|
|
170
|
+
).toBe('/en/products/bota#x');
|
|
171
|
+
});
|
|
172
|
+
test('localizePath maps canonical paths per language', () => {
|
|
173
|
+
expect(localizePath('/products/bota', 'cs', pathsConfig)).toBe(
|
|
174
|
+
'/cs/produkty/bota',
|
|
175
|
+
);
|
|
176
|
+
expect(localizePath('/talks/x', 'en', pathsConfig)).toBe('/en/lectures/x');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('canonicalPath strips language and reverse-maps localized slugs', () => {
|
|
180
|
+
expect(canonicalPath('/cs/produkty/bota', pathsConfig)).toBe(
|
|
181
|
+
'/products/bota',
|
|
182
|
+
);
|
|
183
|
+
expect(canonicalPath('/en/lectures/x?q=1#h', pathsConfig)).toBe(
|
|
184
|
+
'/talks/x?q=1#h',
|
|
185
|
+
);
|
|
186
|
+
expect(canonicalPath('/cs', pathsConfig)).toBe('/');
|
|
187
|
+
expect(canonicalPath('/en/products', pathsConfig)).toBe('/products');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -7,6 +7,14 @@ interface Target {
|
|
|
7
7
|
language: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
rstest.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
rstest.unstubAllGlobals();
|
|
16
|
+
});
|
|
17
|
+
|
|
10
18
|
test('an abandoned suspended render cannot publish an uncommitted target', async () => {
|
|
11
19
|
let resolveFirst!: () => void;
|
|
12
20
|
const firstChange = new Promise<void>(resolve => {
|
|
@@ -14,6 +22,7 @@ test('an abandoned suspended render cannot publish an uncommitted target', async
|
|
|
14
22
|
});
|
|
15
23
|
const changes: string[] = [];
|
|
16
24
|
const commits: string[] = [];
|
|
25
|
+
let suspended = false;
|
|
17
26
|
const neverSettles = new Promise<never>(() => undefined);
|
|
18
27
|
const first = { id: 'first', language: 'en' };
|
|
19
28
|
const speculative = { id: 'speculative', language: 'en' };
|
|
@@ -41,6 +50,7 @@ test('an abandoned suspended render cannot publish an uncommitted target', async
|
|
|
41
50
|
},
|
|
42
51
|
});
|
|
43
52
|
if (suspend) {
|
|
53
|
+
suspended = true;
|
|
44
54
|
throw neverSettles;
|
|
45
55
|
}
|
|
46
56
|
return <main>{target.id}</main>;
|
|
@@ -49,7 +59,11 @@ test('an abandoned suspended render cannot publish an uncommitted target', async
|
|
|
49
59
|
const container = document.createElement('div');
|
|
50
60
|
const root = createRoot(container);
|
|
51
61
|
await act(async () => {
|
|
52
|
-
root.render(
|
|
62
|
+
root.render(
|
|
63
|
+
<Suspense fallback={<p>loading</p>}>
|
|
64
|
+
<Harness target={first} />
|
|
65
|
+
</Suspense>,
|
|
66
|
+
);
|
|
53
67
|
});
|
|
54
68
|
expect(changes).toEqual(['first']);
|
|
55
69
|
|
|
@@ -64,6 +78,11 @@ test('an abandoned suspended render cannot publish an uncommitted target', async
|
|
|
64
78
|
await Promise.resolve();
|
|
65
79
|
});
|
|
66
80
|
|
|
81
|
+
expect(suspended).toBe(true);
|
|
82
|
+
expect(container.textContent).toBe('first');
|
|
83
|
+
expect(changes).toEqual(['first']);
|
|
84
|
+
expect(commits).toEqual([]);
|
|
85
|
+
|
|
67
86
|
await act(async () => {
|
|
68
87
|
resolveFirst();
|
|
69
88
|
await firstChange;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import React, { act } from 'react';
|
|
2
|
-
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import { useClientSideRedirect } from '../../plugin-i18n/src/runtime/hooks';
|
|
4
|
-
|
|
5
|
-
const navigate = rstest.fn(async () => undefined);
|
|
6
|
-
let location = {
|
|
7
|
-
pathname: '/cs/about',
|
|
8
|
-
search: '?from=test',
|
|
9
|
-
hash: '#section',
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
rstest.mock('../../plugin-i18n/src/runtime/routerAdapter', () => ({
|
|
13
|
-
useI18nRouterAdapter: () => ({
|
|
14
|
-
hasRouter: true,
|
|
15
|
-
location,
|
|
16
|
-
navigate,
|
|
17
|
-
}),
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
test('canonicalizes successive prefixed routes in one mounted provider', async () => {
|
|
21
|
-
const previousTarget = process.env.MODERN_TARGET;
|
|
22
|
-
process.env.MODERN_TARGET = 'browser';
|
|
23
|
-
const instance = {
|
|
24
|
-
language: 'cs',
|
|
25
|
-
isInitialized: true,
|
|
26
|
-
} as Parameters<typeof useClientSideRedirect>[0];
|
|
27
|
-
const localisedUrls = {
|
|
28
|
-
'/about': { en: '/about', cs: '/o-nas' },
|
|
29
|
-
'/terms-of-service': {
|
|
30
|
-
en: '/terms-of-service',
|
|
31
|
-
cs: '/podminky-pouzivani',
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
const Harness = () => {
|
|
35
|
-
useClientSideRedirect(
|
|
36
|
-
instance,
|
|
37
|
-
true,
|
|
38
|
-
['en', 'cs'],
|
|
39
|
-
'en',
|
|
40
|
-
undefined,
|
|
41
|
-
localisedUrls,
|
|
42
|
-
);
|
|
43
|
-
return null;
|
|
44
|
-
};
|
|
45
|
-
const container = document.createElement('div');
|
|
46
|
-
const root = createRoot(container);
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
window.history.replaceState(null, '', '/cs/about?from=test#section');
|
|
50
|
-
await act(async () => root.render(<Harness />));
|
|
51
|
-
expect(navigate).toHaveBeenLastCalledWith('/cs/o-nas?from=test#section', {
|
|
52
|
-
replace: true,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
location = {
|
|
56
|
-
pathname: '/cs/terms-of-service',
|
|
57
|
-
search: '',
|
|
58
|
-
hash: '',
|
|
59
|
-
};
|
|
60
|
-
window.history.replaceState(null, '', '/cs/terms-of-service');
|
|
61
|
-
await act(async () => root.render(<Harness />));
|
|
62
|
-
expect(navigate).toHaveBeenLastCalledWith('/cs/podminky-pouzivani', {
|
|
63
|
-
replace: true,
|
|
64
|
-
});
|
|
65
|
-
expect(navigate).toHaveBeenCalledTimes(2);
|
|
66
|
-
} finally {
|
|
67
|
-
await act(async () => root.unmount());
|
|
68
|
-
if (previousTarget === undefined) {
|
|
69
|
-
delete process.env.MODERN_TARGET;
|
|
70
|
-
} else {
|
|
71
|
-
process.env.MODERN_TARGET = previousTarget;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
});
|