@akinon/next 1.126.14-v1-rc.10 → 1.126.14
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 +2 -3
- package/__tests__/attribute-based-shipping-middleware.test.ts +85 -0
- package/__tests__/attribute-based-shipping-page.test.ts +48 -0
- package/data/client/checkout.ts +25 -3
- package/data/urls.ts +2 -0
- package/jest.config.js +2 -1
- package/package.json +2 -2
- package/redux/middlewares/checkout.ts +5 -1
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +22 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
-
## 1.126.14
|
|
3
|
+
## 1.126.14
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- 182a639b: ZERO-4483: Verify v1-rc maintenance pipeline (no-op patch to confirm v1-rc dist-tag flow)
|
|
7
|
+
- fb75f8e: ZERO-5168: Post the attribute-based shipping selection to the page name Commerce reports
|
|
9
8
|
|
|
10
9
|
## 1.126.13
|
|
11
10
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const mockInitiate = jest.fn((options: Record<string, number>) => ({
|
|
2
|
+
type: 'mock/setAttributeBasedShippingOptions',
|
|
3
|
+
payload: options
|
|
4
|
+
}));
|
|
5
|
+
|
|
6
|
+
jest.mock('../data/client/checkout', () => ({
|
|
7
|
+
checkoutApi: {
|
|
8
|
+
endpoints: {
|
|
9
|
+
setAttributeBasedShippingOptions: {
|
|
10
|
+
initiate: (options: Record<string, number>) => mockInitiate(options)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
import { attributeBasedShippingOptionMiddleware } from '../redux/middlewares/pre-order/attribute-based-shipping-option';
|
|
17
|
+
|
|
18
|
+
const group = (...pks: number[]) => ({
|
|
19
|
+
attribute_based_shipping_options: pks.map((pk) => ({ pk })),
|
|
20
|
+
product_ids: [1]
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const run = (attributeBasedShippingOptions: Record<string, unknown>) => {
|
|
24
|
+
const dispatch = jest.fn();
|
|
25
|
+
const action = { payload: { pre_order: {} } };
|
|
26
|
+
const next = jest.fn(() => action);
|
|
27
|
+
|
|
28
|
+
const result = (attributeBasedShippingOptionMiddleware as any)({
|
|
29
|
+
getState: () => ({ checkout: { attributeBasedShippingOptions } }),
|
|
30
|
+
dispatch
|
|
31
|
+
})(next)(action);
|
|
32
|
+
|
|
33
|
+
return { dispatch, result, action };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('attributeBasedShippingOptionMiddleware', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
mockInitiate.mockClear();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('selects the first option of every group', () => {
|
|
42
|
+
const { dispatch, result } = run({
|
|
43
|
+
express: group(11, 12),
|
|
44
|
+
standart: group(21)
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(mockInitiate).toHaveBeenCalledWith({ express: 11, standart: 21 });
|
|
48
|
+
expect(dispatch).toHaveBeenCalledTimes(1);
|
|
49
|
+
expect(result).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('posts nothing when only some of the reported groups are selectable', () => {
|
|
53
|
+
// The backend rejects a selection that does not cover every group it
|
|
54
|
+
// reported, and that rejection leaves pre_order empty, which re-arms this
|
|
55
|
+
// middleware. Posting a partial map would loop.
|
|
56
|
+
const { dispatch, result, action } = run({
|
|
57
|
+
express: group(11),
|
|
58
|
+
None: group()
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(mockInitiate).not.toHaveBeenCalled();
|
|
62
|
+
expect(dispatch).not.toHaveBeenCalled();
|
|
63
|
+
expect(result).toBe(action);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('does not dispatch when no group has a selectable option at all', () => {
|
|
67
|
+
const { dispatch, result, action } = run({ None: group() });
|
|
68
|
+
|
|
69
|
+
expect(mockInitiate).not.toHaveBeenCalled();
|
|
70
|
+
expect(dispatch).not.toHaveBeenCalled();
|
|
71
|
+
expect(result).toBe(action);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('leaves a malformed group alone instead of throwing', () => {
|
|
75
|
+
expect(() => run({ None: {} })).not.toThrow();
|
|
76
|
+
expect(mockInitiate).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('still posts when every reported group is selectable', () => {
|
|
80
|
+
const { dispatch } = run({ express: group(11), standart: group(21, 22) });
|
|
81
|
+
|
|
82
|
+
expect(mockInitiate).toHaveBeenCalledWith({ express: 11, standart: 21 });
|
|
83
|
+
expect(dispatch).toHaveBeenCalledTimes(1);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAttributeBasedShippingOptionUrl,
|
|
3
|
+
setAttributeBasedShippingPageName
|
|
4
|
+
} from '../data/client/checkout';
|
|
5
|
+
import { checkout } from '../data/urls';
|
|
6
|
+
|
|
7
|
+
describe('attribute-based shipping option url', () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
setAttributeBasedShippingPageName(null);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('falls back to the classic page when nothing has been reported', () => {
|
|
13
|
+
expect(getAttributeBasedShippingOptionUrl()).toBe(
|
|
14
|
+
checkout.setAttributeBasedShippingOption
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('posts to the page name the backend reported for the step', () => {
|
|
19
|
+
setAttributeBasedShippingPageName(
|
|
20
|
+
'RemoteAttributeBasedShippingOptionSelectionPage'
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(getAttributeBasedShippingOptionUrl()).toBe(
|
|
24
|
+
'/orders/checkout/?page=RemoteAttributeBasedShippingOptionSelectionPage'
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('keeps the classic url when the reported name is the classic page', () => {
|
|
29
|
+
setAttributeBasedShippingPageName(
|
|
30
|
+
'AttributeBasedShippingOptionSelectionPage'
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(getAttributeBasedShippingOptionUrl()).toBe(
|
|
34
|
+
checkout.setAttributeBasedShippingOption
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns to the fallback once the reported name is cleared', () => {
|
|
39
|
+
setAttributeBasedShippingPageName(
|
|
40
|
+
'RemoteAttributeBasedShippingOptionSelectionPage'
|
|
41
|
+
);
|
|
42
|
+
setAttributeBasedShippingPageName(undefined);
|
|
43
|
+
|
|
44
|
+
expect(getAttributeBasedShippingOptionUrl()).toBe(
|
|
45
|
+
checkout.setAttributeBasedShippingOption
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
});
|
package/data/client/checkout.ts
CHANGED
|
@@ -145,6 +145,25 @@ const buildCheckoutRequestUrl = (
|
|
|
145
145
|
return buildClientRequestUrl(effectivePath, options);
|
|
146
146
|
};
|
|
147
147
|
|
|
148
|
+
// Commerce serves the attribute-based shipping step under more than one page
|
|
149
|
+
// name: the classic page and the remote variant, chosen by a dynamic setting we
|
|
150
|
+
// cannot see from here. Remember the page name reported for that step so the
|
|
151
|
+
// selection is posted back to the same page. contextListMiddleware is the only
|
|
152
|
+
// place that sees context_list, so it writes the value here; an RTK Query
|
|
153
|
+
// `query` callback is synchronous and cannot reach the store.
|
|
154
|
+
let attributeBasedShippingPageName: string | null = null;
|
|
155
|
+
|
|
156
|
+
export const setAttributeBasedShippingPageName = (pageName?: string | null) => {
|
|
157
|
+
attributeBasedShippingPageName = pageName ?? null;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const getAttributeBasedShippingOptionUrl = () =>
|
|
161
|
+
attributeBasedShippingPageName
|
|
162
|
+
? checkout.setAttributeBasedShippingOptionPage(
|
|
163
|
+
attributeBasedShippingPageName
|
|
164
|
+
)
|
|
165
|
+
: checkout.setAttributeBasedShippingOption;
|
|
166
|
+
|
|
148
167
|
const completeMasterpassPayment = async (
|
|
149
168
|
params: CompleteCreditCardParams,
|
|
150
169
|
dispatch: AppDispatch,
|
|
@@ -1003,7 +1022,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
1003
1022
|
Record<string, number>
|
|
1004
1023
|
>({
|
|
1005
1024
|
query: (options) => ({
|
|
1006
|
-
url: buildCheckoutRequestUrl(
|
|
1025
|
+
url: buildCheckoutRequestUrl(getAttributeBasedShippingOptionUrl(), {
|
|
1007
1026
|
useFormData: true
|
|
1008
1027
|
}),
|
|
1009
1028
|
method: 'POST',
|
|
@@ -1013,8 +1032,11 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
1013
1032
|
}),
|
|
1014
1033
|
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
|
|
1015
1034
|
dispatch(setShippingStepBusy(true));
|
|
1016
|
-
|
|
1017
|
-
|
|
1035
|
+
try {
|
|
1036
|
+
await queryFulfilled;
|
|
1037
|
+
} finally {
|
|
1038
|
+
dispatch(setShippingStepBusy(false));
|
|
1039
|
+
}
|
|
1018
1040
|
}
|
|
1019
1041
|
}),
|
|
1020
1042
|
setOrderSelectionPage: build.mutation<
|
package/data/urls.ts
CHANGED
|
@@ -139,6 +139,8 @@ export const checkout = {
|
|
|
139
139
|
couponSelectionPage: '/orders/checkout/?page=CouponSelectionPage',
|
|
140
140
|
setAttributeBasedShippingOption:
|
|
141
141
|
'/orders/checkout/?page=AttributeBasedShippingOptionSelectionPage',
|
|
142
|
+
setAttributeBasedShippingOptionPage: (pageName: string) =>
|
|
143
|
+
`/orders/checkout/?page=${pageName}`,
|
|
142
144
|
deliveryBagsPage: '/orders/checkout/?page=DeliveryBagsPage',
|
|
143
145
|
setOrderSelectionPage: '/orders/checkout/?page=OrderSelectionPage',
|
|
144
146
|
loyaltyCardPage: '/orders/checkout/?page=LoyaltyCardPage',
|
package/jest.config.js
CHANGED
|
@@ -12,7 +12,8 @@ module.exports = {
|
|
|
12
12
|
roots: [path.resolve(__dirname)],
|
|
13
13
|
transformIgnorePatterns: [],
|
|
14
14
|
moduleNameMapper: {
|
|
15
|
-
'^settings$': path.resolve(baseDir, 'src/settings.js')
|
|
15
|
+
'^settings$': path.resolve(baseDir, 'src/settings.js'),
|
|
16
|
+
'^@theme/(.*)$': path.resolve(baseDir, 'src/$1')
|
|
16
17
|
},
|
|
17
18
|
transform: {
|
|
18
19
|
'^.+\\.(tsx?|jsx?|mjs?)$': [
|
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.126.14
|
|
4
|
+
"version": "1.126.14",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"set-cookie-parser": "2.6.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@akinon/eslint-plugin-projectzero": "1.126.14
|
|
38
|
+
"@akinon/eslint-plugin-projectzero": "1.126.14",
|
|
39
39
|
"@babel/core": "7.26.10",
|
|
40
40
|
"@babel/preset-env": "7.26.9",
|
|
41
41
|
"@babel/preset-typescript": "7.27.0",
|
|
@@ -25,7 +25,10 @@ import {
|
|
|
25
25
|
setUnavailablePaymentOptions
|
|
26
26
|
} from '../../redux/reducers/checkout';
|
|
27
27
|
import { RootState, TypedDispatch } from 'redux/store';
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
checkoutApi,
|
|
30
|
+
setAttributeBasedShippingPageName
|
|
31
|
+
} from '../../data/client/checkout';
|
|
29
32
|
import { CheckoutContext, PreOrder } from '../../types';
|
|
30
33
|
import { getCookie } from '../../utils';
|
|
31
34
|
import settings from 'settings';
|
|
@@ -171,6 +174,7 @@ export const contextListMiddleware: Middleware = ({
|
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
if (context.page_context.attribute_based_shipping_options) {
|
|
177
|
+
setAttributeBasedShippingPageName(context.page_name);
|
|
174
178
|
dispatch(
|
|
175
179
|
setAttributeBasedShippingOptions(
|
|
176
180
|
context.page_context.attribute_based_shipping_options
|
|
@@ -21,12 +21,28 @@ export const attributeBasedShippingOptionMiddleware: Middleware = ({
|
|
|
21
21
|
Object.keys(attributeBasedShippingOptions).length > 0 &&
|
|
22
22
|
!preOrder.attribute_based_shipping_options
|
|
23
23
|
) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
24
|
+
// A group can come back without any option: the service logs it and still
|
|
25
|
+
// reports the group with an empty list, so indexing into it would throw
|
|
26
|
+
// inside the middleware chain and drop the whole checkout response.
|
|
27
|
+
const initialSelectedOptions: Record<string, number> = {};
|
|
28
|
+
|
|
29
|
+
Object.entries(attributeBasedShippingOptions).forEach(([key, group]) => {
|
|
30
|
+
const pk = group?.attribute_based_shipping_options?.[0]?.pk;
|
|
31
|
+
|
|
32
|
+
if (typeof pk === 'number') {
|
|
33
|
+
initialSelectedOptions[key] = pk;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// The backend rejects a selection that does not cover every group it
|
|
38
|
+
// reported, and the rejection leaves pre_order empty, which brings us
|
|
39
|
+
// straight back here. Post nothing rather than start that loop.
|
|
40
|
+
if (
|
|
41
|
+
Object.keys(initialSelectedOptions).length !==
|
|
42
|
+
Object.keys(attributeBasedShippingOptions).length
|
|
43
|
+
) {
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
30
46
|
|
|
31
47
|
dispatch(
|
|
32
48
|
apiEndpoints.setAttributeBasedShippingOptions.initiate(
|