@akinon/next 1.126.13 → 1.126.14-v1-rc.11

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.126.14-v1-rc.11
4
+
5
+ ### Patch Changes
6
+
7
+ - a34bb1b: ZERO-5168: Post the attribute-based shipping selection to the page name Commerce reports
8
+
9
+ ## 1.126.14-v1-rc.10
10
+
11
+ ### Patch Changes
12
+
13
+ - d480941: ZERO-4399: add card rewards to pz-masterpass-rest
14
+ - 182a639b: ZERO-4483: Verify v1-rc maintenance pipeline (no-op patch to confirm v1-rc dist-tag flow)
15
+
3
16
  ## 1.126.13
4
17
 
5
18
  ### Patch Changes
@@ -0,0 +1,74 @@
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('skips a group that came back without any option', () => {
53
+ const { dispatch } = run({
54
+ express: group(11),
55
+ None: group()
56
+ });
57
+
58
+ expect(mockInitiate).toHaveBeenCalledWith({ express: 11 });
59
+ expect(dispatch).toHaveBeenCalledTimes(1);
60
+ });
61
+
62
+ it('does not dispatch when no group has a selectable option', () => {
63
+ const { dispatch, result, action } = run({ None: group() });
64
+
65
+ expect(mockInitiate).not.toHaveBeenCalled();
66
+ expect(dispatch).not.toHaveBeenCalled();
67
+ expect(result).toBe(action);
68
+ });
69
+
70
+ it('leaves a malformed group alone instead of throwing', () => {
71
+ expect(() => run({ None: {} })).not.toThrow();
72
+ expect(mockInitiate).not.toHaveBeenCalled();
73
+ });
74
+ });
@@ -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
+ });
@@ -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(checkout.setAttributeBasedShippingOption, {
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
- await queryFulfilled;
1017
- dispatch(setShippingStepBusy(false));
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.13",
4
+ "version": "1.126.14-v1-rc.11",
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.13",
38
+ "@akinon/eslint-plugin-projectzero": "1.126.14-v1-rc.11",
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 { checkoutApi } from '../../data/client/checkout';
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,23 @@ export const attributeBasedShippingOptionMiddleware: Middleware = ({
21
21
  Object.keys(attributeBasedShippingOptions).length > 0 &&
22
22
  !preOrder.attribute_based_shipping_options
23
23
  ) {
24
- const initialSelectedOptions: Record<string, number> = Object.fromEntries(
25
- Object.entries(attributeBasedShippingOptions).map(([key, options]) => [
26
- key,
27
- options.attribute_based_shipping_options[0].pk
28
- ])
29
- );
24
+ // A group can come back without any option -- the remote grouping falls
25
+ // back to a single group when its provider is unreachable, and no rule
26
+ // ships for it. Skip those instead of indexing into an empty list, which
27
+ // would throw inside the middleware chain and drop the whole response.
28
+ const initialSelectedOptions: Record<string, number> = {};
29
+
30
+ Object.entries(attributeBasedShippingOptions).forEach(([key, group]) => {
31
+ const pk = group?.attribute_based_shipping_options?.[0]?.pk;
32
+
33
+ if (typeof pk === 'number') {
34
+ initialSelectedOptions[key] = pk;
35
+ }
36
+ });
37
+
38
+ if (Object.keys(initialSelectedOptions).length === 0) {
39
+ return result;
40
+ }
30
41
 
31
42
  dispatch(
32
43
  apiEndpoints.setAttributeBasedShippingOptions.initiate(