@ordergroove/offers 2.27.16 → 2.27.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ordergroove/offers",
3
- "version": "2.27.16",
3
+ "version": "2.27.17",
4
4
  "description": "offer state component",
5
5
  "author": "Eugenio Lattanzio <eugenio63@gmail.com>",
6
6
  "homepage": "https://github.com/ordergroove/plush-toys#readme",
@@ -45,5 +45,5 @@
45
45
  "devDependencies": {
46
46
  "@ordergroove/offers-templates": "^0.4.15"
47
47
  },
48
- "gitHead": "9d55bfe3c5bac0e80acbe1d6bfdddc8ecf222842"
48
+ "gitHead": "982c709e906d0fe6f151bdd71d194363af85b60d"
49
49
  }
@@ -107,9 +107,6 @@ export class IncentiveText extends withProduct(LitElement) {
107
107
  }
108
108
 
109
109
  renderFallback() {
110
- const incentiveClass = this.from;
111
- const incentiveValue = this.value;
112
- const incentiveType = this.initial ? 'initial' : 'ongoing';
113
110
  return html`
114
111
  ${discountBuilder({
115
112
  field: 'sub_total',
@@ -250,7 +250,7 @@ export class Offer extends TemplateElement {
250
250
  }
251
251
  this.frequency = this.defaultFrequency;
252
252
 
253
- if (changed.has('product') && this.product.id && !this.isPreview) {
253
+ if (changed.has('product') && !this.isPreview) {
254
254
  onReady(() => this.fetchOffer(this.product.id, DEFAULT_OFFER_MODULE, this));
255
255
  }
256
256
 
@@ -89,10 +89,17 @@ export class SelectFrequency extends withChildOptions(FrequencyStatus) {
89
89
  const defaultFrequency = this.defaultFrequency;
90
90
 
91
91
  if (this.frequencies?.length) {
92
- options = this.frequencies.map((value, ix) => ({
93
- value,
94
- text: frequencyText(this.config.frequenciesEveryPeriod[ix], defaultFrequency)
95
- }));
92
+ options = this.frequencies.map((value, ix) => {
93
+ let text;
94
+ if (this.config.frequenciesEveryPeriod && ix in this.config.frequenciesEveryPeriod) {
95
+ text = frequencyText(this.config.frequenciesEveryPeriod[ix], defaultFrequency);
96
+ } else if (this.frequenciesText && ix in this.frequenciesText) {
97
+ text = this.frequenciesText[ix];
98
+ } else {
99
+ text = frequencyText(value, this.defaultFrequency);
100
+ }
101
+ return { value, text };
102
+ });
96
103
  } else {
97
104
  ({ options } = this.childOptions);
98
105
  }
@@ -31,9 +31,8 @@ export class When extends withProduct(LitElement) {
31
31
  shouldUpdate(changedProperties) {
32
32
  return (
33
33
  changedProperties.size &&
34
- this.product &&
35
- this.product.id in this.state.autoshipEligible &&
36
- this.product.id in this.state.inStock
34
+ ((this.product && this.product.id in this.state.autoshipEligible && this.product.id in this.state.inStock) ||
35
+ !this.product.id)
37
36
  );
38
37
  }
39
38
  }
@@ -1,4 +1,44 @@
1
1
  const og = window.og;
2
+
3
+ async function simulateChange(element, value) {
4
+ const evt = new Event('change', { bubbles: true });
5
+ element.value = value;
6
+ element.dispatchEvent(evt, { target: { value } });
7
+ await new Promise(r => setTimeout(r, 1));
8
+ }
9
+
10
+ const expectToBeVisible = offer => {
11
+ const { x, y, width, height } = offer.getClientRects()[0];
12
+ expect(height).toBeGreaterThan(30);
13
+ };
14
+ const expectNotToBeVisible = offer => {
15
+ const { x, y, width, height } = offer.getClientRects()[0];
16
+ expect(height).toBeLessThan(30);
17
+ };
18
+ const waitUpdate = async offer => {
19
+ await offer.updateComplete;
20
+ await new Promise(r => setTimeout(r, 10));
21
+ };
22
+ const mockOfferResponse = (productId, inStock = true, autoship = true) => {
23
+ return Promise.resolve({
24
+ json() {
25
+ return Promise.resolve({
26
+ in_stock: { [productId]: inStock },
27
+ eligibility_groups: { [productId]: ['subscription', 'upsell'] },
28
+ result: 'success',
29
+ autoship: { [productId]: autoship },
30
+ autoship_by_default: { [productId]: false },
31
+ modifiers: {},
32
+ module_view: { regular: '096135e6650111e9a444bc764e106cf4' },
33
+ incentives_display: {},
34
+ incentives: {
35
+ [productId]: { initial: [], ongoing: [] }
36
+ }
37
+ });
38
+ }
39
+ });
40
+ };
41
+
2
42
  describe('og.offers', function() {
3
43
  it('should define og namespace', () => {
4
44
  expect(og).toEqual(jasmine.any(Object));
@@ -33,3 +73,141 @@ describe('og.offers', function() {
33
73
  expect(og.offers).toEqual(api);
34
74
  });
35
75
  });
76
+
77
+ describe('Offer', function() {
78
+ let productIdNotInStock;
79
+ let productIdInStock;
80
+ let fetch;
81
+
82
+ beforeEach(async () => {
83
+ fetch = spyOn(window, 'fetch');
84
+ productIdNotInStock = `oos${Math.random()
85
+ .toString(36)
86
+ .substring(2, 10)}`;
87
+ productIdInStock = `s-${Math.random()
88
+ .toString(36)
89
+ .substring(2, 10)}`;
90
+ });
91
+
92
+ it('should show offer when instock and eligible', async () => {
93
+ fetch.and.returnValue(mockOfferResponse(productIdInStock, true, true));
94
+ document.body.innerHTML = `
95
+ <og-offer product="${productIdInStock}">
96
+ <og-select-frequency default-text=" (recomended)">
97
+ <option value="optedOut">Buy one time</option>
98
+ <option value="2w" selected="selected">2 weeks</option>
99
+ <option value="1m">1 month </option>
100
+ </og-select-frequency>
101
+ </og-offer>
102
+ `;
103
+ const offer = document.querySelector('og-offer');
104
+ await waitUpdate(offer);
105
+ expect(fetch).toHaveBeenCalledTimes(1);
106
+ expectToBeVisible(document.querySelector('og-offer'));
107
+ });
108
+
109
+ it('should not show offer when not in stock', async () => {
110
+ fetch.and.returnValue(mockOfferResponse(productIdNotInStock, false, false));
111
+ document.body.innerHTML = `
112
+ <og-offer product="${productIdNotInStock}">
113
+ <og-when test="regularEligible">
114
+ <og-select-frequency default-text=" (recomended)">
115
+ <option value="optedOut">Buy one time</option>
116
+ <option value="2w" selected="selected">2 weeks</option>
117
+ <option value="1m">1 month </option>
118
+ </og-select-frequency>
119
+ </og-when>
120
+ </og-offer>
121
+ `;
122
+ const offer = document.querySelector('og-offer');
123
+ await waitUpdate(offer);
124
+ expect(fetch).toHaveBeenCalledTimes(1);
125
+ expectNotToBeVisible(document.querySelector('og-offer'));
126
+ });
127
+
128
+ it('should show hide offer based on variants in stock or oos', async () => {
129
+ fetch.and.returnValue(mockOfferResponse(productIdNotInStock, false, true));
130
+ document.body.innerHTML = `
131
+ <og-offer product="${productIdNotInStock}">
132
+ <og-when test="regularEligible">
133
+ <og-select-frequency default-text=" (recomended)">
134
+ <option value="optedOut">Buy one time</option>
135
+ <option value="2w" selected="selected">2 weeks</option>
136
+ <option value="1m">1 month </option>
137
+ </og-select-frequency>
138
+ </og-when>
139
+ </og-offer>
140
+ `;
141
+ const offer = document.querySelector('og-offer');
142
+
143
+ await waitUpdate(offer);
144
+ expect(fetch).toHaveBeenCalledTimes(1);
145
+ expectNotToBeVisible(offer);
146
+
147
+ fetch.and.returnValue(mockOfferResponse(productIdInStock, true, true));
148
+ offer.setAttribute('product', productIdInStock);
149
+ await waitUpdate(offer);
150
+ expect(fetch).toHaveBeenCalledTimes(2);
151
+ expectToBeVisible(offer);
152
+ });
153
+
154
+ it('should hide when no product id', async () => {
155
+ fetch.and.returnValue(mockOfferResponse(productIdInStock, true, true));
156
+ document.body.innerHTML = `
157
+ <og-offer product="${productIdInStock}">
158
+ <og-when test="regularEligible">
159
+ <og-select-frequency default-text=" (recomended)">
160
+ <option value="optedOut">Buy one time</option>
161
+ <option value="2w" selected="selected">2 weeks</option>
162
+ <option value="1m">1 month </option>
163
+ </og-select-frequency>
164
+ </og-when>
165
+ </og-offer>
166
+ `;
167
+ const offer = document.querySelector('og-offer');
168
+ await waitUpdate(offer);
169
+ expect(fetch).toHaveBeenCalledTimes(1);
170
+ expectToBeVisible(offer);
171
+
172
+ offer.removeAttribute('product');
173
+ await waitUpdate(offer);
174
+ expectNotToBeVisible(offer);
175
+ });
176
+
177
+ describe('Select Frequency', function() {
178
+ let element;
179
+ beforeEach(async () => {
180
+ fetch.and.returnValue(mockOfferResponse(productIdInStock));
181
+ document.body.innerHTML = `
182
+ <og-offer product="${productIdInStock}">
183
+ <og-select-frequency default-text=" (recomended)">
184
+ <option value="optedOut">Buy one time</option>
185
+ <option value="2_2" selected="selected">2 weeks</option>
186
+ <option value="1_3">1 month </option>
187
+ </og-select-frequency>
188
+ </og-offer>
189
+ `;
190
+ element = document.querySelector('og-select-frequency');
191
+ await element.updateComplete;
192
+ await new Promise(r => setTimeout(r, 10));
193
+ expect(fetch).toHaveBeenCalledTimes(1);
194
+ });
195
+
196
+ it('it should have default frequency as value', async () => {
197
+ const htmlSelectElement = element.shadowRoot.querySelector('og-select').shadowRoot.querySelector('select');
198
+ expect(htmlSelectElement.value).toEqual('2_2');
199
+ });
200
+
201
+ it('should append recomended text to default frequency', async () => {
202
+ const htmlSelectElement = element.shadowRoot.querySelector('og-select').shadowRoot.querySelector('select');
203
+ expect(htmlSelectElement.innerText).toEqual('Buy one time\n2 weeks (recomended)\n1 month');
204
+ });
205
+
206
+ it('should not append recomended text to clicked frequency', async () => {
207
+ const htmlSelectElement = element.shadowRoot.querySelector('og-select').shadowRoot.querySelector('select');
208
+ await simulateChange(htmlSelectElement, '1_3');
209
+ expect(htmlSelectElement.value).toEqual('1_3');
210
+ expect(htmlSelectElement.innerText).toEqual('Buy one time\n2 weeks (recomended)\n1 month');
211
+ });
212
+ });
213
+ });
@@ -197,7 +197,7 @@ export const fetchOffer = (product, module = constants.DEFAULT_OFFER_MODULE, off
197
197
  dispatch(requestAction);
198
198
 
199
199
  const productId = safeProductId(product);
200
-
200
+ if (!productId) return null;
201
201
  return api
202
202
  .fetchOffer(apiUrl, merchantId, sessionId, productId, module)
203
203
  .then(
@@ -31,7 +31,7 @@ export const upcomingOrderContainsProduct = (state, ownProps) =>
31
31
  */
32
32
  export const upsellEligible = (state, ownProps) =>
33
33
  // don't show IU in cart offers
34
- !ownProps.offer.isCart &&
34
+ !ownProps.offer?.isCart &&
35
35
  state.offerId &&
36
36
  state.offerId !== '0' &&
37
37
  state.auth &&
@@ -111,6 +111,9 @@ export const autoshipEligible = (state = {}, action) => {
111
111
 
112
112
  export const inStock = (state = {}, action) => {
113
113
  switch (action.type) {
114
+ // force offer to refresh when requesting a new one
115
+ case constants.REQUEST_OFFER:
116
+ return { ...state };
114
117
  case constants.RECEIVE_OFFER:
115
118
  return {
116
119
  ...state,
@@ -383,6 +386,7 @@ export const config = (
383
386
  defaultFrequency: action.payload.defaultFrequency
384
387
  ? stringifyFrequency(action.payload.defaultFrequency)
385
388
  : state.defaultFrequency,
389
+ frequenciesEveryPeriod: [],
386
390
  frequencies: action.payload.frequencies ? action.payload.frequencies.map(stringifyFrequency) : state.frequencies
387
391
  };
388
392
  default:
package/src/core/utils.ts CHANGED
@@ -36,6 +36,7 @@ export function resolveEnvAndMerchant() {
36
36
  }
37
37
 
38
38
  export const safeProductId = product => {
39
+ if (!product ) return '';
39
40
  let productId = `${product.id || product}`;
40
41
  if (platform?.shopify_selling_plans) {
41
42
  // we can't avoid make offer request since we need to know the upsell group and autoship by default
@@ -2,3 +2,4 @@ window.OG_SAVE_TO_LOCAL_TIMEOUT = 1;
2
2
  window.merchantId = '12345678901234567890123456789012';
3
3
  window.env = 'staging';
4
4
  window.og.offers.initialize(window.merchantId, window.env, 0);
5
+ localStorage.clear();
@@ -111,7 +111,8 @@ export function textToFreq(text) {
111
111
  export const config = (
112
112
  state = {
113
113
  frequencies: [],
114
- offerType: 'radio'
114
+ offerType: 'radio',
115
+ frequenciesEveryPeriod: []
115
116
  },
116
117
  action
117
118
  ) => {
@@ -168,6 +169,10 @@ export const inStock = (state = {}, action) => {
168
169
 
169
170
  return [product, ...product?.variants]?.reduce(productOrVariantInStockReducer, state) || state;
170
171
  }
172
+ // force offer to refresh when requesting a new one
173
+ if (constants.REQUEST_OFFER === action.type && action.payload.product === null) {
174
+ return { ...state };
175
+ }
171
176
 
172
177
  return state;
173
178
  };
@@ -1,43 +0,0 @@
1
- const og = window.og;
2
-
3
- async function simulateChange(element, value) {
4
- const evt = new Event('change', { bubbles: true });
5
- element.value = value;
6
- element.dispatchEvent(evt, { target: { value } });
7
- await new Promise(r => setTimeout(r, 1));
8
- }
9
-
10
- describe('Select Frequency', function() {
11
- let element;
12
- beforeEach(async () => {
13
- og.offers.clear();
14
- document.body.innerHTML = `
15
- <og-offer product="123" preview-standard-offer>
16
- <og-select-frequency default-text=" (recomended)">
17
- <option value="optedOut">Buy one time</option>
18
- <option value="2w" selected="selected">2 weeks</option>
19
- <option value="1m">1 month </option>
20
- </og-select-frequency>
21
- </og-offer>
22
- `;
23
- element = document.querySelector('og-select-frequency');
24
- await element.updateComplete;
25
- });
26
-
27
- it('it should have default frequency as value', async () => {
28
- const htmlSelectElement = element.shadowRoot.querySelector('og-select').shadowRoot.querySelector('select');
29
- expect(htmlSelectElement.value).toEqual('2_2');
30
- });
31
-
32
- it('should append recomended text to default frequency', async () => {
33
- const htmlSelectElement = element.shadowRoot.querySelector('og-select').shadowRoot.querySelector('select');
34
- expect(htmlSelectElement.innerText).toEqual('Buy one time\n2 weeks (recomended)\n1 month');
35
- });
36
-
37
- it('should not append recomended text to clicked frequency', async () => {
38
- const htmlSelectElement = element.shadowRoot.querySelector('og-select').shadowRoot.querySelector('select');
39
- await simulateChange(htmlSelectElement, '1_3');
40
- expect(htmlSelectElement.value).toEqual('1_3');
41
- expect(htmlSelectElement.innerText).toEqual('Buy one time\n2 weeks (recomended)\n1 month');
42
- });
43
- });
@@ -1,44 +0,0 @@
1
- const og = window.og;
2
-
3
- async function simulateChange(element, value) {
4
- const evt = new Event('change', { bubbles: true });
5
- element.value = value;
6
- element.dispatchEvent(evt, { target: { value } });
7
- await new Promise(r => setTimeout(r, 1));
8
- }
9
-
10
- describe('Select Frequency', function() {
11
- let element;
12
- beforeEach(async () => {
13
- og.offers.clear();
14
- document.body.innerHTML = `
15
- <og-offer product="123" preview-standard-offer>
16
- <og-select-frequency default-text=" (recomended)">
17
- <option value="optedOut">Buy one time</option>
18
- <option value="2w" selected="selected">2 weeks</option>
19
- <option value="1m">1 month </option>
20
- </og-select-frequency>
21
- </og-offer>
22
- `;
23
- element = document.querySelector('og-select-frequency');
24
- await element.updateComplete;
25
- await new Promise(r => setTimeout(r, 1000));
26
- });
27
-
28
- it('it should have default frequency as value', async () => {
29
- const htmlSelectElement = element.shadowRoot.querySelector('og-select').shadowRoot.querySelector('select');
30
- expect(htmlSelectElement.value).toEqual('2_2');
31
- });
32
-
33
- it('should append recomended text to default frequency', async () => {
34
- const htmlSelectElement = element.shadowRoot.querySelector('og-select').shadowRoot.querySelector('select');
35
- expect(htmlSelectElement.innerText).toEqual('Buy one time\n2 weeks (recomended)\n1 month');
36
- });
37
-
38
- it('should not append recomended text to clicked frequency', async () => {
39
- const htmlSelectElement = element.shadowRoot.querySelector('og-select').shadowRoot.querySelector('select');
40
- await simulateChange(htmlSelectElement, '1_3');
41
- expect(htmlSelectElement.value).toEqual('1_3');
42
- expect(htmlSelectElement.innerText).toEqual('Buy one time\n2 weeks (recomended)\n1 month');
43
- });
44
- });