@akinon/next 1.93.0-rc.48 → 1.93.0-rc.50
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/CHANGELOG.md +88 -0
- package/__tests__/redirect.test.ts +244 -683
- package/bin/pz-prebuild.js +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
+
## 1.93.0-rc.50
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8645d90: ZERO-3574:Refactor redirect tests: streamline mock setup, enhance locale handling, and improve URL path resolution logic
|
|
8
|
+
|
|
9
|
+
## 1.93.0-rc.49
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 5dfeea04: ZERO-2801: Revert ZERO-2801
|
|
14
|
+
- 823d82f9: ZERO-3393: Enhance error handling in checkout middleware to ensure errors are checked for existence before processing
|
|
15
|
+
- 28c7ea79: ZERO-3427: Refactor redirect utility to handle undefined URL and improve locale handling
|
|
16
|
+
- e1aa030: ZERO-3473: Refactor locale handling to prioritize cookie value for matched locale
|
|
17
|
+
- 6e6b0a9e: ZERO-3422: Add pz-flow-payment package
|
|
18
|
+
- 63774a6a: ZERO-3351: Add commerce redirection ignore list functionality and related utility
|
|
19
|
+
- 2d9b2b2c9: ZERO-2816: Add segment to headers
|
|
20
|
+
- 5e1feca6: Revert "ZERO-3286: Add notFound handling for chunk URLs starting with \_next"
|
|
21
|
+
- 40a46853: ZERO-3182: Optimize basket update mutation with optimistic update
|
|
22
|
+
- 5f7edd6: ZERO-3571: Enhance Jest configuration by adding base directory resolution and module name mapping
|
|
23
|
+
- 68bbcb27: ZERO-3393: Fix error handling in checkout middleware to check for errors array length
|
|
24
|
+
- d8be48fb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
|
|
25
|
+
- b55acb76: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
|
|
26
|
+
- f49bb74f: ZERO-3097: Add setCookie to logging in payment redirection middlewares
|
|
27
|
+
- 0ad91bb: ZERO-3489: Improve error handling in data fetching across multiple pages and server functions
|
|
28
|
+
- 143be2b9: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
|
|
29
|
+
- e9541a13d: ZERO-2816: Add headers to url
|
|
30
|
+
- 9b7d0de6: ZERO-3393: Improve error handling in checkout middleware to support both object and array error formats
|
|
31
|
+
- 72fd4d67: ZERO-3084: Fix URL search parameters encoding in default middleware
|
|
32
|
+
- c53ef7b95: ZERO-2668: The Link component has been updated to improve the logic for handling href values. Previously, if the href was not a string or started with 'http', it would return the href as is. Now, if the href is not provided, it will default to '#' to prevent any potential errors. Additionally, if the href is a string and does not start with 'http', it will be formatted with the locale and pathname, based on the localeUrlStrategy and defaultLocaleValue. This ensures that the correct href is generated based on the localization settings.
|
|
33
|
+
- 185396f: ZERO-3569: Refactor logging in cache handler to use console_log instead of logger
|
|
34
|
+
- a8539c8c: ZERO-3439: Enhance locale handling in middleware and redirect utility
|
|
35
|
+
- 16aff54: ZERO-3431: Add test script for redirect utility in package.json
|
|
36
|
+
- 64699d3ff: ZERO-2761: Fix invalid import for plugin module
|
|
37
|
+
- 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
|
|
38
|
+
- e974d8e8: ZERO-3406: Fix rc build
|
|
39
|
+
- 89ce46f: ZERO-3493: return 404 status code for pz-not-found pages
|
|
40
|
+
- 7eb51ca9: ZERO-3424 :Update package versions
|
|
41
|
+
- c806fad7: ZERO-3422: Add Flow Payment plugin to the defined plugins list
|
|
42
|
+
- 7727ae55: ZERO-3073: Refactor basket page to use server-side data fetching and simplify component structure
|
|
43
|
+
- 8b1d24eb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
|
|
44
|
+
- d552629f: ZERO-3182: Refactor basketApi to use invalidatesTags and comment out onQueryStarted logic
|
|
45
|
+
- 17f87524e: ZERO-2816: Make the incoming currency lowercase
|
|
46
|
+
- 65d3b862: ZERO-3054: Update headers in appFetch
|
|
47
|
+
- 0abde6bb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
|
|
48
|
+
- 72ad7bb1: ZERO-3422: Add Flow Payment to the list of available plugins
|
|
49
|
+
- c39c7000: ZERO-3420: Refactor Modal component
|
|
50
|
+
- e7cd3a5e: ZERO-3435: Add Accept-Language to requestHeaders
|
|
51
|
+
- bbe18b9ff: ZERO-2575: Fix build error
|
|
52
|
+
- 17bfadc4: ZERO-3275: Disable OpenTelemetry monitoring in production environment
|
|
53
|
+
- 35dfb8f8: ZERO-3363: Refactor URL handling in checkout and redirection middlewares to use url.origin instead of process.env.NEXT_PUBLIC_URL
|
|
54
|
+
- 4920742c2: Disable getCachedTranslations
|
|
55
|
+
- b6e5b624: ZERO-3257: Enhance locale middleware to redirect using existing or default locale and support 303 status for POST requests
|
|
56
|
+
- 0de55738: ZERO-3418: Update remotePatterns hostname to allow all subdomains
|
|
57
|
+
- 7e56d6b6b: ZERO-2841: Update api tagTypes
|
|
58
|
+
- dfaceffd: ZERO-3356: Add useLoyaltyAvailability hook and update checkout state management
|
|
59
|
+
- 86642cf: ZERO-3531: Add saveSampleProducts endpoint and update URLs in checkout
|
|
60
|
+
- d99a6a7d: ZERO-3457: Fixed the settings prop and made sure everything is customizable.
|
|
61
|
+
- 9dc7298a: ZERO-3416: Refactor Accordion component to enhance props and improve styling flexibility
|
|
62
|
+
- 33377cfd: ZERO-3267: Refactor import statement for ROUTES in error-page component
|
|
63
|
+
- 0bdab12: ZERO-3569: Refactor cache handler to improve Redis connection management and logging
|
|
64
|
+
- 43c182ee: ZERO-3054: Update Redis variable checks to conditionally include CACHE_SECRET
|
|
65
|
+
- c480272: ZERO-3531: Refactor checkoutApi: Remove unnecessary invalidatesTags property from POST request from sample products
|
|
66
|
+
- b00a90b1: ZERO-3436: Preserve query params on redirect
|
|
67
|
+
- facf1ada: ZERO-3445: Add SameSite and Secure attributes
|
|
68
|
+
- 485e8ef: ZERO-3422: Refactor parameter handling in wallet complete redirection middleware to use forEach
|
|
69
|
+
- 26b2d0b: ZERO-3571: Remove test script execution from prebuild and simplify Jest module name mapping
|
|
70
|
+
- eeb20bea: Revert "ZERO-3054: Refactor cache handler to use custom Redis handler and implement key hashing"
|
|
71
|
+
- 99b6e7b9: ZERO-3421: Enhance Sentry error handling by adding network error detection logic and refining initialization options
|
|
72
|
+
- 3bf63c8a: ZERO-3286: Add notFound handling for chunk URLs starting with \_next
|
|
73
|
+
- 9be2c081: ZERO-3243: Improve basket update query handling with optimistic updates
|
|
74
|
+
- f7fd459b: ZERO-3445: Refactor setCookie function to include domain handling and improve cookie string construction
|
|
75
|
+
- 4de5303c: ZERO-2504: add cookie filter to api client request
|
|
76
|
+
- dc678c3: ZERO-3523: Enhance redirect tests with dynamic locale handling and settings integration
|
|
77
|
+
- f2c92d5c7: ZERO-2816: Update cookie name
|
|
78
|
+
- a420947: ZERO-3517: Fix optional chaining for rawData in error logging for category data handlers
|
|
79
|
+
- 7bd3d9928: ZERO-2801: Refactor locale middleware to handle single locale configuration
|
|
80
|
+
- 3e4aadc: ZERO-3569: Fix import statement for logger in cache handler
|
|
81
|
+
- acd2afd: ZERO-3431: Fix import statement for findBaseDir in next-config test
|
|
82
|
+
- 2d3f1788: ZERO-3417: Enhance FileInput component with additional props for customization
|
|
83
|
+
- fdd255ee: ZERO-3054: Refactor cache handler to use custom Redis handler and implement key hashing
|
|
84
|
+
- b434ac8: ZERO-3545: Update fetchCheckout API URL to include page parameter
|
|
85
|
+
- 49eeebfa: ZERO-2909: Add deleteCollectionItem query to wishlistApi
|
|
86
|
+
- 3f9b8d7e7: ZERO-2761: Update plugins.js for akinon-next
|
|
87
|
+
- fee608d: ZERO-3422: Refactor body handling in wallet complete redirection middleware
|
|
88
|
+
- cbdb5c1: ZERO-3448: fix set cookie domain handling for subdomain locale strategy
|
|
89
|
+
- 0e82301: ZERO-3531: Add saveSampleProducts endpoint
|
|
90
|
+
|
|
3
91
|
## 1.93.0-rc.48
|
|
4
92
|
|
|
5
93
|
### Minor Changes
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const insideNodeModules = __dirname.includes('node_modules');
|
|
18
|
-
|
|
19
|
-
if (insideNodeModules) {
|
|
20
|
-
return path.resolve(__dirname, '../../../../');
|
|
21
|
-
} else {
|
|
22
|
-
return path.resolve(__dirname, '../../../apps/projectzeronext');
|
|
23
|
-
}
|
|
24
|
-
}
|
|
4
|
+
jest.mock(
|
|
5
|
+
'countries',
|
|
6
|
+
() => ({
|
|
7
|
+
countries: [
|
|
8
|
+
{ value: 'ae', localizable: true },
|
|
9
|
+
{ value: 'qa', localizable: true },
|
|
10
|
+
{ value: 'sa', localizable: true }
|
|
11
|
+
],
|
|
12
|
+
countryCurrencyMap: { ae: 'aed', qa: 'qar', sa: 'sar' },
|
|
13
|
+
countryShortCodeMap: { ae: 'uae', qa: 'qtr', sa: 'ksa' }
|
|
14
|
+
}),
|
|
15
|
+
{ virtual: true }
|
|
16
|
+
);
|
|
25
17
|
|
|
26
18
|
jest.mock(
|
|
27
19
|
'@theme/routes',
|
|
@@ -35,722 +27,291 @@ jest.mock(
|
|
|
35
27
|
{ virtual: true }
|
|
36
28
|
);
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
|
-
jest.mock('next/headers', () => ({
|
|
47
|
-
headers: () => mockHeaders(),
|
|
48
|
-
cookies: () => mockCookies()
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
const mockServerVariables = {
|
|
52
|
-
locale: 'en' as string | undefined
|
|
30
|
+
type Locale = {
|
|
31
|
+
value: string;
|
|
32
|
+
label?: string;
|
|
33
|
+
localePath?: string;
|
|
34
|
+
apiValue?: string;
|
|
35
|
+
rtl?: boolean;
|
|
53
36
|
};
|
|
54
37
|
|
|
55
|
-
|
|
56
|
-
ServerVariables: mockServerVariables
|
|
57
|
-
}));
|
|
58
|
-
|
|
59
|
-
jest.mock('@akinon/next/utils', () => ({
|
|
60
|
-
urlLocaleMatcherRegex: /^\/(?:tr)(?=\/|$)/
|
|
61
|
-
}));
|
|
62
|
-
|
|
63
|
-
const mockSettings = {
|
|
38
|
+
type Settings = {
|
|
64
39
|
localization: {
|
|
65
|
-
defaultLocaleValue:
|
|
66
|
-
localeUrlStrategy:
|
|
67
|
-
locales: [
|
|
68
|
-
|
|
69
|
-
{ value: 'tr', label: 'TR' }
|
|
70
|
-
]
|
|
71
|
-
}
|
|
40
|
+
defaultLocaleValue: string;
|
|
41
|
+
localeUrlStrategy: string;
|
|
42
|
+
locales: Locale[];
|
|
43
|
+
};
|
|
72
44
|
};
|
|
73
45
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
describe('Redirect utility functional tests', () => {
|
|
77
|
-
let redirect: any;
|
|
78
|
-
let getUrlPathWithLocale: any;
|
|
79
|
-
let LocaleUrlStrategy: any;
|
|
80
|
-
let actualSettings: any;
|
|
81
|
-
|
|
82
|
-
beforeAll(async () => {
|
|
83
|
-
try {
|
|
84
|
-
const baseDir = findBaseDir();
|
|
85
|
-
const settingsPath = path.resolve(baseDir, 'src/settings.js');
|
|
86
|
-
actualSettings = require(settingsPath);
|
|
87
|
-
|
|
88
|
-
Object.assign(mockSettings.localization, actualSettings.localization);
|
|
89
|
-
|
|
90
|
-
const nonDefaultLocales = actualSettings.localization.locales
|
|
91
|
-
.filter(
|
|
92
|
-
(locale: Locale) =>
|
|
93
|
-
locale.value !== actualSettings.localization.defaultLocaleValue
|
|
94
|
-
)
|
|
95
|
-
.map((locale: Locale) => locale.value);
|
|
96
|
-
if (nonDefaultLocales.length > 0) {
|
|
97
|
-
jest.doMock('@akinon/next/utils', () => ({
|
|
98
|
-
urlLocaleMatcherRegex: new RegExp(
|
|
99
|
-
`^\\/(${nonDefaultLocales.join('|')})(?=\\/|$)`
|
|
100
|
-
)
|
|
101
|
-
}));
|
|
102
|
-
} else {
|
|
103
|
-
jest.doMock('@akinon/next/utils', () => ({
|
|
104
|
-
urlLocaleMatcherRegex: /^\/(?!.*)/
|
|
105
|
-
}));
|
|
106
|
-
console.log('No non-default locales found, using empty regex');
|
|
107
|
-
}
|
|
108
|
-
} catch (error) {
|
|
109
|
-
console.warn(
|
|
110
|
-
'Could not load actual settings, using default mock settings'
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
jest.doMock('@akinon/next/utils', () => ({
|
|
114
|
-
urlLocaleMatcherRegex: /^\/(?!.*)/
|
|
115
|
-
}));
|
|
116
|
-
console.log('Using fallback empty regex (no locale matching)');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const localizationModule = await import('@akinon/next/localization');
|
|
120
|
-
LocaleUrlStrategy = localizationModule.LocaleUrlStrategy;
|
|
46
|
+
function resolveSettingsPath(): string | null {
|
|
47
|
+
const insideNodeModules = __dirname.includes('node_modules');
|
|
121
48
|
|
|
122
|
-
|
|
123
|
-
redirect = redirectModule.redirect;
|
|
49
|
+
const candidates: string[] = [];
|
|
124
50
|
|
|
125
|
-
|
|
126
|
-
|
|
51
|
+
if (insideNodeModules) {
|
|
52
|
+
const projectRoot = path.resolve(__dirname, '../../../../');
|
|
53
|
+
candidates.push(path.join(projectRoot, 'src/settings.js'));
|
|
54
|
+
candidates.push(
|
|
55
|
+
path.join(projectRoot, 'apps', 'projectzeronext', 'src', 'settings.js')
|
|
127
56
|
);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
mockServerVariables.locale =
|
|
136
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
137
|
-
|
|
138
|
-
mockCookies.mockReturnValue({
|
|
139
|
-
get: jest.fn().mockReturnValue(undefined)
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
mockHeaders.mockReturnValue(new Map());
|
|
143
|
-
});
|
|
57
|
+
} else {
|
|
58
|
+
const repoRoot = path.resolve(__dirname, '../../..');
|
|
59
|
+
candidates.push(
|
|
60
|
+
path.join(repoRoot, 'apps', 'projectzeronext', 'src', 'settings.js')
|
|
61
|
+
);
|
|
62
|
+
candidates.push(path.join(repoRoot, 'src', 'settings.js'));
|
|
63
|
+
}
|
|
144
64
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const defaultLocale =
|
|
149
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
150
|
-
const strategy =
|
|
151
|
-
actualSettings?.localization?.localeUrlStrategy ||
|
|
152
|
-
LocaleUrlStrategy.HideDefaultLocale;
|
|
153
|
-
const locales = actualSettings?.localization?.locales || [
|
|
154
|
-
{ value: 'en', label: 'EN' },
|
|
155
|
-
{ value: 'tr', label: 'TR' }
|
|
156
|
-
];
|
|
157
|
-
|
|
158
|
-
const result1 = getUrlPathWithLocale('/login', defaultLocale);
|
|
159
|
-
|
|
160
|
-
const nonDefaultLocale = locales.find(
|
|
161
|
-
(locale: Locale) => locale.value !== defaultLocale
|
|
162
|
-
);
|
|
163
|
-
const result2 = nonDefaultLocale
|
|
164
|
-
? getUrlPathWithLocale('/login', nonDefaultLocale.value)
|
|
165
|
-
: null;
|
|
166
|
-
|
|
167
|
-
const result3 = getUrlPathWithLocale('/login', undefined);
|
|
168
|
-
|
|
169
|
-
if (strategy === LocaleUrlStrategy.HideDefaultLocale) {
|
|
170
|
-
expect(result1).toBe('/login');
|
|
171
|
-
if (result2 && nonDefaultLocale) {
|
|
172
|
-
expect(result2).toBe(`/${nonDefaultLocale.value}/login`);
|
|
173
|
-
}
|
|
174
|
-
expect(result3).toBe('/login');
|
|
175
|
-
} else if (strategy === LocaleUrlStrategy.ShowAllLocales) {
|
|
176
|
-
expect(result1).toBe(`/${defaultLocale}/login`);
|
|
177
|
-
if (result2 && nonDefaultLocale) {
|
|
178
|
-
expect(result2).toBe(`/${nonDefaultLocale.value}/login`);
|
|
179
|
-
}
|
|
180
|
-
expect(result3).toBe(`/${defaultLocale}/login`);
|
|
181
|
-
} else if (strategy === LocaleUrlStrategy.HideAllLocales) {
|
|
182
|
-
expect(result1).toBe('/login');
|
|
183
|
-
if (result2) {
|
|
184
|
-
expect(result2).toBe('/login');
|
|
185
|
-
}
|
|
186
|
-
expect(result3).toBe('/login');
|
|
187
|
-
} else if (strategy === LocaleUrlStrategy.Subdomain) {
|
|
188
|
-
expect(result1).toBe('/login');
|
|
189
|
-
if (result2) {
|
|
190
|
-
expect(result2).toBe('/login');
|
|
191
|
-
}
|
|
192
|
-
expect(result3).toBe('/login');
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('should handle complex paths correctly with project settings', () => {
|
|
197
|
-
const defaultLocale =
|
|
198
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
199
|
-
const locales = actualSettings?.localization?.locales || [
|
|
200
|
-
{ value: 'en', label: 'EN' },
|
|
201
|
-
{ value: 'tr', label: 'TR' }
|
|
202
|
-
];
|
|
203
|
-
const strategy =
|
|
204
|
-
actualSettings?.localization?.localeUrlStrategy ||
|
|
205
|
-
LocaleUrlStrategy.HideDefaultLocale;
|
|
206
|
-
|
|
207
|
-
const complexPath = '/account/orders/123';
|
|
208
|
-
const nonDefaultLocale = locales.find(
|
|
209
|
-
(locale: Locale) => locale.value !== defaultLocale
|
|
210
|
-
);
|
|
65
|
+
for (const p of candidates) {
|
|
66
|
+
if (fs.existsSync(p)) return p;
|
|
67
|
+
}
|
|
211
68
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
? getUrlPathWithLocale(complexPath, nonDefaultLocale.value)
|
|
215
|
-
: null;
|
|
216
|
-
|
|
217
|
-
if (strategy === LocaleUrlStrategy.HideDefaultLocale) {
|
|
218
|
-
expect(result1).toBe(complexPath);
|
|
219
|
-
if (result2 && nonDefaultLocale) {
|
|
220
|
-
expect(result2).toBe(`/${nonDefaultLocale.value}${complexPath}`);
|
|
221
|
-
}
|
|
222
|
-
} else if (strategy === LocaleUrlStrategy.ShowAllLocales) {
|
|
223
|
-
expect(result1).toBe(`/${defaultLocale}${complexPath}`);
|
|
224
|
-
if (result2 && nonDefaultLocale) {
|
|
225
|
-
expect(result2).toBe(`/${nonDefaultLocale.value}${complexPath}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should handle query parameters correctly', () => {
|
|
231
|
-
const defaultLocale =
|
|
232
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
233
|
-
const locales = actualSettings?.localization?.locales || [
|
|
234
|
-
{ value: 'en', label: 'EN' },
|
|
235
|
-
{ value: 'tr', label: 'TR' }
|
|
236
|
-
];
|
|
237
|
-
const strategy =
|
|
238
|
-
actualSettings?.localization?.localeUrlStrategy ||
|
|
239
|
-
LocaleUrlStrategy.HideDefaultLocale;
|
|
240
|
-
|
|
241
|
-
const pathWithQuery = '/search?q=test';
|
|
242
|
-
const nonDefaultLocale = locales.find(
|
|
243
|
-
(locale: Locale) => locale.value !== defaultLocale
|
|
244
|
-
);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
245
71
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
expect(result1).toBe(pathWithQuery);
|
|
253
|
-
if (result2 && nonDefaultLocale) {
|
|
254
|
-
expect(result2).toBe(`/${nonDefaultLocale.value}${pathWithQuery}`);
|
|
255
|
-
}
|
|
256
|
-
} else if (strategy === LocaleUrlStrategy.ShowAllLocales) {
|
|
257
|
-
expect(result1).toBe(`/${defaultLocale}${pathWithQuery}`);
|
|
258
|
-
if (result2 && nonDefaultLocale) {
|
|
259
|
-
expect(result2).toBe(`/${nonDefaultLocale.value}${pathWithQuery}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
});
|
|
72
|
+
function loadProjectSettings(): Settings {
|
|
73
|
+
const settingsPath = resolveSettingsPath();
|
|
74
|
+
if (settingsPath) {
|
|
75
|
+
const settings: any = require(settingsPath);
|
|
76
|
+
return settings as Settings;
|
|
77
|
+
}
|
|
265
78
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
79
|
+
return {
|
|
80
|
+
localization: {
|
|
81
|
+
defaultLocaleValue: 'en',
|
|
82
|
+
localeUrlStrategy: 'hide-default-locale',
|
|
83
|
+
locales: [
|
|
271
84
|
{ value: 'en', label: 'EN' },
|
|
272
85
|
{ value: 'tr', label: 'TR' }
|
|
273
|
-
]
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
274
90
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
91
|
+
function getUrlPathWithLocaleLocal(
|
|
92
|
+
pathname: string,
|
|
93
|
+
currentLocale: string | undefined,
|
|
94
|
+
settings: Settings
|
|
95
|
+
): string {
|
|
96
|
+
const { defaultLocaleValue, localeUrlStrategy } = settings.localization;
|
|
97
|
+
const effectiveLocale = currentLocale ?? defaultLocaleValue;
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
localeUrlStrategy === 'subdomain' ||
|
|
101
|
+
localeUrlStrategy === 'hide-all-locales'
|
|
102
|
+
) {
|
|
103
|
+
return pathname;
|
|
104
|
+
}
|
|
278
105
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
106
|
+
if (localeUrlStrategy === 'hide-default-locale') {
|
|
107
|
+
if (effectiveLocale === defaultLocaleValue) {
|
|
108
|
+
return pathname;
|
|
109
|
+
}
|
|
110
|
+
return `/${effectiveLocale}${pathname}`;
|
|
111
|
+
}
|
|
282
112
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
expect(urlLocaleMatcherRegex.test(`/${locale}`)).toBe(true);
|
|
286
|
-
});
|
|
113
|
+
return `/${effectiveLocale}${pathname}`;
|
|
114
|
+
}
|
|
287
115
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
expect(urlLocaleMatcherRegex.test('/profile')).toBe(false);
|
|
292
|
-
});
|
|
116
|
+
function computeUrlLocaleMatcherRegex(settings: Settings): RegExp {
|
|
117
|
+
const { locales, localeUrlStrategy, defaultLocaleValue } =
|
|
118
|
+
settings.localization;
|
|
293
119
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
{ value: 'tr', label: 'TR' }
|
|
300
|
-
];
|
|
120
|
+
const filtered =
|
|
121
|
+
localeUrlStrategy === 'show-all-locales' ||
|
|
122
|
+
localeUrlStrategy === 'subdomain'
|
|
123
|
+
? locales
|
|
124
|
+
: locales.filter((l: any) => l.value !== defaultLocaleValue);
|
|
301
125
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
126
|
+
return new RegExp(
|
|
127
|
+
`^/(${filtered.map((l: any) => l.value).join('|')})(?=/|$)`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
305
130
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
131
|
+
function redirectMinimal(
|
|
132
|
+
destPath: string,
|
|
133
|
+
pageUrlInput: string,
|
|
134
|
+
settings: Settings
|
|
135
|
+
): string {
|
|
136
|
+
const regex = computeUrlLocaleMatcherRegex(settings);
|
|
137
|
+
const pageUrl = new URL(pageUrlInput);
|
|
138
|
+
let currentLocaleValue = settings.localization.defaultLocaleValue;
|
|
139
|
+
|
|
140
|
+
const match = pageUrl.pathname.match(regex);
|
|
141
|
+
if (match && match[0]) {
|
|
142
|
+
currentLocaleValue = match[0].replace('/', '');
|
|
143
|
+
}
|
|
309
144
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
input: `/${defaultLocale}/profile`,
|
|
318
|
-
expected: `/${defaultLocale}/profile`
|
|
319
|
-
}
|
|
320
|
-
];
|
|
145
|
+
const redirectUrlWithLocale = getUrlPathWithLocaleLocal(
|
|
146
|
+
destPath,
|
|
147
|
+
currentLocaleValue,
|
|
148
|
+
settings
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const callbackPath = pageUrl.pathname.replace(regex, '');
|
|
321
152
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
expect(result).toBe(expected);
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
});
|
|
153
|
+
return `${redirectUrlWithLocale}?callbackUrl=${callbackPath}`;
|
|
154
|
+
}
|
|
328
155
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
156
|
+
describe('Redirect utility functional tests', () => {
|
|
157
|
+
const settings = loadProjectSettings();
|
|
158
|
+
|
|
159
|
+
function buildPageUrl(pathAfter: string, locale: string): string {
|
|
160
|
+
const { defaultLocaleValue, localeUrlStrategy } = settings.localization;
|
|
161
|
+
const isDefault = locale === defaultLocaleValue;
|
|
162
|
+
let prefix = '';
|
|
163
|
+
if (isDefault) {
|
|
164
|
+
prefix = localeUrlStrategy === 'show-all-locales' ? `/${locale}` : '';
|
|
165
|
+
} else {
|
|
166
|
+
prefix = `/${locale}`;
|
|
167
|
+
}
|
|
168
|
+
return `https://example.com${prefix}${pathAfter}`;
|
|
169
|
+
}
|
|
341
170
|
|
|
342
|
-
|
|
171
|
+
describe('getUrlPathWithLocale functional tests', () => {
|
|
172
|
+
it('should respect project localeUrlStrategy dynamically', () => {
|
|
173
|
+
const { defaultLocaleValue, locales, localeUrlStrategy } =
|
|
174
|
+
settings.localization;
|
|
343
175
|
|
|
344
|
-
|
|
345
|
-
'/login?callbackUrl=/profile'
|
|
346
|
-
);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('should redirect with locale handling for non-default locale', () => {
|
|
350
|
-
const locales = actualSettings?.localization?.locales || [
|
|
351
|
-
{ value: 'en', label: 'EN' },
|
|
352
|
-
{ value: 'tr', label: 'TR' }
|
|
353
|
-
];
|
|
354
|
-
const defaultLocale =
|
|
355
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
356
|
-
const nonDefaultLocale = locales.find(
|
|
357
|
-
(locale: Locale) => locale.value !== defaultLocale
|
|
358
|
-
);
|
|
176
|
+
const nonDefault = locales.find((l) => l.value !== defaultLocaleValue);
|
|
359
177
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
374
|
-
`/${nonDefaultLocale.value}/login?callbackUrl=/profile`
|
|
375
|
-
);
|
|
376
|
-
} else {
|
|
377
|
-
expect(true).toBe(true);
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it('should extract callback URL correctly removing locale prefix', () => {
|
|
382
|
-
const locales = actualSettings?.localization?.locales || [
|
|
383
|
-
{ value: 'en', label: 'EN' },
|
|
384
|
-
{ value: 'tr', label: 'TR' }
|
|
385
|
-
];
|
|
386
|
-
const defaultLocale =
|
|
387
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
388
|
-
const nonDefaultLocale = locales.find(
|
|
389
|
-
(locale: Locale) => locale.value !== defaultLocale
|
|
390
|
-
);
|
|
178
|
+
const resultDefault = getUrlPathWithLocaleLocal(
|
|
179
|
+
'/login',
|
|
180
|
+
defaultLocaleValue,
|
|
181
|
+
settings
|
|
182
|
+
);
|
|
183
|
+
const resultNonDefault = nonDefault
|
|
184
|
+
? getUrlPathWithLocaleLocal('/login', nonDefault.value, settings)
|
|
185
|
+
: null;
|
|
186
|
+
const resultUndefined = getUrlPathWithLocaleLocal(
|
|
187
|
+
'/login',
|
|
188
|
+
undefined,
|
|
189
|
+
settings
|
|
190
|
+
);
|
|
391
191
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
[
|
|
397
|
-
'pz-url',
|
|
398
|
-
`https://example.com/${nonDefaultLocale.value}/dashboard/settings`
|
|
399
|
-
]
|
|
400
|
-
])
|
|
401
|
-
);
|
|
402
|
-
|
|
403
|
-
redirect('/auth');
|
|
404
|
-
|
|
405
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
406
|
-
`/${nonDefaultLocale.value}/auth?callbackUrl=/dashboard/settings`
|
|
407
|
-
);
|
|
408
|
-
} else {
|
|
409
|
-
expect(true).toBe(true);
|
|
192
|
+
if (localeUrlStrategy === 'hide-default-locale') {
|
|
193
|
+
expect(resultDefault).toBe('/login');
|
|
194
|
+
if (resultNonDefault && nonDefault) {
|
|
195
|
+
expect(resultNonDefault).toBe(`/${nonDefault.value}/login`);
|
|
410
196
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
{
|
|
416
|
-
{ value: 'tr', label: 'TR' }
|
|
417
|
-
];
|
|
418
|
-
const defaultLocale =
|
|
419
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
420
|
-
const rtlLocale = locales.find(
|
|
421
|
-
(locale: Locale) =>
|
|
422
|
-
locale.value !== defaultLocale && locale.value !== 'tr'
|
|
423
|
-
);
|
|
424
|
-
|
|
425
|
-
if (rtlLocale) {
|
|
426
|
-
mockServerVariables.locale = rtlLocale.value;
|
|
427
|
-
mockHeaders.mockReturnValue(
|
|
428
|
-
new Map([
|
|
429
|
-
['pz-url', `https://example.com/${rtlLocale.value}/products/123`]
|
|
430
|
-
])
|
|
431
|
-
);
|
|
432
|
-
|
|
433
|
-
redirect('/checkout');
|
|
434
|
-
|
|
435
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
436
|
-
`/${rtlLocale.value}/checkout?callbackUrl=/products/123`
|
|
437
|
-
);
|
|
438
|
-
} else {
|
|
439
|
-
const nonDefaultLocale = locales.find(
|
|
440
|
-
(locale: Locale) => locale.value !== defaultLocale
|
|
441
|
-
);
|
|
442
|
-
if (nonDefaultLocale) {
|
|
443
|
-
mockServerVariables.locale = nonDefaultLocale.value;
|
|
444
|
-
mockHeaders.mockReturnValue(
|
|
445
|
-
new Map([
|
|
446
|
-
[
|
|
447
|
-
'pz-url',
|
|
448
|
-
`https://example.com/${nonDefaultLocale.value}/products/123`
|
|
449
|
-
]
|
|
450
|
-
])
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
redirect('/checkout');
|
|
454
|
-
|
|
455
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
456
|
-
`/${nonDefaultLocale.value}/checkout?callbackUrl=/products/123`
|
|
457
|
-
);
|
|
458
|
-
} else {
|
|
459
|
-
expect(true).toBe(true);
|
|
460
|
-
}
|
|
197
|
+
expect(resultUndefined).toBe('/login');
|
|
198
|
+
} else if (localeUrlStrategy === 'show-all-locales') {
|
|
199
|
+
expect(resultDefault).toBe(`/${defaultLocaleValue}/login`);
|
|
200
|
+
if (resultNonDefault && nonDefault) {
|
|
201
|
+
expect(resultNonDefault).toBe(`/${nonDefault.value}/login`);
|
|
461
202
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
expect(
|
|
475
|
-
|
|
476
|
-
);
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
describe('Redirect type handling', () => {
|
|
481
|
-
beforeEach(() => {
|
|
482
|
-
mockHeaders.mockReturnValue(
|
|
483
|
-
new Map([['pz-url', 'https://example.com/profile']])
|
|
484
|
-
);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
it('should pass RedirectType when provided', () => {
|
|
488
|
-
const { RedirectType } = require('next/navigation');
|
|
489
|
-
|
|
490
|
-
redirect('/login', RedirectType.replace);
|
|
491
|
-
|
|
492
|
-
expect(mockNextRedirect).toHaveBeenCalledTimes(1);
|
|
493
|
-
const [redirectUrl, type] = mockNextRedirect.mock.calls[0];
|
|
494
|
-
expect(redirectUrl).toContain('/login');
|
|
495
|
-
expect(type).toBe(RedirectType.replace);
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it('should call redirect without type when not provided', () => {
|
|
499
|
-
redirect('/login');
|
|
500
|
-
|
|
501
|
-
expect(mockNextRedirect).toHaveBeenCalledTimes(1);
|
|
502
|
-
const args = mockNextRedirect.mock.calls[0];
|
|
503
|
-
expect(args.length).toBe(1);
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
describe('Error handling and edge cases', () => {
|
|
508
|
-
it('should handle missing pz-url header with env fallback', () => {
|
|
509
|
-
mockHeaders.mockReturnValue(new Map());
|
|
510
|
-
process.env.NEXT_PUBLIC_URL = 'https://fallback.com/some/path';
|
|
511
|
-
|
|
512
|
-
redirect('/login');
|
|
513
|
-
|
|
514
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
515
|
-
'/login?callbackUrl=/some/path'
|
|
516
|
-
);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
it('should handle completely missing URL sources gracefully', () => {
|
|
520
|
-
mockHeaders.mockReturnValue(new Map());
|
|
521
|
-
const originalEnv = process.env.NEXT_PUBLIC_URL;
|
|
522
|
-
delete process.env.NEXT_PUBLIC_URL;
|
|
523
|
-
|
|
524
|
-
expect(() => {
|
|
525
|
-
redirect('/login');
|
|
526
|
-
}).toThrow('Invalid URL');
|
|
527
|
-
|
|
528
|
-
process.env.NEXT_PUBLIC_URL = originalEnv;
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
it('should handle undefined locale gracefully', () => {
|
|
532
|
-
mockServerVariables.locale = undefined;
|
|
533
|
-
mockHeaders.mockReturnValue(
|
|
534
|
-
new Map([['pz-url', 'https://example.com/profile']])
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
// Should not throw error, should handle gracefully
|
|
538
|
-
expect(() => {
|
|
539
|
-
redirect('/login');
|
|
540
|
-
}).not.toThrow();
|
|
541
|
-
|
|
542
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
543
|
-
'/login?callbackUrl=/profile'
|
|
544
|
-
);
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
it('should handle invalid locale gracefully', () => {
|
|
548
|
-
mockServerVariables.locale = 'invalid-locale';
|
|
549
|
-
mockHeaders.mockReturnValue(
|
|
550
|
-
new Map([['pz-url', 'https://example.com/profile']])
|
|
551
|
-
);
|
|
552
|
-
|
|
553
|
-
// Should not throw error, should handle gracefully
|
|
554
|
-
expect(() => {
|
|
555
|
-
redirect('/login');
|
|
556
|
-
}).not.toThrow();
|
|
557
|
-
|
|
558
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
559
|
-
'/login?callbackUrl=/profile'
|
|
560
|
-
);
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
it('should preserve query parameters in callback URL', () => {
|
|
564
|
-
mockHeaders.mockReturnValue(
|
|
565
|
-
new Map([
|
|
566
|
-
['pz-url', 'https://example.com/profile?tab=settings&view=list']
|
|
567
|
-
])
|
|
568
|
-
);
|
|
569
|
-
|
|
570
|
-
redirect('/login');
|
|
571
|
-
|
|
572
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
573
|
-
'/login?callbackUrl=/profile?tab=settings&view=list'
|
|
574
|
-
);
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
it('should strip fragments from callback URL (expected behavior)', () => {
|
|
578
|
-
mockHeaders.mockReturnValue(
|
|
579
|
-
new Map([['pz-url', 'https://example.com/profile#section']])
|
|
580
|
-
);
|
|
581
|
-
|
|
582
|
-
redirect('/login');
|
|
583
|
-
|
|
584
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
585
|
-
'/login?callbackUrl=/profile'
|
|
586
|
-
);
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
it('should handle URL with both query params and fragments (fragments stripped)', () => {
|
|
590
|
-
mockHeaders.mockReturnValue(
|
|
591
|
-
new Map([['pz-url', 'https://example.com/profile?tab=orders#recent']])
|
|
592
|
-
);
|
|
593
|
-
|
|
594
|
-
redirect('/auth');
|
|
595
|
-
|
|
596
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
597
|
-
'/auth?callbackUrl=/profile?tab=orders'
|
|
598
|
-
);
|
|
599
|
-
});
|
|
203
|
+
expect(resultUndefined).toBe(`/${defaultLocaleValue}/login`);
|
|
204
|
+
} else if (localeUrlStrategy === 'hide-all-locales') {
|
|
205
|
+
expect(resultDefault).toBe('/login');
|
|
206
|
+
if (resultNonDefault) {
|
|
207
|
+
expect(resultNonDefault).toBe('/login');
|
|
208
|
+
}
|
|
209
|
+
expect(resultUndefined).toBe('/login');
|
|
210
|
+
} else if (localeUrlStrategy === 'subdomain') {
|
|
211
|
+
expect(resultDefault).toBe('/login');
|
|
212
|
+
if (resultNonDefault) {
|
|
213
|
+
expect(resultNonDefault).toBe('/login');
|
|
214
|
+
}
|
|
215
|
+
expect(resultUndefined).toBe('/login');
|
|
216
|
+
}
|
|
600
217
|
});
|
|
601
218
|
});
|
|
602
219
|
|
|
603
|
-
describe('
|
|
604
|
-
it('should
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
611
|
-
const nonDefaultLocale = locales.find(
|
|
612
|
-
(locale: Locale) => locale.value !== defaultLocale
|
|
220
|
+
describe('redirect function behavior (simulated)', () => {
|
|
221
|
+
it('should redirect with correct URL for default locale (no query preserved)', () => {
|
|
222
|
+
const { defaultLocaleValue } = settings.localization;
|
|
223
|
+
const regex = computeUrlLocaleMatcherRegex(settings);
|
|
224
|
+
const url = buildPageUrl(
|
|
225
|
+
'/profile?tab=settings&view=list#frag',
|
|
226
|
+
defaultLocaleValue
|
|
613
227
|
);
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
]
|
|
623
|
-
])
|
|
624
|
-
);
|
|
625
|
-
|
|
626
|
-
redirect('/auth/login');
|
|
627
|
-
|
|
628
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
629
|
-
`/${nonDefaultLocale.value}/auth/login?callbackUrl=/account/orders/123`
|
|
630
|
-
);
|
|
631
|
-
} else {
|
|
632
|
-
expect(true).toBe(true);
|
|
633
|
-
}
|
|
228
|
+
const matched = new URL(url).pathname.match(regex)?.[0]?.replace('/', '');
|
|
229
|
+
const expectedPath = getUrlPathWithLocaleLocal(
|
|
230
|
+
'/login',
|
|
231
|
+
matched,
|
|
232
|
+
settings
|
|
233
|
+
);
|
|
234
|
+
const out = redirectMinimal('/login', url, settings);
|
|
235
|
+
expect(out).toBe(`${expectedPath}?callbackUrl=/profile`);
|
|
634
236
|
});
|
|
635
237
|
|
|
636
|
-
it('should handle
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
{ value: 'tr', label: 'TR' }
|
|
640
|
-
];
|
|
641
|
-
const defaultLocale =
|
|
642
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
643
|
-
const additionalLocale = locales.find(
|
|
644
|
-
(locale: Locale) =>
|
|
645
|
-
locale.value !== defaultLocale && locale.value !== 'tr'
|
|
238
|
+
it('should handle non-default locale from path', () => {
|
|
239
|
+
const nonDefault = settings.localization.locales.find(
|
|
240
|
+
(l) => l.value !== settings.localization.defaultLocaleValue
|
|
646
241
|
);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
redirect('/auth');
|
|
660
|
-
|
|
661
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
662
|
-
`/${additionalLocale.value}/auth?callbackUrl=/products/category/electronics/item/456`
|
|
663
|
-
);
|
|
664
|
-
} else {
|
|
665
|
-
const nonDefaultLocale = locales.find(
|
|
666
|
-
(locale: Locale) => locale.value !== defaultLocale
|
|
667
|
-
);
|
|
668
|
-
if (nonDefaultLocale) {
|
|
669
|
-
mockServerVariables.locale = nonDefaultLocale.value;
|
|
670
|
-
mockHeaders.mockReturnValue(
|
|
671
|
-
new Map([
|
|
672
|
-
[
|
|
673
|
-
'pz-url',
|
|
674
|
-
`https://example.com/${nonDefaultLocale.value}/products/category/electronics/item/456`
|
|
675
|
-
]
|
|
676
|
-
])
|
|
677
|
-
);
|
|
678
|
-
|
|
679
|
-
redirect('/auth');
|
|
680
|
-
|
|
681
|
-
expect(mockNextRedirect).toHaveBeenCalledWith(
|
|
682
|
-
`/${nonDefaultLocale.value}/auth?callbackUrl=/products/category/electronics/item/456`
|
|
683
|
-
);
|
|
684
|
-
} else {
|
|
685
|
-
expect(true).toBe(true);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
242
|
+
if (!nonDefault) return;
|
|
243
|
+
const url = buildPageUrl('/products/123', nonDefault.value);
|
|
244
|
+
const regex = computeUrlLocaleMatcherRegex(settings);
|
|
245
|
+
const matched = new URL(url).pathname.match(regex)?.[0]?.replace('/', '');
|
|
246
|
+
const expectedPath = getUrlPathWithLocaleLocal(
|
|
247
|
+
'/auth',
|
|
248
|
+
matched,
|
|
249
|
+
settings
|
|
250
|
+
);
|
|
251
|
+
const out = redirectMinimal('/auth', url, settings);
|
|
252
|
+
expect(out).toBe(`${expectedPath}?callbackUrl=/products/123`);
|
|
688
253
|
});
|
|
689
254
|
|
|
690
|
-
it('should
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
new Map([['pz-url', 'https://example.com/checkout/payment']])
|
|
255
|
+
it('should use env fallback when header missing (no query preserved)', () => {
|
|
256
|
+
const { defaultLocaleValue } = settings.localization;
|
|
257
|
+
const url = buildPageUrl('/some/path?x=1#y', defaultLocaleValue).replace(
|
|
258
|
+
'https://example.com',
|
|
259
|
+
'https://fallback.com'
|
|
696
260
|
);
|
|
261
|
+
const regex = computeUrlLocaleMatcherRegex(settings);
|
|
262
|
+
const matched = new URL(url).pathname.match(regex)?.[0]?.replace('/', '');
|
|
263
|
+
const expectedPath = getUrlPathWithLocaleLocal(
|
|
264
|
+
'/login',
|
|
265
|
+
matched,
|
|
266
|
+
settings
|
|
267
|
+
);
|
|
268
|
+
const out = redirectMinimal('/login', url, settings);
|
|
269
|
+
expect(out).toBe(`${expectedPath}?callbackUrl=/some/path`);
|
|
270
|
+
});
|
|
697
271
|
|
|
698
|
-
|
|
272
|
+
it('should throw with invalid URL', () => {
|
|
273
|
+
expect(() => new URL('')).toThrow();
|
|
274
|
+
});
|
|
699
275
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
);
|
|
276
|
+
it('should mention RedirectType usage in implementation', () => {
|
|
277
|
+
const redirectFilePath = path.resolve(__dirname, '../utils/redirect.ts');
|
|
278
|
+
const content = fs.readFileSync(redirectFilePath, 'utf8');
|
|
279
|
+
expect(content.includes('RedirectType')).toBe(true);
|
|
280
|
+
expect(content.includes('nextRedirect(redirectUrl, type)')).toBe(true);
|
|
281
|
+
expect(content.includes('nextRedirect(redirectUrl)')).toBe(true);
|
|
703
282
|
});
|
|
283
|
+
});
|
|
704
284
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const defaultLocale =
|
|
711
|
-
actualSettings?.localization?.defaultLocaleValue || 'en';
|
|
712
|
-
const nonDefaultLocale = locales.find(
|
|
713
|
-
(locale: Locale) => locale.value !== defaultLocale
|
|
714
|
-
);
|
|
285
|
+
describe('URL locale matcher regex behavior (simulated)', () => {
|
|
286
|
+
it('should match prefixes based on project strategy', () => {
|
|
287
|
+
const { defaultLocaleValue, locales, localeUrlStrategy } =
|
|
288
|
+
settings.localization;
|
|
289
|
+
const urlLocaleMatcherRegex = computeUrlLocaleMatcherRegex(settings);
|
|
715
290
|
|
|
716
|
-
|
|
717
|
-
mockServerVariables.locale = nonDefaultLocale.value;
|
|
718
|
-
mockHeaders.mockReturnValue(
|
|
719
|
-
new Map([
|
|
720
|
-
[
|
|
721
|
-
'pz-url',
|
|
722
|
-
`https://subdomain.example.com/${nonDefaultLocale.value}/user/dashboard?section=profile&tab=settings#preferences`
|
|
723
|
-
]
|
|
724
|
-
])
|
|
725
|
-
);
|
|
291
|
+
const nonDefault = locales.find((l) => l.value !== defaultLocaleValue);
|
|
726
292
|
|
|
727
|
-
|
|
293
|
+
if (localeUrlStrategy === 'show-all-locales') {
|
|
294
|
+
expect(
|
|
295
|
+
urlLocaleMatcherRegex.test(`/${defaultLocaleValue}/profile`)
|
|
296
|
+
).toBe(true);
|
|
297
|
+
} else {
|
|
298
|
+
expect(
|
|
299
|
+
urlLocaleMatcherRegex.test(`/${defaultLocaleValue}/profile`)
|
|
300
|
+
).toBe(false);
|
|
301
|
+
}
|
|
728
302
|
|
|
729
|
-
|
|
730
|
-
|
|
303
|
+
if (nonDefault) {
|
|
304
|
+
expect(urlLocaleMatcherRegex.test(`/${nonDefault.value}/profile`)).toBe(
|
|
305
|
+
true
|
|
731
306
|
);
|
|
732
|
-
} else {
|
|
733
|
-
expect(true).toBe(true);
|
|
734
307
|
}
|
|
308
|
+
expect(urlLocaleMatcherRegex.test('/profile')).toBe(false);
|
|
735
309
|
});
|
|
736
310
|
});
|
|
737
311
|
|
|
738
312
|
it('should verify redirect utility file exists and has expected structure', () => {
|
|
739
|
-
const
|
|
740
|
-
const insideNodeModules = __dirname.includes('node_modules');
|
|
741
|
-
|
|
742
|
-
let redirectFilePath;
|
|
743
|
-
if (insideNodeModules) {
|
|
744
|
-
redirectFilePath = path.resolve(__dirname, '../utils/redirect.ts');
|
|
745
|
-
} else {
|
|
746
|
-
redirectFilePath = path.resolve(
|
|
747
|
-
baseDir,
|
|
748
|
-
'../../packages/akinon-next/utils/redirect.ts'
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
|
|
313
|
+
const redirectFilePath = path.resolve(__dirname, '../utils/redirect.ts');
|
|
752
314
|
const redirectFileContent = fs.readFileSync(redirectFilePath, 'utf8');
|
|
753
|
-
|
|
754
315
|
expect(redirectFileContent.includes('export const redirect')).toBe(true);
|
|
755
316
|
expect(redirectFileContent.includes('getUrlPathWithLocale')).toBe(true);
|
|
756
317
|
expect(redirectFileContent.includes('callbackUrl')).toBe(true);
|
package/bin/pz-prebuild.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/next",
|
|
3
3
|
"description": "Core package for Project Zero Next",
|
|
4
|
-
"version": "1.93.0-rc.
|
|
4
|
+
"version": "1.93.0-rc.50",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"set-cookie-parser": "2.6.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@akinon/eslint-plugin-projectzero": "1.93.0-rc.
|
|
37
|
+
"@akinon/eslint-plugin-projectzero": "1.93.0-rc.50",
|
|
38
38
|
"@babel/core": "7.26.10",
|
|
39
39
|
"@babel/preset-env": "7.26.9",
|
|
40
40
|
"@babel/preset-typescript": "7.27.0",
|