@tapcart/mobile-components 0.13.0 → 0.13.1

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.
@@ -4,6 +4,8 @@ export declare const useBlockConditionalRendering: (_props: {
4
4
  apiUrl: string;
5
5
  deviceVariables: any;
6
6
  customerVariables: any;
7
+ sessionVariables?: any;
8
+ storageVariables?: any;
7
9
  }, _block: PhoenixBlock, mobileComponentOverrides?: any) => {
8
10
  shouldShow: boolean;
9
11
  isLoading: any;
@@ -1 +1 @@
1
- {"version":3,"file":"use-block-conditional-rendering.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-block-conditional-rendering.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,YAAY,EAGb,MAAM,kBAAkB,CAAA;AAezB,eAAO,MAAM,4BAA4B,WAC/B;IACN,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,GAAG,CAAA;IACpB,iBAAiB,EAAE,GAAG,CAAA;CACvB,UACO,YAAY,6BACO,GAAG;;;CAoT/B,CAAA"}
1
+ {"version":3,"file":"use-block-conditional-rendering.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-block-conditional-rendering.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,YAAY,EAGb,MAAM,kBAAkB,CAAA;AAezB,eAAO,MAAM,4BAA4B,WAC/B;IACN,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,GAAG,CAAA;IACpB,iBAAiB,EAAE,GAAG,CAAA;IACtB,gBAAgB,CAAC,EAAE,GAAG,CAAA;IACtB,gBAAgB,CAAC,EAAE,GAAG,CAAA;CACvB,UACO,YAAY,6BACO,GAAG;;;CAwT/B,CAAA"}
@@ -16,12 +16,12 @@ import { useCollection as mclUseCollection } from "./use-collection";
16
16
  import { useSearchParams } from "next/navigation";
17
17
  import { gidFromId, countNumberOfTagsInState, getEnvState, shouldShowBlock, evaluateConditions, parseBCP47Locale, } from "../../lib/utils";
18
18
  export const useBlockConditionalRendering = (_props, _block, mobileComponentOverrides) => {
19
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
20
- const _l = ((_a = _block === null || _block === void 0 ? void 0 : _block.visibilityConditions) === null || _a === void 0 ? void 0 : _a.conditions) || {}, { enabled: conditionalsV1IsEnabled = false, exclude: isInverse = false } = _l, restOfProps = __rest(_l, ["enabled", "exclude"]);
19
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
20
+ const _k = ((_a = _block === null || _block === void 0 ? void 0 : _block.visibilityConditions) === null || _a === void 0 ? void 0 : _a.conditions) || {}, { enabled: conditionalsV1IsEnabled = false, exclude: isInverse = false } = _k, restOfProps = __rest(_k, ["enabled", "exclude"]);
21
21
  const { _version = 1, conditions, enabled: conditionalsV2IsEnabled = false, } = (_block === null || _block === void 0 ? void 0 : _block.visibilityConditions) || {};
22
22
  const isConditionalsEnabled = conditionalsV1IsEnabled || conditionalsV2IsEnabled;
23
23
  const blockState = restOfProps;
24
- const { appId = "", apiUrl = "", deviceVariables, customerVariables } = _props;
24
+ const { appId = "", apiUrl = "", deviceVariables, customerVariables, sessionVariables, storageVariables, } = _props;
25
25
  const searchParams = useSearchParams();
26
26
  const [deviceLanguage = "en", deviceCountry = "US"] = parseBCP47Locale(deviceVariables === null || deviceVariables === void 0 ? void 0 : deviceVariables.locale);
27
27
  const country = (_c = (_b = deviceVariables === null || deviceVariables === void 0 ? void 0 : deviceVariables.countryCode) !== null && _b !== void 0 ? _b : searchParams === null || searchParams === void 0 ? void 0 : searchParams.get("country")) !== null && _c !== void 0 ? _c : deviceCountry;
@@ -87,9 +87,7 @@ export const useBlockConditionalRendering = (_props, _block, mobileComponentOver
87
87
  country,
88
88
  productMetafieldsQuery,
89
89
  ]);
90
- const useProductsSkipArguments = useMemo(() => (mobileComponentOverrides === null || mobileComponentOverrides === void 0 ? void 0 : mobileComponentOverrides.useProducts)
91
- ? { skip: true }
92
- : null, [mobileComponentOverrides === null || mobileComponentOverrides === void 0 ? void 0 : mobileComponentOverrides.useProducts]);
90
+ const useProductsSkipArguments = useMemo(() => ((mobileComponentOverrides === null || mobileComponentOverrides === void 0 ? void 0 : mobileComponentOverrides.useProducts) ? { skip: true } : null), [mobileComponentOverrides === null || mobileComponentOverrides === void 0 ? void 0 : mobileComponentOverrides.useProducts]);
93
91
  const useProductsInput = useMemo(() => shouldFetchProduct ? useProductsArguments : useProductsSkipArguments, [shouldFetchProduct, useProductsArguments, useProductsSkipArguments]);
94
92
  const { products, error: useProductsError, isLoading: isProductsLoading, } = useProducts(useProductsInput);
95
93
  const blockCollectionMetafields = useMemo(() => {
@@ -180,8 +178,11 @@ export const useBlockConditionalRendering = (_props, _block, mobileComponentOver
180
178
  product,
181
179
  collection: specificCollection,
182
180
  customer: customerVariables,
183
- userGroup: (_k = deviceVariables === null || deviceVariables === void 0 ? void 0 : deviceVariables.session) === null || _k === void 0 ? void 0 : _k.userGroup,
184
- storage: deviceVariables === null || deviceVariables === void 0 ? void 0 : deviceVariables.storage,
181
+ // userGroup/storage live at the top level of the SDK variables object
182
+ // (variables.session / variables.storage), as siblings of device and
183
+ // customer — not nested under device.
184
+ userGroup: sessionVariables === null || sessionVariables === void 0 ? void 0 : sessionVariables.userGroup,
185
+ storage: storageVariables,
185
186
  searchParams,
186
187
  });
187
188
  const evaluateV1Conditions = () => {
@@ -216,5 +217,5 @@ export const useBlockConditionalRendering = (_props, _block, mobileComponentOver
216
217
  console.error("Error evaluating block visibility conditions:", e);
217
218
  shouldShow = true; // Fail-safe to show block
218
219
  }
219
- return useMemo(() => ({ shouldShow, isLoading }), [shouldShow, isLoading]);
220
+ return { shouldShow, isLoading };
220
221
  };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-block-conditional-rendering.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-block-conditional-rendering.test.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-block-conditional-rendering.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,112 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ // Controlled by tests; the next/navigation mock reads it on each call.
3
+ let mockSearchParams = new URLSearchParams();
4
+ jest.mock("next/navigation", () => ({
5
+ useSearchParams: () => mockSearchParams,
6
+ }));
7
+ // The hook fetches products/collections via SWR-backed hooks; stub them so no
8
+ // network/fetch happens. userGroup/storage/queryParam paths don't need them.
9
+ jest.mock("./use-products", () => ({
10
+ useProducts: () => ({ products: [], error: undefined, isLoading: false }),
11
+ }));
12
+ jest.mock("./use-collection", () => ({
13
+ useCollection: () => ({
14
+ specificCollection: {},
15
+ error: undefined,
16
+ loading: false,
17
+ }),
18
+ }));
19
+ import { useBlockConditionalRendering } from "./use-block-conditional-rendering";
20
+ const v2Block = (conditions, enabled = true) => ({
21
+ _id: "block-1",
22
+ visibilityConditions: { _version: 2, enabled, conditions },
23
+ });
24
+ const baseProps = {
25
+ appId: "app-1",
26
+ apiUrl: "https://example.test",
27
+ deviceVariables: { locale: "en-US" },
28
+ customerVariables: {},
29
+ sessionVariables: { userGroup: 1 },
30
+ storageVariables: {},
31
+ };
32
+ const run = (props, block) => renderHook(() => useBlockConditionalRendering(props, block, undefined)).result
33
+ .current;
34
+ beforeEach(() => {
35
+ mockSearchParams = new URLSearchParams();
36
+ });
37
+ describe("useBlockConditionalRendering - userGroup", () => {
38
+ const block = v2Block({
39
+ logic: "OR",
40
+ conditions: [{ type: "userGroup", operator: "equals", value: "1" }],
41
+ });
42
+ it("shows the block when the top-level session.userGroup matches", () => {
43
+ // Regression guard: userGroup must be read from sessionVariables
44
+ // (variables.session.userGroup), NOT variables.device.session.userGroup.
45
+ const { shouldShow } = run(Object.assign(Object.assign({}, baseProps), { sessionVariables: { userGroup: 1 } }), block);
46
+ expect(shouldShow).toBe(true);
47
+ });
48
+ it("hides the block when the user is in a different group", () => {
49
+ const { shouldShow } = run(Object.assign(Object.assign({}, baseProps), { sessionVariables: { userGroup: 2 } }), block);
50
+ expect(shouldShow).toBe(false);
51
+ });
52
+ it("hides the block (fails closed) when no session is present", () => {
53
+ const { shouldShow } = run(Object.assign(Object.assign({}, baseProps), { sessionVariables: undefined }), block);
54
+ expect(shouldShow).toBe(false);
55
+ });
56
+ it("does NOT read userGroup from deviceVariables.session", () => {
57
+ // If the wiring regresses to deviceVariables.session.userGroup, this
58
+ // (group on device, not session) would incorrectly show the block.
59
+ const { shouldShow } = run(Object.assign(Object.assign({}, baseProps), { sessionVariables: undefined, deviceVariables: { locale: "en-US", session: { userGroup: 1 } } }), block);
60
+ expect(shouldShow).toBe(false);
61
+ });
62
+ });
63
+ describe("useBlockConditionalRendering - queryParam", () => {
64
+ const block = v2Block({
65
+ logic: "OR",
66
+ conditions: [
67
+ {
68
+ type: "queryParam",
69
+ operator: "equals",
70
+ value: { key: "utm", value: "spring" },
71
+ },
72
+ ],
73
+ });
74
+ it("shows when the query param matches", () => {
75
+ mockSearchParams = new URLSearchParams("utm=spring");
76
+ expect(run(baseProps, block).shouldShow).toBe(true);
77
+ });
78
+ it("hides when the query param does not match", () => {
79
+ mockSearchParams = new URLSearchParams("utm=fall");
80
+ expect(run(baseProps, block).shouldShow).toBe(false);
81
+ });
82
+ });
83
+ describe("useBlockConditionalRendering - storageVariable", () => {
84
+ const block = v2Block({
85
+ logic: "OR",
86
+ conditions: [
87
+ {
88
+ type: "storageVariable",
89
+ operator: "equals",
90
+ value: { key: "promo", value: "active" },
91
+ },
92
+ ],
93
+ });
94
+ it("shows when the storage value matches", () => {
95
+ expect(run(Object.assign(Object.assign({}, baseProps), { storageVariables: { promo: "active" } }), block)
96
+ .shouldShow).toBe(true);
97
+ });
98
+ it("hides when the storage value does not match", () => {
99
+ expect(run(Object.assign(Object.assign({}, baseProps), { storageVariables: { promo: "inactive" } }), block)
100
+ .shouldShow).toBe(false);
101
+ });
102
+ });
103
+ describe("useBlockConditionalRendering - disabled", () => {
104
+ it("shows the block when visibility conditions are disabled", () => {
105
+ const block = v2Block({
106
+ logic: "OR",
107
+ conditions: [{ type: "userGroup", operator: "equals", value: "999" }],
108
+ }, false // enabled: false
109
+ );
110
+ expect(run(baseProps, block).shouldShow).toBe(true);
111
+ });
112
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=conditional-rendering.util.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conditional-rendering.util.test.d.ts","sourceRoot":"","sources":["../../lib/conditional-rendering.util.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,116 @@
1
+ import { evaluateConditions, getEnvState } from "./utils";
2
+ // Pure-function coverage for the v2 block visibility-condition logic:
3
+ // getEnvState (env assembly) and evaluateConditions/evaluateSingleCondition
4
+ // (matching + AND/OR/NOT). evaluateSingleCondition is not exported, so it is
5
+ // exercised through single-condition groups.
6
+ const baseLocation = { country: "US", language: "en", deviceId: "device-1" };
7
+ describe("getEnvState", () => {
8
+ it("maps a numeric userGroup to a string-valued tag", () => {
9
+ const env = getEnvState({ location: baseLocation, userGroup: 1 });
10
+ expect(env.userGroup).toEqual([{ value: "1" }]);
11
+ });
12
+ it("maps a string userGroup as-is", () => {
13
+ const env = getEnvState({ location: baseLocation, userGroup: "7" });
14
+ expect(env.userGroup).toEqual([{ value: "7" }]);
15
+ });
16
+ it("returns an empty userGroup tag list when userGroup is missing", () => {
17
+ expect(getEnvState({ location: baseLocation }).userGroup).toEqual([]);
18
+ expect(getEnvState({ location: baseLocation, userGroup: undefined }).userGroup).toEqual([]);
19
+ });
20
+ it("exposes storage and searchParams as the underscored context keys", () => {
21
+ const params = new URLSearchParams("utm=spring");
22
+ const env = getEnvState({
23
+ location: baseLocation,
24
+ storage: { promo: "active" },
25
+ searchParams: params,
26
+ });
27
+ expect(env._storage).toEqual({ promo: "active" });
28
+ expect(env._searchParams).toBe(params);
29
+ });
30
+ it("defaults storage/searchParams to empty/null", () => {
31
+ const env = getEnvState({ location: baseLocation });
32
+ expect(env._storage).toEqual({});
33
+ expect(env._searchParams).toBeNull();
34
+ });
35
+ });
36
+ describe("evaluateConditions - userGroup", () => {
37
+ const ctx = { userGroup: [{ value: "1" }] };
38
+ const group = (operator, value) => ({
39
+ logic: "OR",
40
+ conditions: [{ type: "userGroup", operator, value }],
41
+ });
42
+ it("matches the user's group with equals", () => {
43
+ expect(evaluateConditions(group("equals", "1"), ctx)).toBe(true);
44
+ expect(evaluateConditions(group("equals", "2"), ctx)).toBe(false);
45
+ });
46
+ it("handles does not equal", () => {
47
+ expect(evaluateConditions(group("does not equal", "2"), ctx)).toBe(true);
48
+ expect(evaluateConditions(group("does not equal", "1"), ctx)).toBe(false);
49
+ });
50
+ it("does not match when no group is present (fails closed)", () => {
51
+ // empty userGroup array (e.g. session.userGroup undefined at runtime)
52
+ expect(evaluateConditions(group("equals", "1"), { userGroup: [] })).toBe(false);
53
+ });
54
+ });
55
+ describe("evaluateConditions - storageVariable", () => {
56
+ const ctx = { _storage: { promo: "active" } };
57
+ const cond = (operator, key, value) => ({
58
+ logic: "OR",
59
+ conditions: [{ type: "storageVariable", operator, value: { key, value } }],
60
+ });
61
+ it("compares the stored value", () => {
62
+ expect(evaluateConditions(cond("equals", "promo", "active"), ctx)).toBe(true);
63
+ expect(evaluateConditions(cond("equals", "promo", "inactive"), ctx)).toBe(false);
64
+ });
65
+ it("treats a missing key as not-equal", () => {
66
+ expect(evaluateConditions(cond("equals", "missing", "x"), ctx)).toBe(false);
67
+ expect(evaluateConditions(cond("does not equal", "missing", "x"), ctx)).toBe(true);
68
+ });
69
+ });
70
+ describe("evaluateConditions - queryParam", () => {
71
+ const ctx = { _searchParams: new URLSearchParams("utm=spring") };
72
+ const cond = (operator, key, value) => ({
73
+ logic: "OR",
74
+ conditions: [{ type: "queryParam", operator, value: { key, value } }],
75
+ });
76
+ it("compares the query param value", () => {
77
+ expect(evaluateConditions(cond("equals", "utm", "spring"), ctx)).toBe(true);
78
+ expect(evaluateConditions(cond("equals", "utm", "fall"), ctx)).toBe(false);
79
+ });
80
+ it("treats a missing param as not-equal", () => {
81
+ expect(evaluateConditions(cond("equals", "missing", "x"), ctx)).toBe(false);
82
+ expect(evaluateConditions(cond("does not equal", "missing", "x"), ctx)).toBe(true);
83
+ });
84
+ });
85
+ describe("evaluateConditions - logic operators", () => {
86
+ const ctx = {
87
+ userGroup: [{ value: "1" }],
88
+ _storage: { promo: "active" },
89
+ };
90
+ const userGroupIs1 = { type: "userGroup", operator: "equals", value: "1" };
91
+ const userGroupIs2 = { type: "userGroup", operator: "equals", value: "2" };
92
+ it("AND requires every condition", () => {
93
+ expect(evaluateConditions({ logic: "AND", conditions: [userGroupIs1, userGroupIs1] }, ctx)).toBe(true);
94
+ expect(evaluateConditions({ logic: "AND", conditions: [userGroupIs1, userGroupIs2] }, ctx)).toBe(false);
95
+ });
96
+ it("OR requires at least one condition", () => {
97
+ expect(evaluateConditions({ logic: "OR", conditions: [userGroupIs2, userGroupIs1] }, ctx)).toBe(true);
98
+ expect(evaluateConditions({ logic: "OR", conditions: [userGroupIs2, userGroupIs2] }, ctx)).toBe(false);
99
+ });
100
+ it("NOT negates an all-true group", () => {
101
+ expect(evaluateConditions({ logic: "NOT", conditions: [userGroupIs1] }, ctx)).toBe(false);
102
+ expect(evaluateConditions({ logic: "NOT", conditions: [userGroupIs2] }, ctx)).toBe(true);
103
+ });
104
+ it("evaluates nested groups", () => {
105
+ expect(evaluateConditions({
106
+ logic: "AND",
107
+ conditions: [
108
+ userGroupIs1,
109
+ { logic: "OR", conditions: [userGroupIs2, userGroupIs1] },
110
+ ],
111
+ }, ctx)).toBe(true);
112
+ });
113
+ it("passes when there are no conditions", () => {
114
+ expect(evaluateConditions({ logic: "AND", conditions: [] }, ctx)).toBe(true);
115
+ });
116
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapcart/mobile-components",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "style": "dist/styles.css",