@tagadapay/plugin-sdk 3.1.11 → 3.1.22
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/README.md +1129 -1129
- package/build-cdn.js +231 -228
- package/dist/data/iso3166.d.ts +23 -33
- package/dist/data/iso3166.js +134 -198
- package/dist/data/languages.d.ts +5 -64
- package/dist/data/languages.js +23 -143
- package/dist/external-tracker.js +968 -102
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/hooks/useISOData.js +1 -1
- package/dist/react/hooks/usePaymentPolling.d.ts +3 -3
- package/dist/react/hooks/useShippingRates.d.ts +6 -0
- package/dist/react/hooks/useShippingRates.js +38 -0
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/react/services/apiService.d.ts +21 -0
- package/dist/react/services/apiService.js +10 -0
- package/dist/tagada-sdk.js +2079 -179
- package/dist/tagada-sdk.min.js +4 -2
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/core/client.d.ts +4 -2
- package/dist/v2/core/client.js +4 -3
- package/dist/v2/core/errors.d.ts +75 -0
- package/dist/v2/core/errors.js +104 -0
- package/dist/v2/core/funnelClient.d.ts +16 -15
- package/dist/v2/core/funnelClient.js +1 -1
- package/dist/v2/core/index.d.ts +1 -0
- package/dist/v2/core/index.js +2 -0
- package/dist/v2/core/pixelMapping.d.ts +49 -0
- package/dist/v2/core/pixelMapping.js +325 -0
- package/dist/v2/core/resources/apiClient.d.ts +2 -0
- package/dist/v2/core/resources/apiClient.js +52 -9
- package/dist/v2/core/resources/checkout.d.ts +89 -30
- package/dist/v2/core/resources/checkout.js +8 -0
- package/dist/v2/core/resources/customer.d.ts +20 -19
- package/dist/v2/core/resources/funnel.d.ts +17 -17
- package/dist/v2/core/resources/payments.d.ts +84 -13
- package/dist/v2/core/resources/payments.js +26 -9
- package/dist/v2/core/resources/shippingRates.d.ts +15 -0
- package/dist/v2/core/resources/shippingRates.js +11 -0
- package/dist/v2/core/types.d.ts +50 -12
- package/dist/v2/core/types.js +0 -3
- package/dist/v2/core/utils/checkout.d.ts +2 -2
- package/dist/v2/core/utils/checkout.js +7 -2
- package/dist/v2/core/utils/order.d.ts +11 -9
- package/dist/v2/core/utils/previewModeIndicator.js +101 -101
- package/dist/v2/index.d.ts +4 -2
- package/dist/v2/index.js +1 -1
- package/dist/v2/react/components/ApplePayButton.js +13 -4
- package/dist/v2/react/components/FunnelScriptInjector.js +51 -30
- package/dist/v2/react/components/WhopCheckout.d.ts +24 -0
- package/dist/v2/react/components/WhopCheckout.js +231 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +1 -1
- package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +181 -0
- package/dist/v2/react/hooks/payment-actions/useErrorAction.d.ts +9 -0
- package/dist/v2/react/hooks/payment-actions/useErrorAction.js +21 -0
- package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.js +187 -0
- package/dist/v2/react/hooks/payment-actions/useKessPayAction.d.ts +11 -0
- package/dist/v2/react/hooks/payment-actions/useKessPayAction.js +91 -0
- package/dist/v2/react/hooks/payment-actions/useMasterCardAction.d.ts +24 -0
- package/dist/v2/react/hooks/payment-actions/useMasterCardAction.js +221 -0
- package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.d.ts +15 -0
- package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +142 -0
- package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.d.ts +3 -0
- package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +13 -0
- package/dist/v2/react/hooks/payment-actions/useRedirectAction.d.ts +10 -0
- package/dist/v2/react/hooks/payment-actions/useRedirectAction.js +35 -0
- package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.js +192 -0
- package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.d.ts +14 -0
- package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.js +81 -0
- package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.d.ts +11 -0
- package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.js +84 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.d.ts +14 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.js +36 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.d.ts +31 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +212 -0
- package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.d.ts +14 -0
- package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.js +207 -0
- package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.d.ts +12 -0
- package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.js +101 -0
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +6 -0
- package/dist/v2/react/hooks/useCheckoutQuery.js +45 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +1 -2
- package/dist/v2/react/hooks/useGeoLocation.d.ts +2 -1
- package/dist/v2/react/hooks/useGeoLocation.js +4 -2
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +82 -33
- package/dist/v2/react/hooks/useISOData.js +1 -1
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +3 -3
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +18 -5
- package/dist/v2/react/hooks/usePaymentQuery.js +63 -1015
- package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +3 -2
- package/dist/v2/react/hooks/usePaymentRetrieve.js +3 -1
- package/dist/v2/react/hooks/usePixelTracking.d.ts +5 -43
- package/dist/v2/react/hooks/usePixelTracking.js +213 -407
- package/dist/v2/react/hooks/useShippingRatesQuery.d.ts +6 -0
- package/dist/v2/react/hooks/useShippingRatesQuery.js +47 -4
- package/dist/v2/react/hooks/useStepConfig.d.ts +2 -8
- package/dist/v2/react/hooks/useStepConfig.js +1 -1
- package/dist/v2/react/hooks/useWhopPaymentPolling.d.ts +30 -0
- package/dist/v2/react/hooks/useWhopPaymentPolling.js +61 -0
- package/dist/v2/react/index.d.ts +7 -0
- package/dist/v2/react/index.js +4 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +2 -1
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +3 -1
- package/dist/v2/react/providers/TagadaProvider.js +76 -7
- package/dist/v2/standalone/external-tracker.d.ts +52 -46
- package/dist/v2/standalone/external-tracker.js +205 -98
- package/dist/v2/standalone/index.d.ts +22 -0
- package/dist/v2/standalone/index.js +125 -0
- package/package.json +112 -112
- package/dist/react/utils/__tests__/urlUtils.test.d.ts +0 -1
- package/dist/react/utils/__tests__/urlUtils.test.js +0 -189
- package/dist/v2/core/__tests__/pathRemapping.test.d.ts +0 -11
- package/dist/v2/core/__tests__/pathRemapping.test.js +0 -776
|
@@ -1,776 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path Remapping Tests
|
|
3
|
-
*
|
|
4
|
-
* Comprehensive test suite for path remapping functionality including:
|
|
5
|
-
* - Exact path matching
|
|
6
|
-
* - Parameter matching with different names
|
|
7
|
-
* - Wildcards and optional segments
|
|
8
|
-
* - Edge cases and error handling
|
|
9
|
-
* - SDK functions: shouldMatchRoute, getInternalPath, getPathInfo
|
|
10
|
-
*/
|
|
11
|
-
import { match } from 'path-to-regexp';
|
|
12
|
-
// Mock browser environment
|
|
13
|
-
const mockWindow = {
|
|
14
|
-
location: {
|
|
15
|
-
pathname: '/',
|
|
16
|
-
hostname: 'localhost',
|
|
17
|
-
search: '',
|
|
18
|
-
href: 'http://localhost:5173/',
|
|
19
|
-
},
|
|
20
|
-
addEventListener: jest.fn(),
|
|
21
|
-
removeEventListener: jest.fn(),
|
|
22
|
-
dispatchEvent: jest.fn()
|
|
23
|
-
};
|
|
24
|
-
const mockDocument = {
|
|
25
|
-
querySelector: jest.fn()
|
|
26
|
-
};
|
|
27
|
-
const mockLocalStorage = {
|
|
28
|
-
getItem: jest.fn(),
|
|
29
|
-
setItem: jest.fn(),
|
|
30
|
-
removeItem: jest.fn(),
|
|
31
|
-
clear: jest.fn()
|
|
32
|
-
};
|
|
33
|
-
const mockHistory = {
|
|
34
|
-
pushState: jest.fn(),
|
|
35
|
-
replaceState: jest.fn(),
|
|
36
|
-
back: jest.fn(),
|
|
37
|
-
forward: jest.fn(),
|
|
38
|
-
go: jest.fn(),
|
|
39
|
-
length: 0,
|
|
40
|
-
scrollRestoration: 'auto',
|
|
41
|
-
state: null
|
|
42
|
-
};
|
|
43
|
-
// Setup global mocks
|
|
44
|
-
global.window = mockWindow;
|
|
45
|
-
global.document = mockDocument;
|
|
46
|
-
global.localStorage = mockLocalStorage;
|
|
47
|
-
global.history = mockHistory;
|
|
48
|
-
describe('Path Remapping - Pattern Matching', () => {
|
|
49
|
-
describe('Exact Path Matching', () => {
|
|
50
|
-
it('should match exact paths', () => {
|
|
51
|
-
const pattern = '/hello';
|
|
52
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
53
|
-
expect(matchFn('/hello')).not.toBe(false);
|
|
54
|
-
expect(matchFn('/hello/')).not.toBe(false);
|
|
55
|
-
expect(matchFn('/hello-world')).toBe(false);
|
|
56
|
-
expect(matchFn('/helloworld')).toBe(false);
|
|
57
|
-
});
|
|
58
|
-
it('should NOT match paths with additional segments', () => {
|
|
59
|
-
const pattern = '/hello';
|
|
60
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
61
|
-
expect(matchFn('/hello/world')).toBe(false);
|
|
62
|
-
expect(matchFn('/hello/123')).toBe(false);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
describe('Parameter Matching - Same Names', () => {
|
|
66
|
-
it('should match paths with single parameter', () => {
|
|
67
|
-
const pattern = '/products/:id';
|
|
68
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
69
|
-
const result1 = matchFn('/products/123');
|
|
70
|
-
const result2 = matchFn('/products/abc-xyz');
|
|
71
|
-
expect(result1).not.toBe(false);
|
|
72
|
-
expect(result2).not.toBe(false);
|
|
73
|
-
if (result1 !== false) {
|
|
74
|
-
expect(result1.params).toEqual({ id: '123' });
|
|
75
|
-
}
|
|
76
|
-
if (result2 !== false) {
|
|
77
|
-
expect(result2.params).toEqual({ id: 'abc-xyz' });
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
it('should match paths with multiple parameters', () => {
|
|
81
|
-
const pattern = '/users/:userId/posts/:postId';
|
|
82
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
83
|
-
const result = matchFn('/users/john/posts/456');
|
|
84
|
-
expect(result).not.toBe(false);
|
|
85
|
-
if (result !== false) {
|
|
86
|
-
expect(result.params).toEqual({ userId: 'john', postId: '456' });
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
describe('Parameter Matching - Different Names', () => {
|
|
91
|
-
it('should match external pattern with different parameter names than internal', () => {
|
|
92
|
-
// External: /myremap/:param
|
|
93
|
-
// Internal: /hello-with-param/:myparam
|
|
94
|
-
// The matching only cares about the external pattern structure
|
|
95
|
-
const externalPattern = '/myremap/:param';
|
|
96
|
-
const matchFn = match(externalPattern, { decode: decodeURIComponent, end: true });
|
|
97
|
-
const result = matchFn('/myremap/test123');
|
|
98
|
-
expect(result).not.toBe(false);
|
|
99
|
-
if (result !== false) {
|
|
100
|
-
expect(result.params).toEqual({ param: 'test123' });
|
|
101
|
-
// The internal path would be /hello-with-param/:myparam
|
|
102
|
-
// The param 'test123' would be available as :myparam in the internal route
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
it('should match complex remapping scenario', () => {
|
|
106
|
-
// External: /products/:productId/reviews/:reviewId
|
|
107
|
-
// Internal: /items/:id/comments/:commentId
|
|
108
|
-
const externalPattern = '/products/:productId/reviews/:reviewId';
|
|
109
|
-
const matchFn = match(externalPattern, { decode: decodeURIComponent, end: true });
|
|
110
|
-
const result = matchFn('/products/abc123/reviews/xyz789');
|
|
111
|
-
expect(result).not.toBe(false);
|
|
112
|
-
if (result !== false) {
|
|
113
|
-
expect(result.params).toEqual({
|
|
114
|
-
productId: 'abc123',
|
|
115
|
-
reviewId: 'xyz789'
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
describe('Special Characters and Encoding', () => {
|
|
121
|
-
it('should handle URL-encoded characters', () => {
|
|
122
|
-
const pattern = '/products/:id';
|
|
123
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
124
|
-
const result = matchFn('/products/hello%20world');
|
|
125
|
-
expect(result).not.toBe(false);
|
|
126
|
-
if (result !== false) {
|
|
127
|
-
expect(result.params).toEqual({ id: 'hello world' });
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
it('should handle special characters in parameters', () => {
|
|
131
|
-
const pattern = '/users/:username';
|
|
132
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
133
|
-
const result1 = matchFn('/users/john-doe');
|
|
134
|
-
const result2 = matchFn('/users/user_123');
|
|
135
|
-
const result3 = matchFn('/users/user.name');
|
|
136
|
-
expect(result1).not.toBe(false);
|
|
137
|
-
expect(result2).not.toBe(false);
|
|
138
|
-
expect(result3).not.toBe(false);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
describe('Optional Segments', () => {
|
|
142
|
-
it('should match paths with optional segments', () => {
|
|
143
|
-
const pattern = '/users{/:id}';
|
|
144
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
145
|
-
const result1 = matchFn('/users');
|
|
146
|
-
const result2 = matchFn('/users/123');
|
|
147
|
-
expect(result1).not.toBe(false);
|
|
148
|
-
expect(result2).not.toBe(false);
|
|
149
|
-
if (result2 !== false) {
|
|
150
|
-
expect(result2.params).toEqual({ id: '123' });
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
it('should match paths with multiple optional segments', () => {
|
|
154
|
-
const pattern = '/api{/:version}{/:endpoint}';
|
|
155
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
156
|
-
expect(matchFn('/api')).not.toBe(false);
|
|
157
|
-
expect(matchFn('/api/v1')).not.toBe(false);
|
|
158
|
-
expect(matchFn('/api/v1/users')).not.toBe(false);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
describe('Wildcard Matching', () => {
|
|
162
|
-
it('should match paths with wildcards', () => {
|
|
163
|
-
const pattern = '/files/*path';
|
|
164
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
165
|
-
const result1 = matchFn('/files/docs');
|
|
166
|
-
const result2 = matchFn('/files/docs/readme.md');
|
|
167
|
-
const result3 = matchFn('/files/images/logo.png');
|
|
168
|
-
expect(result1).not.toBe(false);
|
|
169
|
-
expect(result2).not.toBe(false);
|
|
170
|
-
expect(result3).not.toBe(false);
|
|
171
|
-
if (result2 !== false) {
|
|
172
|
-
expect(result2.params.path).toEqual(['docs', 'readme.md']);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
describe('Edge Cases', () => {
|
|
177
|
-
it('should NOT match when path has trailing segments', () => {
|
|
178
|
-
const pattern = '/hello';
|
|
179
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
180
|
-
expect(matchFn('/hello/extra')).toBe(false);
|
|
181
|
-
expect(matchFn('/hello/extra/segments')).toBe(false);
|
|
182
|
-
});
|
|
183
|
-
it('should handle root path', () => {
|
|
184
|
-
const pattern = '/';
|
|
185
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
186
|
-
expect(matchFn('/')).not.toBe(false);
|
|
187
|
-
expect(matchFn('/hello')).toBe(false);
|
|
188
|
-
});
|
|
189
|
-
it('should handle empty parameter values', () => {
|
|
190
|
-
const pattern = '/users/:id';
|
|
191
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
192
|
-
// Empty parameter should not match
|
|
193
|
-
expect(matchFn('/users/')).toBe(false);
|
|
194
|
-
expect(matchFn('/users')).toBe(false);
|
|
195
|
-
});
|
|
196
|
-
it('should handle numeric parameters', () => {
|
|
197
|
-
const pattern = '/items/:id';
|
|
198
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true });
|
|
199
|
-
const result = matchFn('/items/12345');
|
|
200
|
-
expect(result).not.toBe(false);
|
|
201
|
-
if (result !== false) {
|
|
202
|
-
// Note: params are always strings
|
|
203
|
-
expect(result.params).toEqual({ id: '12345' });
|
|
204
|
-
expect(typeof result.params.id).toBe('string');
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
describe('Case Sensitivity', () => {
|
|
209
|
-
it('should be case-sensitive when option is enabled', () => {
|
|
210
|
-
const pattern = '/Hello';
|
|
211
|
-
const matchFn = match(pattern, { decode: decodeURIComponent, end: true, sensitive: true });
|
|
212
|
-
expect(matchFn('/Hello')).not.toBe(false);
|
|
213
|
-
expect(matchFn('/hello')).toBe(false);
|
|
214
|
-
expect(matchFn('/HELLO')).toBe(false);
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
describe('Real-World Scenarios', () => {
|
|
218
|
-
it('should handle e-commerce product remapping', () => {
|
|
219
|
-
// External: /p/:productSlug
|
|
220
|
-
// Internal: /products/:id
|
|
221
|
-
const externalPattern = '/p/:productSlug';
|
|
222
|
-
const matchFn = match(externalPattern, { decode: decodeURIComponent, end: true });
|
|
223
|
-
const result = matchFn('/p/awesome-product-name');
|
|
224
|
-
expect(result).not.toBe(false);
|
|
225
|
-
if (result !== false) {
|
|
226
|
-
expect(result.params).toEqual({ productSlug: 'awesome-product-name' });
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
it('should handle checkout flow remapping', () => {
|
|
230
|
-
// External: /checkout/:step
|
|
231
|
-
// Internal: /payment/:stepId
|
|
232
|
-
const externalPattern = '/checkout/:step';
|
|
233
|
-
const matchFn = match(externalPattern, { decode: decodeURIComponent, end: true });
|
|
234
|
-
const result1 = matchFn('/checkout/shipping');
|
|
235
|
-
const result2 = matchFn('/checkout/payment');
|
|
236
|
-
const result3 = matchFn('/checkout/confirmation');
|
|
237
|
-
expect(result1).not.toBe(false);
|
|
238
|
-
expect(result2).not.toBe(false);
|
|
239
|
-
expect(result3).not.toBe(false);
|
|
240
|
-
});
|
|
241
|
-
it('should handle thank you page with order ID remapping', () => {
|
|
242
|
-
// External: /order-success/:orderId
|
|
243
|
-
// Internal: /thankyou/:id
|
|
244
|
-
const externalPattern = '/order-success/:orderId';
|
|
245
|
-
const matchFn = match(externalPattern, { decode: decodeURIComponent, end: true });
|
|
246
|
-
const result = matchFn('/order-success/ORD-123-ABC');
|
|
247
|
-
expect(result).not.toBe(false);
|
|
248
|
-
if (result !== false) {
|
|
249
|
-
expect(result.params).toEqual({ orderId: 'ORD-123-ABC' });
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
it('should NOT match wrong paths', () => {
|
|
253
|
-
const externalPattern = '/myremap/:param';
|
|
254
|
-
const matchFn = match(externalPattern, { decode: decodeURIComponent, end: true });
|
|
255
|
-
// These should NOT match
|
|
256
|
-
expect(matchFn('/other-page')).toBe(false);
|
|
257
|
-
expect(matchFn('/myremap')).toBe(false); // Missing parameter
|
|
258
|
-
expect(matchFn('/myremapped/test')).toBe(false); // Wrong path
|
|
259
|
-
expect(matchFn('/myremap/test/extra')).toBe(false); // Extra segment
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
describe('Parameter Name Mapping Logic', () => {
|
|
263
|
-
it('should demonstrate parameter name independence', () => {
|
|
264
|
-
// The key insight: External and internal paths can have different param names
|
|
265
|
-
// The actual URL is matched against the EXTERNAL pattern
|
|
266
|
-
// The resulting params are then mapped to the INTERNAL route
|
|
267
|
-
const externalPattern = '/custom/:myParam';
|
|
268
|
-
const internalPath = '/hello/:differentParam';
|
|
269
|
-
const matchFn = match(externalPattern, { decode: decodeURIComponent, end: true });
|
|
270
|
-
const result = matchFn('/custom/value123');
|
|
271
|
-
expect(result).not.toBe(false);
|
|
272
|
-
if (result !== false) {
|
|
273
|
-
// External pattern matched, param is 'myParam'
|
|
274
|
-
expect(result.params).toEqual({ myParam: 'value123' });
|
|
275
|
-
// In the actual implementation, React Router will handle the internal route
|
|
276
|
-
// The param value 'value123' will be available as 'differentParam'
|
|
277
|
-
// This works because both routes expect a single param in the same position
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
it('should work with multiple params in different order', () => {
|
|
281
|
-
// External: /users/:userId/posts/:postId
|
|
282
|
-
// Internal: /members/:id/articles/:articleId
|
|
283
|
-
const externalPattern = '/users/:userId/posts/:postId';
|
|
284
|
-
const matchFn = match(externalPattern, { decode: decodeURIComponent, end: true });
|
|
285
|
-
const result = matchFn('/users/john/posts/article-123');
|
|
286
|
-
expect(result).not.toBe(false);
|
|
287
|
-
if (result !== false) {
|
|
288
|
-
expect(result.params).toEqual({
|
|
289
|
-
userId: 'john',
|
|
290
|
-
postId: 'article-123'
|
|
291
|
-
});
|
|
292
|
-
// React Router will map these positions to the internal route params
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
describe('Path Remapping - Integration Tests', () => {
|
|
298
|
-
describe('Complete Remapping Flow', () => {
|
|
299
|
-
it('should simulate complete remapping from external to internal', () => {
|
|
300
|
-
const remapConfig = {
|
|
301
|
-
externalPath: '/myremap/:param',
|
|
302
|
-
internalPath: '/hello-with-param/:myparam'
|
|
303
|
-
};
|
|
304
|
-
const currentPath = '/myremap/test123';
|
|
305
|
-
// Step 1: Check if current path matches external pattern
|
|
306
|
-
const matchFn = match(remapConfig.externalPath, {
|
|
307
|
-
decode: decodeURIComponent,
|
|
308
|
-
end: true
|
|
309
|
-
});
|
|
310
|
-
const result = matchFn(currentPath);
|
|
311
|
-
// Step 2: If match, use internal path
|
|
312
|
-
expect(result).not.toBe(false);
|
|
313
|
-
if (result !== false) {
|
|
314
|
-
const shouldUseInternalPath = remapConfig.internalPath;
|
|
315
|
-
expect(shouldUseInternalPath).toBe('/hello-with-param/:myparam');
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
it('should handle no-match scenario correctly', () => {
|
|
319
|
-
const remapConfig = {
|
|
320
|
-
externalPath: '/myremap/:param',
|
|
321
|
-
internalPath: '/hello-with-param/:myparam'
|
|
322
|
-
};
|
|
323
|
-
const currentPath = '/different-path/test';
|
|
324
|
-
const matchFn = match(remapConfig.externalPath, {
|
|
325
|
-
decode: decodeURIComponent,
|
|
326
|
-
end: true
|
|
327
|
-
});
|
|
328
|
-
const result = matchFn(currentPath);
|
|
329
|
-
// Should NOT match
|
|
330
|
-
expect(result).toBe(false);
|
|
331
|
-
// Therefore, no remapping should occur
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
describe('Multiple Remap Rules (Future Feature)', () => {
|
|
335
|
-
it('should handle multiple remap configurations', () => {
|
|
336
|
-
const remapRules = [
|
|
337
|
-
{ externalPath: '/products/:id', internalPath: '/items/:itemId' },
|
|
338
|
-
{ externalPath: '/checkout/:step', internalPath: '/payment/:stepId' },
|
|
339
|
-
{ externalPath: '/order/:orderId', internalPath: '/thankyou/:id' }
|
|
340
|
-
];
|
|
341
|
-
const testCases = [
|
|
342
|
-
{ url: '/products/123', shouldMatch: 0 },
|
|
343
|
-
{ url: '/checkout/shipping', shouldMatch: 1 },
|
|
344
|
-
{ url: '/order/ABC-123', shouldMatch: 2 },
|
|
345
|
-
{ url: '/random/path', shouldMatch: -1 }
|
|
346
|
-
];
|
|
347
|
-
testCases.forEach(testCase => {
|
|
348
|
-
let matchedRuleIndex = -1;
|
|
349
|
-
for (let i = 0; i < remapRules.length; i++) {
|
|
350
|
-
const rule = remapRules[i];
|
|
351
|
-
const matchFn = match(rule.externalPath, {
|
|
352
|
-
decode: decodeURIComponent,
|
|
353
|
-
end: true
|
|
354
|
-
});
|
|
355
|
-
const result = matchFn(testCase.url);
|
|
356
|
-
if (result !== false) {
|
|
357
|
-
matchedRuleIndex = i;
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
expect(matchedRuleIndex).toBe(testCase.shouldMatch);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
});
|
|
366
|
-
describe('SDK Functions - shouldMatchRoute, getInternalPath, getPathInfo', () => {
|
|
367
|
-
// Import SDK functions
|
|
368
|
-
let shouldMatchRoute;
|
|
369
|
-
let getInternalPath;
|
|
370
|
-
let getPathInfo;
|
|
371
|
-
beforeEach(() => {
|
|
372
|
-
// Reset mocks
|
|
373
|
-
jest.resetModules();
|
|
374
|
-
jest.clearAllMocks();
|
|
375
|
-
// Reset window location
|
|
376
|
-
mockWindow.location.pathname = '/';
|
|
377
|
-
mockWindow.location.hostname = 'localhost';
|
|
378
|
-
mockWindow.location.search = '';
|
|
379
|
-
mockWindow.location.href = 'http://localhost:5173/';
|
|
380
|
-
// Reset localStorage mock
|
|
381
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
382
|
-
mockDocument.querySelector.mockReturnValue(null);
|
|
383
|
-
// Dynamically import to get fresh instances
|
|
384
|
-
const pathRemapping = require('../pathRemapping');
|
|
385
|
-
shouldMatchRoute = pathRemapping.shouldMatchRoute;
|
|
386
|
-
getInternalPath = pathRemapping.getInternalPath;
|
|
387
|
-
getPathInfo = pathRemapping.getPathInfo;
|
|
388
|
-
});
|
|
389
|
-
describe('shouldMatchRoute() - No Remapping', () => {
|
|
390
|
-
it('should match exact path without remapping', () => {
|
|
391
|
-
mockWindow.location.pathname = '/hello';
|
|
392
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
393
|
-
mockDocument.querySelector.mockReturnValue(null);
|
|
394
|
-
const result = shouldMatchRoute('/hello');
|
|
395
|
-
expect(result).toBe(true);
|
|
396
|
-
});
|
|
397
|
-
it('should match path with parameters', () => {
|
|
398
|
-
mockWindow.location.pathname = '/hello-with-param/test123';
|
|
399
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
400
|
-
mockDocument.querySelector.mockReturnValue(null);
|
|
401
|
-
const result = shouldMatchRoute('/hello-with-param/:myparam');
|
|
402
|
-
expect(result).toBe(true);
|
|
403
|
-
});
|
|
404
|
-
it('should NOT match different paths', () => {
|
|
405
|
-
mockWindow.location.pathname = '/other-page';
|
|
406
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
407
|
-
mockDocument.querySelector.mockReturnValue(null);
|
|
408
|
-
const result = shouldMatchRoute('/hello');
|
|
409
|
-
expect(result).toBe(false);
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
describe('shouldMatchRoute() - With Remapping', () => {
|
|
413
|
-
it('should match when current URL matches external pattern', () => {
|
|
414
|
-
mockWindow.location.pathname = '/myremap/test123';
|
|
415
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
416
|
-
externalPath: '/myremap/:param',
|
|
417
|
-
internalPath: '/hello-with-param/:myparam'
|
|
418
|
-
}));
|
|
419
|
-
mockDocument.querySelector.mockReturnValue(null);
|
|
420
|
-
const result = shouldMatchRoute('/hello-with-param/:myparam');
|
|
421
|
-
expect(result).toBe(true);
|
|
422
|
-
});
|
|
423
|
-
it('should NOT match when URL does not match external pattern', () => {
|
|
424
|
-
mockWindow.location.pathname = '/other-page';
|
|
425
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
426
|
-
externalPath: '/myremap/:param',
|
|
427
|
-
internalPath: '/hello-with-param/:myparam'
|
|
428
|
-
}));
|
|
429
|
-
mockDocument.querySelector.mockReturnValue(null);
|
|
430
|
-
const result = shouldMatchRoute('/hello-with-param/:myparam');
|
|
431
|
-
expect(result).toBe(false);
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
|
-
describe('Real-World Scenarios', () => {
|
|
435
|
-
it('E-commerce: Product page remapping', () => {
|
|
436
|
-
mockWindow.location.pathname = '/p/awesome-product-slug';
|
|
437
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
438
|
-
externalPath: '/p/:slug',
|
|
439
|
-
internalPath: '/products/:id'
|
|
440
|
-
}));
|
|
441
|
-
expect(getInternalPath()).toBe('/products/:id');
|
|
442
|
-
expect(shouldMatchRoute('/products/:id')).toBe(true);
|
|
443
|
-
expect(shouldMatchRoute('/hello')).toBe(false);
|
|
444
|
-
});
|
|
445
|
-
it('Checkout: Step remapping', () => {
|
|
446
|
-
mockWindow.location.pathname = '/checkout/shipping';
|
|
447
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
448
|
-
externalPath: '/checkout/:step',
|
|
449
|
-
internalPath: '/payment/:stepId'
|
|
450
|
-
}));
|
|
451
|
-
expect(getInternalPath()).toBe('/payment/:stepId');
|
|
452
|
-
expect(shouldMatchRoute('/payment/:stepId')).toBe(true);
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
});
|
|
456
|
-
describe('SDK Functions - matchRoute() with Parameter Extraction', () => {
|
|
457
|
-
let matchRoute;
|
|
458
|
-
beforeEach(() => {
|
|
459
|
-
// Reset mocks
|
|
460
|
-
jest.resetModules();
|
|
461
|
-
jest.clearAllMocks();
|
|
462
|
-
// Reset window location
|
|
463
|
-
mockWindow.location.pathname = '/';
|
|
464
|
-
mockWindow.location.hostname = 'localhost';
|
|
465
|
-
mockWindow.location.search = '';
|
|
466
|
-
mockWindow.location.href = 'http://localhost:5173/';
|
|
467
|
-
// Reset localStorage mock
|
|
468
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
469
|
-
mockDocument.querySelector.mockReturnValue(null);
|
|
470
|
-
// Dynamically import to get fresh instances
|
|
471
|
-
const pathRemapping = require('../pathRemapping');
|
|
472
|
-
matchRoute = pathRemapping.matchRoute;
|
|
473
|
-
});
|
|
474
|
-
describe('Non-Remapped Paths - Parameter Extraction', () => {
|
|
475
|
-
it('should match static path and return empty params', () => {
|
|
476
|
-
mockWindow.location.pathname = '/hello';
|
|
477
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
478
|
-
const result = matchRoute('/hello');
|
|
479
|
-
expect(result.matched).toBe(true);
|
|
480
|
-
expect(result.params).toEqual({});
|
|
481
|
-
});
|
|
482
|
-
it('should extract single parameter from URL', () => {
|
|
483
|
-
mockWindow.location.pathname = '/hello-with-param/test123';
|
|
484
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
485
|
-
const result = matchRoute('/hello-with-param/:myparam');
|
|
486
|
-
expect(result.matched).toBe(true);
|
|
487
|
-
expect(result.params).toEqual({ myparam: 'test123' });
|
|
488
|
-
});
|
|
489
|
-
it('should extract multiple parameters from URL', () => {
|
|
490
|
-
mockWindow.location.pathname = '/users/john/posts/456';
|
|
491
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
492
|
-
const result = matchRoute('/users/:userId/posts/:postId');
|
|
493
|
-
expect(result.matched).toBe(true);
|
|
494
|
-
expect(result.params).toEqual({
|
|
495
|
-
userId: 'john',
|
|
496
|
-
postId: '456'
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
it('should return no match for different path', () => {
|
|
500
|
-
mockWindow.location.pathname = '/other-page';
|
|
501
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
502
|
-
const result = matchRoute('/hello');
|
|
503
|
-
expect(result.matched).toBe(false);
|
|
504
|
-
expect(result.params).toEqual({});
|
|
505
|
-
});
|
|
506
|
-
it('should handle URL-encoded parameters', () => {
|
|
507
|
-
mockWindow.location.pathname = '/products/hello%20world';
|
|
508
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
509
|
-
const result = matchRoute('/products/:slug');
|
|
510
|
-
expect(result.matched).toBe(true);
|
|
511
|
-
expect(result.params).toEqual({ slug: 'hello world' });
|
|
512
|
-
});
|
|
513
|
-
});
|
|
514
|
-
describe('Remapped Paths - Parameter Extraction (Matching Names)', () => {
|
|
515
|
-
it('should extract params from external URL with matching parameter names', () => {
|
|
516
|
-
mockWindow.location.pathname = '/myremap/test123';
|
|
517
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
518
|
-
externalPath: '/myremap/:myparam',
|
|
519
|
-
internalPath: '/hello-with-param/:myparam'
|
|
520
|
-
}));
|
|
521
|
-
const result = matchRoute('/hello-with-param/:myparam');
|
|
522
|
-
expect(result.matched).toBe(true);
|
|
523
|
-
expect(result.params).toEqual({ myparam: 'test123' });
|
|
524
|
-
});
|
|
525
|
-
it('should extract multiple params from external URL with matching names', () => {
|
|
526
|
-
mockWindow.location.pathname = '/u/john/p/456';
|
|
527
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
528
|
-
externalPath: '/u/:userId/p/:postId',
|
|
529
|
-
internalPath: '/users/:userId/posts/:postId'
|
|
530
|
-
}));
|
|
531
|
-
const result = matchRoute('/users/:userId/posts/:postId');
|
|
532
|
-
expect(result.matched).toBe(true);
|
|
533
|
-
expect(result.params).toEqual({
|
|
534
|
-
userId: 'john',
|
|
535
|
-
postId: '456'
|
|
536
|
-
});
|
|
537
|
-
});
|
|
538
|
-
it('should handle complex slugs with hyphens and numbers', () => {
|
|
539
|
-
mockWindow.location.pathname = '/custom/awesome-product-123';
|
|
540
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
541
|
-
externalPath: '/custom/:slug',
|
|
542
|
-
internalPath: '/products/:slug'
|
|
543
|
-
}));
|
|
544
|
-
const result = matchRoute('/products/:slug');
|
|
545
|
-
expect(result.matched).toBe(true);
|
|
546
|
-
expect(result.params).toEqual({ slug: 'awesome-product-123' });
|
|
547
|
-
});
|
|
548
|
-
it('should return no match when external URL does not match pattern', () => {
|
|
549
|
-
mockWindow.location.pathname = '/wrong-path/test';
|
|
550
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
551
|
-
externalPath: '/myremap/:param',
|
|
552
|
-
internalPath: '/hello-with-param/:myparam'
|
|
553
|
-
}));
|
|
554
|
-
const result = matchRoute('/hello-with-param/:myparam');
|
|
555
|
-
expect(result.matched).toBe(false);
|
|
556
|
-
expect(result.params).toEqual({});
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
describe('Remapped Paths - Production Meta Tag', () => {
|
|
560
|
-
it('should work with simple meta tag (current production format)', () => {
|
|
561
|
-
// Note: In production, meta tag only provides the internal path, not the full remap config
|
|
562
|
-
// The remapping logic would be handled by the CRM/server-side
|
|
563
|
-
mockWindow.location.pathname = '/custom/test456';
|
|
564
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
565
|
-
// Mock the x-plugin-internal-path meta tag (current production format)
|
|
566
|
-
const mockMetaTag = {
|
|
567
|
-
getAttribute: jest.fn().mockReturnValue('/hello-with-param/:myparam')
|
|
568
|
-
};
|
|
569
|
-
mockDocument.querySelector.mockReturnValue(mockMetaTag);
|
|
570
|
-
const result = matchRoute('/hello-with-param/:myparam');
|
|
571
|
-
// Without localStorage config, the meta tag alone doesn't provide external pattern
|
|
572
|
-
// so params can't be extracted from the external URL
|
|
573
|
-
// This test confirms the current behavior
|
|
574
|
-
expect(result.matched).toBe(true);
|
|
575
|
-
// Params will be empty or extracted from trying to match against internal pattern
|
|
576
|
-
expect(result.params).toBeDefined();
|
|
577
|
-
});
|
|
578
|
-
it('should prioritize localStorage over meta tag', () => {
|
|
579
|
-
mockWindow.location.pathname = '/local/test789';
|
|
580
|
-
// localStorage config
|
|
581
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
582
|
-
externalPath: '/local/:localParam',
|
|
583
|
-
internalPath: '/hello-with-param/:myparam'
|
|
584
|
-
}));
|
|
585
|
-
// Meta tag config (should be ignored)
|
|
586
|
-
const mockMetaTag = {
|
|
587
|
-
getAttribute: jest.fn().mockReturnValue(JSON.stringify({
|
|
588
|
-
external: '/meta/:metaParam',
|
|
589
|
-
internal: '/hello-with-param/:myparam'
|
|
590
|
-
}))
|
|
591
|
-
};
|
|
592
|
-
mockDocument.querySelector.mockReturnValue(mockMetaTag);
|
|
593
|
-
const result = matchRoute('/hello-with-param/:myparam');
|
|
594
|
-
expect(result.matched).toBe(true);
|
|
595
|
-
expect(result.params).toEqual({ localParam: 'test789' });
|
|
596
|
-
});
|
|
597
|
-
});
|
|
598
|
-
describe('Edge Cases and Error Handling', () => {
|
|
599
|
-
it('should handle empty parameter values', () => {
|
|
600
|
-
mockWindow.location.pathname = '/hello-with-param/';
|
|
601
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
602
|
-
const result = matchRoute('/hello-with-param/:myparam');
|
|
603
|
-
// path-to-regexp should handle this
|
|
604
|
-
expect(result.matched).toBe(false);
|
|
605
|
-
});
|
|
606
|
-
it('should handle special characters in parameters', () => {
|
|
607
|
-
mockWindow.location.pathname = '/products/test-product_123';
|
|
608
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
609
|
-
const result = matchRoute('/products/:id');
|
|
610
|
-
expect(result.matched).toBe(true);
|
|
611
|
-
expect(result.params).toEqual({ id: 'test-product_123' });
|
|
612
|
-
});
|
|
613
|
-
it('should return no match for malformed paths', () => {
|
|
614
|
-
mockWindow.location.pathname = '/hello//double-slash';
|
|
615
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
616
|
-
const result = matchRoute('/hello/:param');
|
|
617
|
-
expect(result.matched).toBe(false);
|
|
618
|
-
});
|
|
619
|
-
it('should handle root path', () => {
|
|
620
|
-
mockWindow.location.pathname = '/';
|
|
621
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
622
|
-
const result = matchRoute('/');
|
|
623
|
-
expect(result.matched).toBe(true);
|
|
624
|
-
expect(result.params).toEqual({});
|
|
625
|
-
});
|
|
626
|
-
it('should throw error for invalid internal path (no leading slash)', () => {
|
|
627
|
-
mockWindow.location.pathname = '/hello';
|
|
628
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
629
|
-
expect(() => {
|
|
630
|
-
matchRoute('hello');
|
|
631
|
-
}).toThrow('internalPath must start with /');
|
|
632
|
-
});
|
|
633
|
-
it('should throw error for empty internal path', () => {
|
|
634
|
-
mockWindow.location.pathname = '/hello';
|
|
635
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
636
|
-
expect(() => {
|
|
637
|
-
matchRoute('');
|
|
638
|
-
}).toThrow('internalPath cannot be empty');
|
|
639
|
-
});
|
|
640
|
-
});
|
|
641
|
-
describe('Real-World E-Commerce Scenarios', () => {
|
|
642
|
-
it('Product page: Custom SEO-friendly URL → Internal product ID', () => {
|
|
643
|
-
mockWindow.location.pathname = '/p/awesome-wireless-headphones';
|
|
644
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
645
|
-
externalPath: '/p/:slug',
|
|
646
|
-
internalPath: '/products/:slug'
|
|
647
|
-
}));
|
|
648
|
-
const result = matchRoute('/products/:slug');
|
|
649
|
-
expect(result.matched).toBe(true);
|
|
650
|
-
expect(result.params).toEqual({ slug: 'awesome-wireless-headphones' });
|
|
651
|
-
});
|
|
652
|
-
it('Checkout flow: Custom steps → Internal payment flow', () => {
|
|
653
|
-
mockWindow.location.pathname = '/checkout/billing-info';
|
|
654
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
655
|
-
externalPath: '/checkout/:step',
|
|
656
|
-
internalPath: '/payment/:step'
|
|
657
|
-
}));
|
|
658
|
-
const result = matchRoute('/payment/:step');
|
|
659
|
-
expect(result.matched).toBe(true);
|
|
660
|
-
expect(result.params).toEqual({ step: 'billing-info' });
|
|
661
|
-
});
|
|
662
|
-
it('Thank you page: Order confirmation with custom URL', () => {
|
|
663
|
-
mockWindow.location.pathname = '/order/ORD-2024-12345';
|
|
664
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
665
|
-
externalPath: '/order/:orderId',
|
|
666
|
-
internalPath: '/thankyou/:orderId'
|
|
667
|
-
}));
|
|
668
|
-
const result = matchRoute('/thankyou/:orderId');
|
|
669
|
-
expect(result.matched).toBe(true);
|
|
670
|
-
expect(result.params).toEqual({ orderId: 'ORD-2024-12345' });
|
|
671
|
-
});
|
|
672
|
-
it('User profile: Username in URL', () => {
|
|
673
|
-
mockWindow.location.pathname = '/user/john_doe_123';
|
|
674
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
675
|
-
externalPath: '/user/:username',
|
|
676
|
-
internalPath: '/profile/:username'
|
|
677
|
-
}));
|
|
678
|
-
const result = matchRoute('/profile/:username');
|
|
679
|
-
expect(result.matched).toBe(true);
|
|
680
|
-
expect(result.params).toEqual({ username: 'john_doe_123' });
|
|
681
|
-
});
|
|
682
|
-
it('Blog post: SEO slug → Internal post ID structure', () => {
|
|
683
|
-
mockWindow.location.pathname = '/blog/how-to-use-path-remapping';
|
|
684
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
685
|
-
externalPath: '/blog/:slug',
|
|
686
|
-
internalPath: '/posts/:slug'
|
|
687
|
-
}));
|
|
688
|
-
const result = matchRoute('/posts/:slug');
|
|
689
|
-
expect(result.matched).toBe(true);
|
|
690
|
-
expect(result.params).toEqual({ slug: 'how-to-use-path-remapping' });
|
|
691
|
-
});
|
|
692
|
-
});
|
|
693
|
-
describe('Complex Path Patterns', () => {
|
|
694
|
-
it('should handle nested paths with multiple segments', () => {
|
|
695
|
-
mockWindow.location.pathname = '/users/john/posts/456/comments';
|
|
696
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
697
|
-
const result = matchRoute('/users/:userId/posts/:postId/comments');
|
|
698
|
-
expect(result.matched).toBe(true);
|
|
699
|
-
expect(result.params).toEqual({
|
|
700
|
-
userId: 'john',
|
|
701
|
-
postId: '456'
|
|
702
|
-
});
|
|
703
|
-
});
|
|
704
|
-
it('should handle paths with trailing slashes', () => {
|
|
705
|
-
mockWindow.location.pathname = '/hello-with-param/test123/';
|
|
706
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
707
|
-
const result = matchRoute('/hello-with-param/:myparam');
|
|
708
|
-
expect(result.matched).toBe(true);
|
|
709
|
-
expect(result.params).toEqual({ myparam: 'test123' });
|
|
710
|
-
});
|
|
711
|
-
it('should handle numeric IDs', () => {
|
|
712
|
-
mockWindow.location.pathname = '/products/12345';
|
|
713
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
714
|
-
const result = matchRoute('/products/:id');
|
|
715
|
-
expect(result.matched).toBe(true);
|
|
716
|
-
expect(result.params).toEqual({ id: '12345' });
|
|
717
|
-
});
|
|
718
|
-
it('should handle UUID-style IDs', () => {
|
|
719
|
-
mockWindow.location.pathname = '/orders/550e8400-e29b-41d4-a716-446655440000';
|
|
720
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
721
|
-
const result = matchRoute('/orders/:orderId');
|
|
722
|
-
expect(result.matched).toBe(true);
|
|
723
|
-
expect(result.params).toEqual({ orderId: '550e8400-e29b-41d4-a716-446655440000' });
|
|
724
|
-
});
|
|
725
|
-
});
|
|
726
|
-
describe('Concurrent Requests - Cache Behavior', () => {
|
|
727
|
-
it('should return consistent results for same path', () => {
|
|
728
|
-
mockWindow.location.pathname = '/hello-with-param/test123';
|
|
729
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
730
|
-
const result1 = matchRoute('/hello-with-param/:myparam');
|
|
731
|
-
const result2 = matchRoute('/hello-with-param/:myparam');
|
|
732
|
-
expect(result1).toEqual(result2);
|
|
733
|
-
expect(result1.params).toEqual({ myparam: 'test123' });
|
|
734
|
-
});
|
|
735
|
-
it('should handle different paths in same session', () => {
|
|
736
|
-
mockLocalStorage.getItem.mockReturnValue(null);
|
|
737
|
-
// First path
|
|
738
|
-
mockWindow.location.pathname = '/products/123';
|
|
739
|
-
const result1 = matchRoute('/products/:id');
|
|
740
|
-
// Second path (simulating navigation)
|
|
741
|
-
mockWindow.location.pathname = '/users/john';
|
|
742
|
-
const result2 = matchRoute('/users/:username');
|
|
743
|
-
expect(result1.matched).toBe(true);
|
|
744
|
-
expect(result1.params).toEqual({ id: '123' });
|
|
745
|
-
expect(result2.matched).toBe(true);
|
|
746
|
-
expect(result2.params).toEqual({ username: 'john' });
|
|
747
|
-
});
|
|
748
|
-
});
|
|
749
|
-
describe('Integration with CRM Validation Rules', () => {
|
|
750
|
-
it('should work with enforced matching parameter names', () => {
|
|
751
|
-
// CRM ensures external and internal params have same names
|
|
752
|
-
mockWindow.location.pathname = '/custom/test123';
|
|
753
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
754
|
-
externalPath: '/custom/:myparam',
|
|
755
|
-
internalPath: '/hello-with-param/:myparam'
|
|
756
|
-
}));
|
|
757
|
-
const result = matchRoute('/hello-with-param/:myparam');
|
|
758
|
-
expect(result.matched).toBe(true);
|
|
759
|
-
// Parameter names match, so extraction is straightforward
|
|
760
|
-
expect(result.params).toEqual({ myparam: 'test123' });
|
|
761
|
-
});
|
|
762
|
-
it('should handle multiple params with enforced naming', () => {
|
|
763
|
-
mockWindow.location.pathname = '/c/electronics/p/laptop-123';
|
|
764
|
-
mockLocalStorage.getItem.mockReturnValue(JSON.stringify({
|
|
765
|
-
externalPath: '/c/:category/p/:productId',
|
|
766
|
-
internalPath: '/shop/:category/item/:productId'
|
|
767
|
-
}));
|
|
768
|
-
const result = matchRoute('/shop/:category/item/:productId');
|
|
769
|
-
expect(result.matched).toBe(true);
|
|
770
|
-
expect(result.params).toEqual({
|
|
771
|
-
category: 'electronics',
|
|
772
|
-
productId: 'laptop-123'
|
|
773
|
-
});
|
|
774
|
-
});
|
|
775
|
-
});
|
|
776
|
-
});
|