@ordergroove/offers 2.26.2 → 2.26.3-alpha-PR-593-20.20

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.
Files changed (48) hide show
  1. package/build.js +3 -1
  2. package/dist/bundle-report.html +170 -104
  3. package/dist/offers.js +64 -75
  4. package/dist/offers.js.map +3 -3
  5. package/examples/cart.js +105 -0
  6. package/examples/index.html +2 -2
  7. package/examples/products/cheap-watch.js +183 -0
  8. package/examples/shopify-cart.html +26 -0
  9. package/examples/shopify-pdp.html +34 -0
  10. package/karma.conf.js +2 -1
  11. package/package.json +4 -4
  12. package/src/__tests__/offers.spec.js +35 -10
  13. package/src/components/FrequencyStatus.js +14 -11
  14. package/src/components/Offer.js +11 -7
  15. package/src/components/OptinButton.js +1 -1
  16. package/src/components/OptinSelect.js +2 -2
  17. package/src/components/OptinToggle.js +2 -2
  18. package/src/components/OptoutButton.js +1 -1
  19. package/src/components/Price.js +8 -4
  20. package/src/components/Select.js +3 -13
  21. package/src/components/SelectFrequency.js +24 -6
  22. package/src/components/TestWizard.js +1 -1
  23. package/src/components/__tests__/OG.fspec.js +24 -0
  24. package/src/components/__tests__/Offer.spec.js +4 -4
  25. package/src/components/__tests__/OptinButton.spec.js +2 -2
  26. package/src/components/__tests__/OptinToggle.spec.js +2 -2
  27. package/src/components/__tests__/OptoutButton.spec.js +1 -1
  28. package/src/components/__tests__/SelectFrequency.fspec.js +1 -0
  29. package/src/components/__tests__/SelectFrequency.spec.js +1 -1
  30. package/src/components/__tests__/TestWizard.spec.js +2 -2
  31. package/src/components/__tests__/Text.spec.js +3 -0
  32. package/src/core/__tests__/actions.spec.js +6 -6
  33. package/src/core/actions.js +12 -10
  34. package/src/core/constants.js +3 -0
  35. package/src/core/reducer.js +14 -14
  36. package/src/core/resolveProperties.js +2 -7
  37. package/src/core/selectors.js +1 -1
  38. package/src/core/store.js +6 -5
  39. package/src/index.js +57 -202
  40. package/src/make-api.js +190 -0
  41. package/src/platform.ts +5 -0
  42. package/src/shopify/__tests__/shopifyReducer.spec.js +477 -0
  43. package/src/shopify/shopifyMiddleware.ts +202 -0
  44. package/src/shopify/shopifyReducer.js +214 -0
  45. package/tsconfig.json +35 -0
  46. package/examples/5starnutrition-main.js +0 -3
  47. package/examples/single-offer.html +0 -9
  48. package/src/init-test.js +0 -3
@@ -35,14 +35,7 @@ export class Select extends LitElement {
35
35
  select::-ms-expand {
36
36
  display: none;
37
37
  }
38
- select:hover {
39
- // border-color: #888;
40
- }
41
38
  select:focus {
42
- // border-color: #aaa;
43
- // box-shadow: 0 0 1px 3px rgba(59, 153, 252, 0.7);
44
- // box-shadow: 0 0 0 3px -moz-mac-focusring;
45
- // color: #222;
46
39
  outline: none;
47
40
  }
48
41
  select option {
@@ -72,19 +65,16 @@ export class Select extends LitElement {
72
65
  }
73
66
 
74
67
  render() {
68
+ const handleOnChange = ev => this.onChange(ev);
75
69
  return html`
76
- <select @change="${this.onChange}" .value="${this.selected}">
70
+ <select @change="${handleOnChange}">
77
71
  ${this.options.map(
78
72
  option => html`
79
- <option value="${option.value}" ?selected=${this.selected === option.value}>${option.text}</option>
73
+ <option value="${option.value}" ?selected=${option.value === this.selected}>${option.text}</option>
80
74
  `
81
75
  )}
82
76
  </select>
83
77
  <span>&#9660;</span>
84
- <!-- <svg xmlns="http://www.w3.org/2000/svg" width="36" height="100%" viewBox="0 0 36 36">
85
- <path d="M10.5 15l7.5 7.5 7.5-7.5z" />
86
- <path d="M0 0h36v36h-36z" fill="none" />
87
- </svg> -->
88
78
  `;
89
79
  }
90
80
  }
@@ -50,10 +50,13 @@ export class SelectFrequency extends withChildOptions(FrequencyStatus) {
50
50
 
51
51
  // default frequency comes from redux store first, then default option value, and then finally attribute value.
52
52
  get defaultFrequency() {
53
- const { options, isSelected } = this.childOptions;
53
+ if (this.configDefaultFrequency) {
54
+ return this.configDefaultFrequency;
55
+ }
54
56
  if (this.productDefaultFrequency) {
55
57
  return this.productDefaultFrequency;
56
58
  }
59
+ const { options, isSelected } = this.childOptions;
57
60
  if (isSelected) {
58
61
  return isSelected;
59
62
  }
@@ -64,7 +67,10 @@ export class SelectFrequency extends withChildOptions(FrequencyStatus) {
64
67
  }
65
68
 
66
69
  get currentFrequency() {
67
- return this.frequency || this.defaultFrequency;
70
+ if (this.frequency) {
71
+ return this.frequency;
72
+ }
73
+ return this.defaultFrequency;
68
74
  }
69
75
 
70
76
  productChangeFrequency(_, value) {
@@ -72,7 +78,20 @@ export class SelectFrequency extends withChildOptions(FrequencyStatus) {
72
78
  }
73
79
 
74
80
  render() {
75
- let { options } = this.childOptions;
81
+ let options;
82
+
83
+ if (this.frequencies?.length) {
84
+ options = this.frequencies.map((value, ix) => ({
85
+ value,
86
+ text:
87
+ this.frequenciesText && ix in this.frequenciesText
88
+ ? this.frequenciesText[ix]
89
+ : frequencyText(value, this.defaultFrequency)
90
+ }));
91
+ } else {
92
+ ({ options } = this.childOptions);
93
+ }
94
+
76
95
  if (!options.length) {
77
96
  options = (this.frequencies || []).map(value => ({
78
97
  value,
@@ -82,16 +101,15 @@ export class SelectFrequency extends withChildOptions(FrequencyStatus) {
82
101
  const defaultFrequency = this.defaultFrequency;
83
102
 
84
103
  options = options.map(({ text, value }) => ({
85
- text: value === defaultFrequency ? `${text} ${(this.defaultText || '').trim()}`.trim() : text,
104
+ text: value === defaultFrequency ? `${text} ${(this.defaultText || '').trim()}` : text,
86
105
  value
87
106
  }));
88
-
89
107
  return html`
90
108
  <og-select
91
109
  .options="${options}"
92
110
  .selected="${this.currentFrequency}"
93
111
  .onChange="${({ target: { value } }) => {
94
- this.productChangeFrequency(this.product, value);
112
+ this.productChangeFrequency(this.product, value, this.offer);
95
113
  }}"
96
114
  ></og-select>
97
115
  `;
@@ -8,7 +8,7 @@ export class TestWizard extends LitElement {
8
8
  :host {
9
9
  position: fixed;
10
10
  top: 5em;
11
- right: 5em;
11
+ righit: 5em;
12
12
  background-color: rgba(255, 255, 255, 0.7);
13
13
  width: 400px;
14
14
  padding: 1em;
@@ -8,4 +8,28 @@ describe('og.offers', function() {
8
8
  it('should define register() method', () => {
9
9
  expect(og.offers.register).toEqual(jasmine.any(Function));
10
10
  });
11
+
12
+ const api = jasmine.objectContaining({
13
+ store: jasmine.any(Object),
14
+ addOptinChangedCallback: jasmine.any(Function),
15
+ addTemplate: jasmine.any(Function),
16
+ clear: jasmine.any(Function),
17
+ config: jasmine.any(Function),
18
+ disableOptinChangedCallbacks: jasmine.any(Function),
19
+ getOptins: jasmine.any(Function),
20
+ getProductsForPurchasePost: jasmine.any(Function),
21
+ initialize: jasmine.any(Function),
22
+ previewMode: jasmine.any(Function),
23
+ register: jasmine.any(Function),
24
+ resolveSettings: jasmine.any(Function),
25
+ setAuthUrl: jasmine.any(Function),
26
+ setEnvironment: jasmine.any(Function),
27
+ setLocale: jasmine.any(Function),
28
+ setMerchantId: jasmine.any(Function),
29
+ setPublicPath: jasmine.any(Function),
30
+ setTemplates: jasmine.any(Function)
31
+ });
32
+ it('imported ', () => {
33
+ expect(og.offers).toEqual(api);
34
+ });
11
35
  });
@@ -22,7 +22,7 @@ describe('Offer', function() {
22
22
  expect(this.underTest.fetchOffer).not.toHaveBeenCalledWith();
23
23
  this.underTest.setAttribute('product', 'yum product');
24
24
  await this.underTest.updateComplete;
25
- expect(this.underTest.fetchOffer).toHaveBeenCalledWith('yum product');
25
+ expect(this.underTest.fetchOffer).toHaveBeenCalledWith('yum product', 'pdp', this.underTest);
26
26
  });
27
27
 
28
28
  it('should not call dispatch given product attribute has not changed', async function() {
@@ -125,7 +125,7 @@ describe('Offer', function() {
125
125
  el.location = 'cart';
126
126
  el.product = { id: 'yum id' };
127
127
  await appendToBody(el);
128
- expect(el.optinProduct).toHaveBeenCalledWith({ id: 'yum id' }, '1_1');
128
+ expect(el.optinProduct).toHaveBeenCalledWith({ id: 'yum id' }, '1_1', el);
129
129
  });
130
130
 
131
131
  it('should not optin by default on location=cart when optin exists', async () => {
@@ -165,7 +165,7 @@ describe('Offer', function() {
165
165
  el.product = { id: 'yum id' };
166
166
  el.productComponents = ['a', 'b'];
167
167
  await appendToBody(el);
168
- expect(el.optinProduct).toHaveBeenCalledWith({ id: 'yum id', components: ['a', 'b'] }, '1_1');
168
+ expect(el.optinProduct).toHaveBeenCalledWith({ id: 'yum id', components: ['a', 'b'] }, '1_1', el);
169
169
  });
170
170
 
171
171
  it('should optin withithout components given offer is autoship eligible, offer does not have product components, and location is cart', async () => {
@@ -178,7 +178,7 @@ describe('Offer', function() {
178
178
  el.location = 'cart';
179
179
  el.product = { id: 'yum id' };
180
180
  await appendToBody(el);
181
- expect(el.optinProduct).toHaveBeenCalledWith({ id: 'yum id' }, '1_1');
181
+ expect(el.optinProduct).toHaveBeenCalledWith({ id: 'yum id' }, '1_1', el);
182
182
  });
183
183
  });
184
184
  });
@@ -30,7 +30,7 @@ describe('OptinButton', function() {
30
30
  element.defaultFrequency = '1_1';
31
31
  element.optinProduct = jasmine.createSpy('optinProduct');
32
32
  await simulateClick(element, 'button');
33
- expect(element.optinProduct).toHaveBeenCalledWith({ id: '123' }, '1_1');
33
+ expect(element.optinProduct).toHaveBeenCalledWith({ id: '123' }, '1_1', undefined);
34
34
  });
35
35
 
36
36
  it('should dispatch optinProduct(product, someFreq) action on click', async () => {
@@ -39,7 +39,7 @@ describe('OptinButton', function() {
39
39
  element.defaultFrequency = 'custom 2.2';
40
40
  element.optinProduct = jasmine.createSpy('optinProduct');
41
41
  await simulateClick(element, 'button');
42
- expect(element.optinProduct).toHaveBeenCalledWith({ id: '123' }, 'custom 2.2');
42
+ expect(element.optinProduct).toHaveBeenCalledWith({ id: '123' }, 'custom 2.2', undefined);
43
43
  });
44
44
  });
45
45
  });
@@ -46,7 +46,7 @@ describe('OptinToggle', () => {
46
46
  elm.setAttribute('product', 'yum product');
47
47
  elm.setAttribute('frequency', '1_1');
48
48
  await simulateClick(elm, 'button');
49
- expect(elm.optinProduct).toHaveBeenCalledWith({ id: 'yum product' }, '1_1');
49
+ expect(elm.optinProduct).toHaveBeenCalledWith({ id: 'yum product' }, '1_1', undefined);
50
50
  });
51
51
 
52
52
  it('should optinProduct with product and set frequency', async () => {
@@ -57,6 +57,6 @@ describe('OptinToggle', () => {
57
57
  elm.setAttribute('product', 'yum product');
58
58
  elm.frequency = '3_3';
59
59
  await simulateClick(elm, 'button');
60
- expect(elm.optinProduct).toHaveBeenCalledWith({ id: 'yum product' }, '3_3');
60
+ expect(elm.optinProduct).toHaveBeenCalledWith({ id: 'yum product' }, '3_3', undefined);
61
61
  });
62
62
  });
@@ -16,7 +16,7 @@ describe('OptoutButton', () => {
16
16
  elm.setAttribute('product', 'foo');
17
17
  elm.optoutProduct = jasmine.createSpy('optoutProduct');
18
18
  await simulateClick(elm, 'button');
19
- expect(elm.optoutProduct).toHaveBeenCalledWith({ id: 'foo' });
19
+ expect(elm.optoutProduct).toHaveBeenCalledWith({ id: 'foo' }, undefined);
20
20
  });
21
21
 
22
22
  it('should have empty label if subscribed', async () => {
@@ -22,6 +22,7 @@ describe('Select Frequency', function() {
22
22
  `;
23
23
  element = document.querySelector('og-select-frequency');
24
24
  await element.updateComplete;
25
+ await new Promise(r => setTimeout(r, 1000));
25
26
  });
26
27
 
27
28
  it('it should have default frequency as value', async () => {
@@ -10,7 +10,7 @@ describe('SelectFrequency', () => {
10
10
  selectFrequency.productChangeFrequency = jasmine.createSpy('productChangeFrequency');
11
11
  selectFrequency.setAttribute('product', 'yum product');
12
12
  await simulateChange(selectFrequency, '2_1');
13
- expect(selectFrequency.productChangeFrequency).toHaveBeenCalledWith({ id: 'yum product' }, '2_1');
13
+ expect(selectFrequency.productChangeFrequency).toHaveBeenCalledWith({ id: 'yum product' }, '2_1', undefined);
14
14
  });
15
15
 
16
16
  it('should append default-text to selected frequency', async () => {
@@ -1,11 +1,11 @@
1
1
  import { TestWizard } from '../TestWizard';
2
2
  import { simulateClick } from './utils';
3
- import { getStore } from '../../core/store';
3
+ import { makeStore } from '../../core/store';
4
4
 
5
5
  customElements.define('og-test-wizard', TestWizard);
6
6
 
7
7
  describe('TestWizard', () => {
8
- const store = getStore();
8
+ const store = makeStore();
9
9
  it('should run the tests when the button is clicked', async () => {
10
10
  const testWizard = new TestWizard();
11
11
  testWizard.runTests = jasmine.createSpy('runTests');
@@ -16,6 +16,7 @@ describe('Text', () => {
16
16
 
17
17
  it('should show the i18n text from locale matching the key', async () => {
18
18
  const offer = document.createElement('og-offer');
19
+ offer.fetchOffer = jasmine.createSpy();
19
20
  offer.locale = {
20
21
  foo: 'baz'
21
22
  };
@@ -26,6 +27,8 @@ describe('Text', () => {
26
27
  element.key = 'foo';
27
28
  offer.appendChild(element);
28
29
  await appendToBody(offer);
30
+ await offer.updateComplete;
31
+
29
32
  expect(element.innerText.trim()).toEqual('baz');
30
33
  });
31
34
 
@@ -55,29 +55,29 @@ describe('redux actions', function() {
55
55
  it('optinProduct should return payload', () => {
56
56
  expect(optinProduct('foo', '1_2')).toEqual({
57
57
  type: 'OPTIN_PRODUCT',
58
- payload: {
58
+ payload: jasmine.objectContaining({
59
59
  product: 'foo',
60
60
  frequency: '1_2'
61
- }
61
+ })
62
62
  });
63
63
  });
64
64
 
65
65
  it('optoutProduct should return payload', () => {
66
66
  expect(optoutProduct('foo')).toEqual({
67
67
  type: 'OPTOUT_PRODUCT',
68
- payload: {
68
+ payload: jasmine.objectContaining({
69
69
  product: 'foo'
70
- }
70
+ })
71
71
  });
72
72
  });
73
73
 
74
74
  it('productChangeFrequency should return payload', () => {
75
75
  expect(productChangeFrequency('foo', 'freq')).toEqual({
76
76
  type: 'PRODUCT_CHANGE_FREQUENCY',
77
- payload: {
77
+ payload: jasmine.objectContaining({
78
78
  product: 'foo',
79
79
  frequency: 'freq'
80
- }
80
+ })
81
81
  });
82
82
  });
83
83
 
@@ -1,15 +1,16 @@
1
1
  import { resolveAuth } from '@ordergroove/auth';
2
2
  import * as constants from './constants';
3
3
  import { api } from './api';
4
+ import platform from '../platform';
4
5
 
5
- export const optinProduct = (product, frequency) => ({
6
+ export const optinProduct = (product, frequency, offer) => ({
6
7
  type: constants.OPTIN_PRODUCT,
7
- payload: { product, frequency }
8
+ payload: { product, frequency, offer }
8
9
  });
9
10
 
10
- export const optoutProduct = product => ({
11
+ export const optoutProduct = (product, offer) => ({
11
12
  type: constants.OPTOUT_PRODUCT,
12
- payload: { product }
13
+ payload: { product, offer }
13
14
  });
14
15
 
15
16
  export const productHasChangedComponents = (newProduct, product) => ({
@@ -17,9 +18,9 @@ export const productHasChangedComponents = (newProduct, product) => ({
17
18
  payload: { newProduct, product }
18
19
  });
19
20
 
20
- export const productChangeFrequency = (product, frequency) => ({
21
+ export const productChangeFrequency = (product, frequency, offer) => ({
21
22
  type: constants.PRODUCT_CHANGE_FREQUENCY,
22
- payload: { product, frequency }
23
+ payload: { product, frequency, offer }
23
24
  });
24
25
 
25
26
  export const concludeUpsell = product => ({
@@ -179,20 +180,21 @@ export const fetchResponseError = err => ({
179
180
  payload: err
180
181
  });
181
182
 
182
- export const requestOffer = (product, module = 'pdp') => ({
183
+ export const requestOffer = (product, module = constants.DEFAULT_OFFER_MODULE, offer) => ({
183
184
  type: constants.REQUEST_OFFER,
184
- payload: { product, module }
185
+ payload: { product, module, offer }
185
186
  });
186
187
 
187
- export const fetchOffer = (product, module = 'pdp') =>
188
+ export const fetchOffer = (product, module = constants.DEFAULT_OFFER_MODULE, offer) =>
188
189
  function fetchOfferThunk(dispatch, getState) {
189
190
  const {
190
191
  merchantId,
191
192
  sessionId,
192
193
  environment: { apiUrl }
193
194
  } = getState();
194
- const requestAction = requestOffer(product, module);
195
+ const requestAction = requestOffer(product, module, offer);
195
196
  dispatch(requestAction);
197
+ if (platform.shopify_selling_plans) return null;
196
198
  return api
197
199
  .fetchOffer(apiUrl, merchantId, sessionId, product, module)
198
200
  .then(
@@ -37,3 +37,6 @@ export const LOCAL_STORAGE_CLEAR = 'LOCAL_STORAGE_CLEAR';
37
37
  export const SET_FIRST_ORDER_PLACE_DATE = 'SET_FIRST_ORDER_PLACE_DATE';
38
38
  export const SET_PRODUCT_TO_SUBSCRIBE = 'SET_PRODUCT_TO_SUBSCRIBE';
39
39
  export const RECEIVE_PRODUCT_PLANS = 'RECEIVE_PRODUCT_PLANS';
40
+ export const SETUP_PRODUCT = 'SETUP_PRODUCT';
41
+ export const SETUP_CART = 'SETUP_CART';
42
+ export const DEFAULT_OFFER_MODULE = 'pdp';
@@ -370,7 +370,6 @@ export const locale = (
370
370
 
371
371
  export const config = (
372
372
  state = {
373
- frequencies: ['1_2', '1_2', '1_3'],
374
373
  offerType: 'radio'
375
374
  },
376
375
  action
@@ -454,29 +453,30 @@ export const productPlans = (state = {}, action) => {
454
453
  };
455
454
 
456
455
  export default combineReducers({
457
- productPlans,
458
- environment,
459
456
  optedin,
460
457
  optedout,
461
- merchantId,
462
- offer,
463
- offerId,
464
- productOffer,
465
- sessionId,
458
+ nextUpcomingOrder,
459
+ autoshipEligible,
466
460
  inStock,
467
461
  eligibilityGroups,
468
- autoshipByDefault,
469
- autoshipEligible,
470
462
  incentives,
471
- nextUpcomingOrder,
463
+ frequency,
472
464
  auth,
465
+ merchantId,
473
466
  authUrl,
467
+ offer,
468
+ offerId,
469
+ sessionId,
470
+ productOffer,
471
+ firstOrderPlaceDate,
472
+ productToSubscribe,
473
+ environment,
474
474
  locale,
475
475
  config,
476
476
  previewStandardOffer,
477
477
  previewUpsellOffer,
478
- templates,
478
+ autoshipByDefault,
479
479
  defaultFrequencies,
480
- firstOrderPlaceDate,
481
- productToSubscribe
480
+ templates,
481
+ productPlans
482
482
  });
@@ -93,22 +93,17 @@ export const withProduct = Base =>
93
93
  export const withChildOptions = Base =>
94
94
  class extends Base {
95
95
  get childOptions() {
96
- return this._childOptions || { options: [] };
97
- }
98
-
99
- connectedCallback() {
100
96
  const options = [];
101
97
  let isSelected = null;
98
+
102
99
  this.querySelectorAll('option').forEach(it => {
103
100
  const value = sanitizeFrequencyString(it.value);
104
101
  const text = it.innerText.trim();
105
102
  options.push({ value, text });
106
-
107
103
  if (!isSelected && it.selected) {
108
104
  isSelected = value;
109
105
  }
110
106
  });
111
- this._childOptions = { options, isSelected };
112
- super.connectedCallback && super.connectedCallback();
107
+ return { options, isSelected };
113
108
  }
114
109
  };
@@ -133,9 +133,9 @@ export const kebabCase = string => {
133
133
  */
134
134
  export const configSelector = (state, element, key, defaultValue) => ({
135
135
  [key]:
136
+ (state.config && state.config[key]) ||
136
137
  (element && element.hasAttribute && element.hasAttribute(kebabCase(key)) && element[key]) ||
137
138
  (element.offer && typeof (element.offer[key] !== 'undefined') && element.offer[key]) ||
138
- (state.config && state.config[key]) ||
139
139
  defaultValue
140
140
  });
141
141
 
package/src/core/store.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createStore, compose, applyMiddleware } from 'redux';
2
2
  import thunk from 'redux-thunk';
3
- import reducer from './reducer';
4
- import { loadState, saveState } from './localStorage';
3
+
4
+ import { loadState } from './localStorage';
5
5
  import { dispatchMiddleware, localStorageMiddleware } from './middleware';
6
6
 
7
- export function getStore() {
7
+ export function makeStore(reducer, ...extraMiddlewares) {
8
8
  if (window.og && window.og.store) return window.og.store;
9
9
 
10
10
  const isPreviewMode = window.og && window.og.previewMode;
@@ -19,11 +19,12 @@ export function getStore() {
19
19
  : compose;
20
20
 
21
21
  const middlewares = [thunk, dispatchMiddleware];
22
+
22
23
  if (!isPreviewMode) {
23
24
  middlewares.push(localStorageMiddleware);
24
25
  }
25
26
 
26
- const enhancer = composeEnhancers(applyMiddleware(...middlewares));
27
+ const enhancer = composeEnhancers(applyMiddleware(...middlewares, ...extraMiddlewares));
27
28
  const store = createStore(reducer, isPreviewMode ? {} : loadState(), enhancer);
28
29
 
29
30
  window.og = window.og || {};
@@ -31,4 +32,4 @@ export function getStore() {
31
32
  return store;
32
33
  }
33
34
 
34
- export default getStore;
35
+ export default makeStore;