@zoxllc/shopify-checkout-extensions 0.0.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.
- package/README.md +3 -0
- package/package.json +31 -0
- package/src/common/AndSelector.ts +35 -0
- package/src/common/BuyXGetY.ts +109 -0
- package/src/common/Campaign.ts +55 -0
- package/src/common/CampaignConfiguration.ts +10 -0
- package/src/common/CampaignFactory.ts +180 -0
- package/src/common/CartAmountQualifier.ts +64 -0
- package/src/common/CartHasItemQualifier.ts +64 -0
- package/src/common/CartQuantityQualifier.ts +73 -0
- package/src/common/CustomerEmailQualifier.ts +60 -0
- package/src/common/CustomerSubscriberQualifier.ts +32 -0
- package/src/common/CustomerTagQualifier.ts +56 -0
- package/src/common/DiscountCart.ts +130 -0
- package/src/common/DiscountInterface.ts +13 -0
- package/src/common/OrSelector.ts +35 -0
- package/src/common/PostCartAmountQualifier.ts +21 -0
- package/src/common/ProductHandleSelector.ts +29 -0
- package/src/common/ProductIdSelector.ts +28 -0
- package/src/common/ProductTagSelector.ts +54 -0
- package/src/common/ProductTypeSelector.ts +34 -0
- package/src/common/Qualifier.ts +67 -0
- package/src/common/SaleItemSelector.ts +35 -0
- package/src/common/Selector.ts +53 -0
- package/src/common/SubscriptionItemSelector.ts +21 -0
- package/src/generated/api.ts +2103 -0
- package/src/index.ts +39 -0
- package/src/lineItem/ConditionalDiscount.ts +102 -0
- package/src/lineItem/DiscountCodeList.ts +91 -0
- package/src/lineItem/FixedItemDiscount.ts +53 -0
- package/src/lineItem/PercentageDiscount.ts +46 -0
- package/tests/AndSelector.test.ts +27 -0
- package/tests/CartQuantityQualifier.test.ts +381 -0
- package/tests/CustomerSubscriberQualifier.test.ts +101 -0
- package/tests/DiscountCart.test.ts +115 -0
- package/tests/OrSelector.test.ts +27 -0
- package/tests/ProductTagSelector.test.ts +75 -0
- package/tests/Qualifier.test.ts +193 -0
- package/tests/SaleItemSelector.test.ts +113 -0
- package/tests/Selector.test.ts +83 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
NumericalComparisonType,
|
|
4
|
+
Qualifier,
|
|
5
|
+
} from '../src/common/Qualifier';
|
|
6
|
+
import type { DiscountCart } from '../src/common/DiscountCart';
|
|
7
|
+
import type { CartLine } from '../src/generated/api';
|
|
8
|
+
import type { Selector } from '../src/common/Selector';
|
|
9
|
+
|
|
10
|
+
export class MockTruthyQualifier extends Qualifier {
|
|
11
|
+
match(
|
|
12
|
+
subject: DiscountCart | CartLine,
|
|
13
|
+
selector?: Selector | undefined
|
|
14
|
+
): boolean {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class MockFalseyQualifier extends Qualifier {
|
|
20
|
+
match(
|
|
21
|
+
subject: DiscountCart | CartLine,
|
|
22
|
+
selector?: Selector | undefined
|
|
23
|
+
): boolean {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('Qualifier', () => {
|
|
29
|
+
const qualifier = new Qualifier();
|
|
30
|
+
it('Contains a root compareAmounts method', () => {
|
|
31
|
+
expect(qualifier.compareAmounts).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('Compares amounts properly for all cases', () => {
|
|
35
|
+
// EQUAL_TO
|
|
36
|
+
expect(
|
|
37
|
+
qualifier.compareAmounts(
|
|
38
|
+
10,
|
|
39
|
+
NumericalComparisonType.EQUAL_TO,
|
|
40
|
+
10
|
|
41
|
+
),
|
|
42
|
+
'Must be TRUE when 10 EQUAL_TO 10'
|
|
43
|
+
).toEqual(true);
|
|
44
|
+
expect(
|
|
45
|
+
qualifier.compareAmounts(
|
|
46
|
+
1,
|
|
47
|
+
NumericalComparisonType.EQUAL_TO,
|
|
48
|
+
10
|
|
49
|
+
),
|
|
50
|
+
'Must be FALSE when 1 EQUAL_TO 10'
|
|
51
|
+
).toEqual(false);
|
|
52
|
+
expect(
|
|
53
|
+
qualifier.compareAmounts(
|
|
54
|
+
10,
|
|
55
|
+
NumericalComparisonType.EQUAL_TO,
|
|
56
|
+
1
|
|
57
|
+
),
|
|
58
|
+
'Must be FALSE when 10 EQUAL_TO 1'
|
|
59
|
+
).toEqual(false);
|
|
60
|
+
|
|
61
|
+
// GREATER_THAN
|
|
62
|
+
expect(
|
|
63
|
+
qualifier.compareAmounts(
|
|
64
|
+
11,
|
|
65
|
+
NumericalComparisonType.GREATER_THAN,
|
|
66
|
+
10
|
|
67
|
+
),
|
|
68
|
+
'Must be TRUE when 11 GREATER_THAN 10'
|
|
69
|
+
).toEqual(true);
|
|
70
|
+
expect(
|
|
71
|
+
qualifier.compareAmounts(
|
|
72
|
+
1,
|
|
73
|
+
NumericalComparisonType.GREATER_THAN,
|
|
74
|
+
10
|
|
75
|
+
),
|
|
76
|
+
'Must be FALSE when 1 GREATER_THAN 10'
|
|
77
|
+
).toEqual(false);
|
|
78
|
+
expect(
|
|
79
|
+
qualifier.compareAmounts(
|
|
80
|
+
10,
|
|
81
|
+
NumericalComparisonType.GREATER_THAN,
|
|
82
|
+
1
|
|
83
|
+
),
|
|
84
|
+
'Must be TRUE when 10 GREATER_THAN 1'
|
|
85
|
+
).toEqual(true);
|
|
86
|
+
|
|
87
|
+
// GREATER_THAN_OR_EQUAL
|
|
88
|
+
expect(
|
|
89
|
+
qualifier.compareAmounts(
|
|
90
|
+
11,
|
|
91
|
+
NumericalComparisonType.GREATER_THAN_OR_EQUAL,
|
|
92
|
+
10
|
|
93
|
+
),
|
|
94
|
+
'Must be TRUE when 11 GREATER_THAN_OR_EQUAL 10'
|
|
95
|
+
).toEqual(true);
|
|
96
|
+
expect(
|
|
97
|
+
qualifier.compareAmounts(
|
|
98
|
+
10,
|
|
99
|
+
NumericalComparisonType.GREATER_THAN_OR_EQUAL,
|
|
100
|
+
10
|
|
101
|
+
),
|
|
102
|
+
'Must be TRUE when 10 GREATER_THAN_OR_EQUAL 10'
|
|
103
|
+
).toEqual(true);
|
|
104
|
+
expect(
|
|
105
|
+
qualifier.compareAmounts(
|
|
106
|
+
1,
|
|
107
|
+
NumericalComparisonType.GREATER_THAN_OR_EQUAL,
|
|
108
|
+
10
|
|
109
|
+
),
|
|
110
|
+
'Must be FALSE when 1 GREATER_THAN_OR_EQUAL 10'
|
|
111
|
+
).toEqual(false);
|
|
112
|
+
expect(
|
|
113
|
+
qualifier.compareAmounts(
|
|
114
|
+
10,
|
|
115
|
+
NumericalComparisonType.GREATER_THAN_OR_EQUAL,
|
|
116
|
+
1
|
|
117
|
+
),
|
|
118
|
+
'Must be TRUE when 10 GREATER_THAN_OR_EQUAL 1'
|
|
119
|
+
).toEqual(true);
|
|
120
|
+
|
|
121
|
+
// LESS_THAN
|
|
122
|
+
expect(
|
|
123
|
+
qualifier.compareAmounts(
|
|
124
|
+
11,
|
|
125
|
+
NumericalComparisonType.LESS_THAN,
|
|
126
|
+
10
|
|
127
|
+
),
|
|
128
|
+
'Must be FALSE when 11 LESS_THAN 10'
|
|
129
|
+
).toEqual(false);
|
|
130
|
+
expect(
|
|
131
|
+
qualifier.compareAmounts(
|
|
132
|
+
1,
|
|
133
|
+
NumericalComparisonType.LESS_THAN,
|
|
134
|
+
10
|
|
135
|
+
),
|
|
136
|
+
'Must be TRUE when 1 LESS_THAN 10'
|
|
137
|
+
).toEqual(true);
|
|
138
|
+
expect(
|
|
139
|
+
qualifier.compareAmounts(
|
|
140
|
+
10,
|
|
141
|
+
NumericalComparisonType.LESS_THAN,
|
|
142
|
+
1
|
|
143
|
+
),
|
|
144
|
+
'Must be FALSE when 10 LESS_THAN 1'
|
|
145
|
+
).toEqual(false);
|
|
146
|
+
expect(
|
|
147
|
+
qualifier.compareAmounts(
|
|
148
|
+
1,
|
|
149
|
+
NumericalComparisonType.LESS_THAN,
|
|
150
|
+
1
|
|
151
|
+
),
|
|
152
|
+
'Must be FALSE when 1 LESS_THAN 1'
|
|
153
|
+
).toEqual(false);
|
|
154
|
+
|
|
155
|
+
// LESS_THAN_OR_EQUAL
|
|
156
|
+
expect(
|
|
157
|
+
qualifier.compareAmounts(
|
|
158
|
+
11,
|
|
159
|
+
NumericalComparisonType.LESS_THAN_OR_EQUAL,
|
|
160
|
+
10
|
|
161
|
+
),
|
|
162
|
+
'Must be FALSE when 11 LESS_THAN_OR_EQUAL 10'
|
|
163
|
+
).toEqual(false);
|
|
164
|
+
expect(
|
|
165
|
+
qualifier.compareAmounts(
|
|
166
|
+
10,
|
|
167
|
+
NumericalComparisonType.LESS_THAN_OR_EQUAL,
|
|
168
|
+
10
|
|
169
|
+
),
|
|
170
|
+
'Must be TRUE when 10 LESS_THAN_OR_EQUAL 10'
|
|
171
|
+
).toEqual(true);
|
|
172
|
+
expect(
|
|
173
|
+
qualifier.compareAmounts(
|
|
174
|
+
1,
|
|
175
|
+
NumericalComparisonType.LESS_THAN_OR_EQUAL,
|
|
176
|
+
10
|
|
177
|
+
),
|
|
178
|
+
'Must be TRUE when 1 LESS_THAN_OR_EQUAL 10'
|
|
179
|
+
).toEqual(true);
|
|
180
|
+
expect(
|
|
181
|
+
qualifier.compareAmounts(
|
|
182
|
+
10,
|
|
183
|
+
NumericalComparisonType.LESS_THAN_OR_EQUAL,
|
|
184
|
+
1
|
|
185
|
+
),
|
|
186
|
+
'Must be FALSE when 10 LESS_THAN_OR_EQUAL 1'
|
|
187
|
+
).toEqual(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('Will always return false when calling match()', () => {
|
|
191
|
+
expect(qualifier.match({} as DiscountCart)).toBeFalsy();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
CartLine,
|
|
4
|
+
CurrencyCode,
|
|
5
|
+
WeightUnit,
|
|
6
|
+
} from '../src/generated/api';
|
|
7
|
+
import _ from 'lodash';
|
|
8
|
+
import {
|
|
9
|
+
SaleItemSelector,
|
|
10
|
+
SaleItemSelectorMatchType,
|
|
11
|
+
} from '../src/common/SaleItemSelector';
|
|
12
|
+
|
|
13
|
+
describe('SaleItemSelector', () => {
|
|
14
|
+
it('returns true when item is on sale and we pass :IS', () => {
|
|
15
|
+
const isSelector = new SaleItemSelector(
|
|
16
|
+
SaleItemSelectorMatchType.IS
|
|
17
|
+
);
|
|
18
|
+
const isResult = isSelector.match(lineItemOnSale);
|
|
19
|
+
expect(isResult).toEqual(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns false when item is on sale and we pass :NOT', () => {
|
|
23
|
+
const notSelector = new SaleItemSelector(
|
|
24
|
+
SaleItemSelectorMatchType.NOT
|
|
25
|
+
);
|
|
26
|
+
const notResult = notSelector.match(lineItemOnSale);
|
|
27
|
+
expect(notResult).toEqual(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('returns false when item is NOT on sale and we pass :IS', () => {
|
|
31
|
+
const isSelector = new SaleItemSelector(
|
|
32
|
+
SaleItemSelectorMatchType.IS
|
|
33
|
+
);
|
|
34
|
+
const isResult = isSelector.match(lineItemNotOnSale);
|
|
35
|
+
expect(isResult).toEqual(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns true when :NOT matchtype passed and item is NOT on sale', () => {
|
|
39
|
+
const notSelector = new SaleItemSelector(
|
|
40
|
+
SaleItemSelectorMatchType.NOT
|
|
41
|
+
);
|
|
42
|
+
const notResult = notSelector.match(lineItemNotOnSale);
|
|
43
|
+
expect(notResult).toEqual(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const lineItemOnSale: CartLine = {
|
|
47
|
+
id: 'gid://shopify/CartLine/111111',
|
|
48
|
+
cost: {
|
|
49
|
+
amountPerQuantity: {
|
|
50
|
+
amount: '8.0',
|
|
51
|
+
currencyCode: CurrencyCode.Usd,
|
|
52
|
+
},
|
|
53
|
+
subtotalAmount: {
|
|
54
|
+
amount: '64.0',
|
|
55
|
+
currencyCode: CurrencyCode.Usd,
|
|
56
|
+
},
|
|
57
|
+
totalAmount: {
|
|
58
|
+
amount: '64.0',
|
|
59
|
+
currencyCode: CurrencyCode.Usd,
|
|
60
|
+
},
|
|
61
|
+
compareAtAmountPerQuantity: {
|
|
62
|
+
amount: '10.0',
|
|
63
|
+
currencyCode: CurrencyCode.Usd,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
quantity: 8,
|
|
67
|
+
merchandise: {
|
|
68
|
+
__typename: 'ProductVariant',
|
|
69
|
+
id: 'gid://shopify/ProductVariant/43900890579178',
|
|
70
|
+
product: {
|
|
71
|
+
title: 'Wristband Product',
|
|
72
|
+
handle: 'wristband-product',
|
|
73
|
+
id: 'gid://shopify/Product/8069766840554',
|
|
74
|
+
productType: 'Wristband - Single',
|
|
75
|
+
inAnyCollection: false,
|
|
76
|
+
inCollections: [],
|
|
77
|
+
hasAnyTag: false,
|
|
78
|
+
isGiftCard: false,
|
|
79
|
+
hasTags: [
|
|
80
|
+
{
|
|
81
|
+
tag: 'meta-exclude-gift',
|
|
82
|
+
hasTag: false,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
tag: 'feel-good-mystery-3-pack',
|
|
86
|
+
hasTag: true,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
requiresShipping: false,
|
|
91
|
+
weightUnit: WeightUnit.Pounds,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const lineItemNotOnSale = _.cloneDeep(lineItemOnSale);
|
|
96
|
+
_.merge(lineItemNotOnSale, {
|
|
97
|
+
cost: {
|
|
98
|
+
amountPerQuantity: {
|
|
99
|
+
amount: '8.0',
|
|
100
|
+
currencyCode: CurrencyCode.Usd,
|
|
101
|
+
},
|
|
102
|
+
subtotalAmount: {
|
|
103
|
+
amount: '64.0',
|
|
104
|
+
currencyCode: CurrencyCode.Usd,
|
|
105
|
+
},
|
|
106
|
+
totalAmount: {
|
|
107
|
+
amount: '64.0',
|
|
108
|
+
currencyCode: CurrencyCode.Usd,
|
|
109
|
+
},
|
|
110
|
+
compareAtAmountPerQuantity: null,
|
|
111
|
+
},
|
|
112
|
+
} as CartLine);
|
|
113
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type { CartLine } from '../src/generated/api';
|
|
3
|
+
import { Selector } from '../src/common/Selector';
|
|
4
|
+
import { StringComparisonType } from '../src/common/Qualifier';
|
|
5
|
+
|
|
6
|
+
export enum MatchType {
|
|
7
|
+
'ALL' = ':all',
|
|
8
|
+
'IS_ONE' = ':is_one',
|
|
9
|
+
'NOT_ONE' = ':not_one',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('Selector', () => {
|
|
13
|
+
it('always returns true when calling match method', () => {
|
|
14
|
+
const selector = new Selector();
|
|
15
|
+
expect(selector.match({} as CartLine)).toBeTruthy();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('is true when part of the string is present', () => {
|
|
19
|
+
const selector = new Selector();
|
|
20
|
+
const result = selector.partialMatch(
|
|
21
|
+
StringComparisonType.CONTAINS,
|
|
22
|
+
['tag1', 'tag3'],
|
|
23
|
+
['ag', 'a']
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(result).toEqual(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('is false when part of the string is not present', () => {
|
|
30
|
+
const selector = new Selector();
|
|
31
|
+
const result = selector.partialMatch(
|
|
32
|
+
StringComparisonType.CONTAINS,
|
|
33
|
+
['tag1', 'tag3'],
|
|
34
|
+
['sandwich', 'marshmallow']
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(result).toEqual(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('is true when it starts with a string', () => {
|
|
41
|
+
const selector = new Selector();
|
|
42
|
+
const result = selector.partialMatch(
|
|
43
|
+
StringComparisonType.START_WITH,
|
|
44
|
+
['tag1', 'tag3'],
|
|
45
|
+
['ta', 'tag']
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(result).toEqual(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('is false when it does NOT start with that string', () => {
|
|
52
|
+
const selector = new Selector();
|
|
53
|
+
const result = selector.partialMatch(
|
|
54
|
+
StringComparisonType.START_WITH,
|
|
55
|
+
['tag1', 'tag3'],
|
|
56
|
+
['ag1', 'ag3']
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(result).toEqual(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('is true when it ends with a string', () => {
|
|
63
|
+
const selector = new Selector();
|
|
64
|
+
const result = selector.partialMatch(
|
|
65
|
+
StringComparisonType.END_WITH,
|
|
66
|
+
['tag1', 'tag3'],
|
|
67
|
+
['ag3', 'coconuts']
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('is false when it does NOT end with that string', () => {
|
|
74
|
+
const selector = new Selector();
|
|
75
|
+
const result = selector.partialMatch(
|
|
76
|
+
StringComparisonType.END_WITH,
|
|
77
|
+
['tag1', 'tag3'],
|
|
78
|
+
['ta', 'tag']
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(result).toEqual(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"lib": ["DOM", "DOM.Iterable", "ES2019"],
|
|
5
|
+
"strict": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"removeComments": false,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"allowJs": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"module": "Node16",
|
|
15
|
+
"moduleResolution": "Node16",
|
|
16
|
+
"target": "ES2019",
|
|
17
|
+
"baseUrl": ".",
|
|
18
|
+
"paths": {
|
|
19
|
+
"~/*": ["./src/*"]
|
|
20
|
+
},
|
|
21
|
+
"types": [
|
|
22
|
+
"node", "@shopify/app-bridge-types"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|