@rebilly/instruments 3.14.4-beta.0 → 3.15.1-beta.0

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": "@rebilly/instruments",
3
- "version": "3.14.4-beta.0",
3
+ "version": "3.15.1-beta.0",
4
4
  "author": "Rebilly",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -6,22 +6,21 @@ export async function destroy({ state }) {
6
6
  // wait to allow for cancellation to catch any pending api requests
7
7
  const sleepMilliseconds = 1000;
8
8
  await sleep(sleepMilliseconds);
9
- [
10
- ...(state.iframeComponents?.form || []),
11
- ...(state.iframeComponents?.summary || [])
12
- ].forEach((iframe) => {
13
- iframe.destroy();
14
- });
9
+
10
+ Object.values(state.iframeComponents)
11
+ .forEach(iframe => iframe?.destroy())
15
12
 
16
13
  registeredListeners.removeAll(document);
17
14
 
18
15
  state.iframeComponents = {
19
- summary: [],
20
- form: [],
16
+ summary: null,
17
+ form: null,
21
18
  };
22
19
  state.hasMounted = false;
23
20
 
24
- state.summary.textContent = '';
21
+ if (state.summary) {
22
+ state.summary.textContent = '';
23
+ }
25
24
  state.form.textContent = '';
26
25
  cancellation.cancelAll();
27
26
  state.loader.clearAll();
@@ -105,7 +105,7 @@ export async function mount({
105
105
  state.loader.addDOMElement({ el: state.form });
106
106
  state.loader.addDOMElement({ section: 'summary', el: state.summary });
107
107
  state.loader.startLoading({ state, section: 'summary', id: 'rebilly-instruments-summary' });
108
- state.loader.startLoading({ state, id: 'rebilly-instruments-methods' });
108
+ state.loader.startLoading({ state, id: 'rebilly-instruments-form' });
109
109
 
110
110
  state.data = await fetchData({ state });
111
111
  Events.dataReady.dispatch(state.data);
@@ -1,5 +1,6 @@
1
1
  import camelCase from 'lodash.camelcase';
2
2
  import { RenderMockRebillyInstruments } from 'tests/mocks/rebilly-instruments-mock';
3
+ import { RebillyInstrumentsInstance } from '../instance';
3
4
  import Events from '../events';
4
5
 
5
6
  describe('RebillyInstruments on', () => {
@@ -25,12 +26,18 @@ describe('RebillyInstruments on', () => {
25
26
 
26
27
  it('should throw error for internal namespaced events', async () => {
27
28
  const callback = jest.fn();
28
- const rebillyInstruments = await RenderMockRebillyInstruments();
29
+ const instance = new RebillyInstrumentsInstance();
30
+ const iframeMock = {
31
+ component: {
32
+ call: jest.fn()
33
+ }
34
+ };
35
+ instance.state.iframeComponents.form = iframeMock;
29
36
 
30
37
  let error;
31
38
  try {
32
39
  // rebilly-instruments-purchase-completed will be used internally but not available externally
33
- await rebillyInstruments.on('rebilly-instruments-purchase-completed', callback);
40
+ await instance.on('rebilly-instruments-purchase-completed', callback);
34
41
  } catch (e) {
35
42
  error = e;
36
43
  }
@@ -39,11 +46,17 @@ describe('RebillyInstruments on', () => {
39
46
 
40
47
  it('should throw error for a non defined event', async () => {
41
48
  const callback = jest.fn();
42
- const rebillyInstruments = await RenderMockRebillyInstruments();
49
+ const instance = new RebillyInstrumentsInstance();
50
+ const iframeMock = {
51
+ component: {
52
+ call: jest.fn()
53
+ }
54
+ };
55
+ instance.state.iframeComponents.form = iframeMock;
43
56
 
44
57
  let error;
45
58
  try {
46
- await rebillyInstruments.on('not-an-event', callback);
59
+ await instance.on('not-an-event', callback);
47
60
  } catch (e) {
48
61
  error = e;
49
62
  }
@@ -16,17 +16,13 @@ import { mountResult } from '../views/result';
16
16
  export async function show({ componentName, payload, state }) {
17
17
  switch (componentName) {
18
18
  case 'result':
19
- state.iframeComponents.form = state.iframeComponents.form.filter((iframe) => {
20
- iframe.destroy();
21
- return false;
22
- });
19
+ if (state.iframeComponents.form) {
20
+ state.iframeComponents.form.destroy();
21
+ state.iframeComponents.form = null;
22
+ }
23
23
  mountResult({payload, state});
24
24
  break;
25
25
  case 'confirmation':
26
- state.iframeComponents.form = state.iframeComponents.form.filter((iframe) => {
27
- iframe.destroy();
28
- return false;
29
- });
30
26
  mountConfirmation({payload, state});
31
27
  break;
32
28
  default:
@@ -12,7 +12,7 @@ describe('RebillyInstruments show', () => {
12
12
  .mockReturnValue(Promise.resolve());
13
13
 
14
14
  const instance = new RebillyInstrumentsInstance();
15
- instance.state.iframeComponents.form.push(iframeMock);
15
+ instance.state.iframeComponents.form = iframeMock;
16
16
 
17
17
  const payload = {
18
18
  test: 'value'
@@ -22,7 +22,7 @@ describe('RebillyInstruments show', () => {
22
22
 
23
23
  expect(mountResult).toBeCalledTimes(1);
24
24
  expect(mountResult).toBeCalledWith({payload, state: instance.state});
25
- expect(instance.state.iframeComponents.form).toEqual([]);
25
+ expect(instance.state.iframeComponents.form).toEqual(null);
26
26
  });
27
27
 
28
28
  it('should show confirmation component', async () => {
@@ -31,7 +31,7 @@ describe('RebillyInstruments show', () => {
31
31
  .mockReturnValue(Promise.resolve());
32
32
 
33
33
  const instance = new RebillyInstrumentsInstance();
34
- instance.state.iframeComponents.form.push(iframeMock);
34
+ instance.state.iframeComponents.form = iframeMock;
35
35
 
36
36
  const payload = {
37
37
  test: 'value'
@@ -41,7 +41,7 @@ describe('RebillyInstruments show', () => {
41
41
 
42
42
  expect(mountConfirmation).toBeCalledTimes(1);
43
43
  expect(mountConfirmation).toBeCalledWith({payload, state: instance.state});
44
- expect(instance.state.iframeComponents.form).toEqual([]);
44
+ expect(instance.state.iframeComponents.form).toEqual(iframeMock);
45
45
  });
46
46
 
47
47
  it('should fail for non supported component', async () => {
@@ -31,7 +31,7 @@ describe('RebillyInstruments Update', () => {
31
31
  call
32
32
  }
33
33
  };
34
- rebillyInstruments.state.iframeComponents.form = [fakeIFrameComponent];
34
+ rebillyInstruments.state.iframeComponents.form = fakeIFrameComponent;
35
35
 
36
36
  await rebillyInstruments.update({ locale: 'ja' });
37
37
 
@@ -58,7 +58,7 @@ describe('RebillyInstruments Update', () => {
58
58
  call
59
59
  }
60
60
  };
61
- rebillyInstruments.state.iframeComponents.form = [fakeIFrameComponent];
61
+ rebillyInstruments.state.iframeComponents.form = fakeIFrameComponent;
62
62
 
63
63
  await rebillyInstruments.update({
64
64
  countryCode: 'ES'
@@ -84,7 +84,7 @@ describe('RebillyInstruments Update', () => {
84
84
  }
85
85
  };
86
86
 
87
- rebillyInstruments.state.iframeComponents.form = [fakeIFrameComponent];
87
+ rebillyInstruments.state.iframeComponents.form = fakeIFrameComponent;
88
88
 
89
89
  await rebillyInstruments.update({
90
90
  items: [
package/src/instance.js CHANGED
@@ -19,8 +19,8 @@ export class InstrumentsState {
19
19
  this.loader = new Loader();
20
20
  this.translate = new Translate();
21
21
  this.iframeComponents = {
22
- summary: [],
23
- form: [],
22
+ summary: null,
23
+ form: null,
24
24
  };
25
25
  this.hasMounted = false;
26
26
  }
@@ -92,8 +92,10 @@ export class Loader {
92
92
  }
93
93
 
94
94
  if (
95
- !this[section].length &&
96
- this.DOM[section].querySelector('.rebilly-instruments-loader')
95
+ this.DOM[section] && (
96
+ !this[section].length &&
97
+ this.DOM[section].querySelector('.rebilly-instruments-loader')
98
+ )
97
99
  ) {
98
100
  this.DOM[section]
99
101
  .querySelector('.rebilly-instruments-loader')
@@ -106,9 +108,11 @@ export class Loader {
106
108
  this.form.forEach((id) => {
107
109
  this.stopLoading({ id });
108
110
  });
109
- this.summary.forEach((id) => {
110
- this.stopLoading({ section: 'summary', id });
111
- });
111
+ if (this.summary) {
112
+ this.summary.forEach((id) => {
113
+ this.stopLoading({ section: 'summary', id });
114
+ });
115
+ }
112
116
  this.modal.forEach((id) => {
113
117
  this.stopLoading({ section: 'modal', id });
114
118
  });
@@ -0,0 +1,39 @@
1
+ import { StorefontTestingInstance } from 'tests/mocks/storefront-mock';
2
+ import { ok, get } from 'msw-when-then';
3
+ import { when } from 'tests/msw/server';
4
+ import { storefrontURL } from 'tests/mocks/storefront-api-mock';
5
+ import { fetchAccount } from './account';
6
+ import { AddressModel } from './models/account-model';
7
+
8
+ describe('Storefront API Account', () => {
9
+ it('should handle fetch a customer with a null primary address', async () => {
10
+ const options = {
11
+ jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzYjBkNTRkOS0xNmM4LTRlZDAtYjljMy01ODAyZmQ4YzE2ZTIiLCJleHAiOjE2ODY2OTc0MTQuODQxMjUsImlhdCI6MTY1NTE2MTQxNC44Nzk4OTEsImFjbCI6W3sic2NvcGUiOnsib3JnYW5pemF0aW9uSWQiOlsiMzY0NEFGTEFKMUciXSwicGF5bWVudE1ldGhvZHMiOlsicGF5bWVudC1jYXJkIl19LCJwZXJtaXNzaW9ucyI6WzI4NCwyODYsNDE0LDQxNSw0MzQsNDExLDQxMiw0MTMsNDI0LDQyNSw0MjYsNDI3LDQyOCw0MjksNDA5LDQxMCw0MDEsNDAyLDQzMyw0ODgsNDg3XX1dLCJjbGFpbXMiOnsid2Vic2l0ZUlkIjoiZGVtby13ZWJzaXRlIiwidHJhbnNhY3Rpb25JZCI6IjRiZDE3N2NmLTFlY2EtNDA2Yy04OWI0LTEzYzMzODk5NzlmMCIsInBheW1lbnRNZXRob2RzIjpbInBheW1lbnQtY2FyZCJdfSwibWVyY2hhbnQiOiIzNjQ0QUZMQUoxRyIsImN1c3RvbWVyIjp7ImlkIjoidGVzdC1jdXN0b21lci1pZCIsIm5hbWUiOiJUZXN0IEN1c3RvbWVyIE5hbWUiLCJjcmVhdGVkVGltZSI6IjIwMjItMDctMTRUMDA6MDA6MDArMDA6MDAifX0.VPue9QBhRGe3vvncaD47w84N8Bv2hEeShUl6SlNqoOQ',
12
+ websiteId: 'test-website-id',
13
+ items: [
14
+ {
15
+ planId: 'test-plan-id',
16
+ quantity: 1
17
+ }
18
+ ]
19
+ };
20
+
21
+ when(get(`${storefrontURL}/acount`)).thenReturn(
22
+ ok({
23
+ defaultPaymentInstrument: null,
24
+ id: "test-customer-id",
25
+ primaryAddress: null,
26
+ websiteId: "test-website-id",
27
+ })
28
+ );
29
+
30
+ const instance = StorefontTestingInstance({
31
+ options
32
+ });
33
+
34
+ jest.spyOn(instance.storefront.account, 'get');
35
+
36
+ const response = await fetchAccount({ state: instance });
37
+ expect(response.address).toEqual(new AddressModel({}));
38
+ })
39
+ });
@@ -30,10 +30,10 @@ export class AddressModel {
30
30
 
31
31
  export default class AccountModel extends BaseModel {
32
32
  constructor({
33
- primaryAddress = null,
33
+ primaryAddress = {},
34
34
  ...fields
35
35
  } = {}) {
36
36
  super(fields);
37
- this.address = new AddressModel(primaryAddress);
37
+ this.address = new AddressModel({...primaryAddress});
38
38
  }
39
39
  }
@@ -1,7 +1,5 @@
1
- import { MethodIframe as method } from './method-iframe';
2
1
  import { ViewIframe as view } from './view-iframe';
3
2
  import { ModalIframe as modal } from './modal-iframe';
4
3
 
5
- export const MethodIframe = method;
6
4
  export const ViewIframe = view;
7
5
  export const ModalIframe = modal;
@@ -1,7 +1,3 @@
1
- import { purchase } from '../functions/purchase';
2
- import { setup } from '../functions/setup';
3
- import { ViewIframe } from './common/iframe';
4
- import { updateMethodSelector } from './method-selector';
5
1
  import { updateSummary } from './summary';
6
2
 
7
3
  export const baseConfirmationHTML =
@@ -15,58 +11,23 @@ export async function mountConfirmation({ payload: instrument, state }) {
15
11
  updateSummary({ state, instrument });
16
12
  }
17
13
 
18
- const contentElement = state.form.querySelector('.rebilly-instruments-content');
19
- contentElement.insertAdjacentHTML('afterend', baseConfirmationHTML);
20
-
21
- const methodSelectorElement = state.form.querySelector('.rebilly-instruments-method-selector');
22
- methodSelectorElement.style.visibility = 'hidden';
23
- methodSelectorElement.style.height = '0px';
24
-
25
- state.loader.startLoading({ id: 'rebilly-instruments-confirmation' });
26
-
27
- const container = document.querySelector('.rebilly-instruments-confirmation');
28
-
29
- const { paymentMethodsUrl } = state.options._computed;
30
-
14
+ const iframe = state.iframeComponents.form;
31
15
  const model = {
32
16
  options: state.options,
33
17
  data: state.data.toPostmatesModel(),
34
18
  mainStyleVars: state.mainStyleVars,
35
19
  instrument
36
20
  };
37
- const name = 'rebilly-instruments-confirmation';
38
- const iframe = await new ViewIframe({
39
- name,
40
- url: `${paymentMethodsUrl}/confirmation`,
41
- container,
42
- model
43
- });
44
- iframe.bindEventListeners({
45
- loader: state.loader
46
- });
47
21
 
48
- iframe.component.on(`${name}-confirm-purchase`, (confirmedInstrument) => {
49
- purchase({ state, payload: confirmedInstrument });
22
+ iframe?.component?.call('route', {
23
+ name: 'confirmation'
50
24
  });
51
25
 
52
- iframe.component.on(`${name}-confirm-setup`, (confirmedInstrument) => {
53
- setup({ state, payload: confirmedInstrument });
54
- });
26
+ iframe?.component?.call('update', model);
55
27
 
56
- iframe.component.on('choose-another-method', () => {
57
- state.iframeComponents.form = state.iframeComponents.form.filter((item) => {
58
- if (item.name === iframe.name) {
59
- item.destroy();
60
- return false;
61
- }
62
- return true;
28
+ document.querySelectorAll('[data-rebilly-instruments="express-method"]')
29
+ .forEach(el => {
30
+ el.style.overflow = 'hidden';
31
+ el.style.height = '0px';
63
32
  });
64
- if (state.data.isPurchase) {
65
- updateSummary({ state });
66
- }
67
- updateMethodSelector({ state, mainStyleVars: state.mainStyleVars });
68
- state.form.querySelector('.rebilly-instruments-confirmation').remove();
69
- });
70
-
71
- state.iframeComponents.form.push(iframe);
72
33
  }
@@ -3,13 +3,16 @@ import { collectData } from '@rebilly/risk-data-collector';
3
3
  import { getPaymentMethods } from './get-payment-methods';
4
4
  import { fetchData } from '../../functions/mount/fetch-data';
5
5
  import { ViewIframe } from '../common/iframe';
6
+ import { purchase } from '../../functions/purchase';
7
+ import { setup } from '../../functions/setup';
6
8
  import { mountExpressMethods } from './mount-express-methods';
7
9
  import { generateDigitalWallet } from './generate-digital-wallet';
10
+ import { updateSummary } from '../summary';
8
11
 
9
12
  export const baseMethodSelectorHTML = (compactExpressInstruments) => `
10
13
  <div class="rebilly-instruments-content">
11
14
  <div id="rebilly-instruments-error"></div>
12
- <div class="rebilly-instruments-method-selector ${compactExpressInstruments ? 'has-express-compact' : ''}">
15
+ <div data-rebilly-instruments="express-method" class="rebilly-instruments-method-selector ${compactExpressInstruments ? 'has-express-compact' : ''}">
13
16
  <div class="rebilly-instruments-express-methods ${
14
17
  compactExpressInstruments ? 'is-compact' : ''
15
18
  }">
@@ -19,8 +22,8 @@ export const baseMethodSelectorHTML = (compactExpressInstruments) => `
19
22
  <div data-rebilly-instruments="divider" class="rebilly-instruments-divider">
20
23
  <span class="rebilly-instruments-divider-label" data-rebilly-i18n="form.or">Or</span>
21
24
  </div>
22
- <div class="rebilly-instruments-methods"></div>
23
25
  </div>
26
+ <div class="rebilly-instruments-methods"></div>
24
27
  </div>
25
28
  `;
26
29
 
@@ -63,14 +66,41 @@ export async function mountMethodSelector({ state }) {
63
66
  };
64
67
  const { paymentMethodsUrl } = state.options._computed;
65
68
 
69
+ const name = 'rebilly-instruments-form';
66
70
  const iframe = await new ViewIframe({
67
- name: 'rebilly-instruments-methods',
71
+ name,
68
72
  url: `${paymentMethodsUrl}`,
69
73
  container: METHODS_CONTAINER,
70
74
  model
71
75
  });
72
76
  iframe.bindEventListeners({loader: state.loader});
73
- state.iframeComponents.form.push(iframe);
77
+
78
+ iframe.component.on(`${name}-confirm-purchase`, (confirmedInstrument) => {
79
+ purchase({ state, payload: confirmedInstrument });
80
+ });
81
+
82
+ iframe.component.on(`${name}-confirm-setup`, (confirmedInstrument) => {
83
+ setup({ state, payload: confirmedInstrument });
84
+ });
85
+
86
+ iframe.component.on('choose-another-method', () => {
87
+ document.querySelectorAll('[data-rebilly-instruments="express-method"]')
88
+ .forEach(el => {
89
+ el.style.height = 'auto';
90
+ });
91
+
92
+ iframe.component.call('route', {
93
+ name: 'method-switch'
94
+ });
95
+
96
+ if (state.data.isPurchase) {
97
+ updateSummary({ state });
98
+ }
99
+
100
+ iframe.component.call('update', model);
101
+ });
102
+
103
+ state.iframeComponents.form = iframe;
74
104
  } else {
75
105
  METHODS_CONTAINER.style.display = 'none';
76
106
  document.querySelectorAll('[data-rebilly-instruments="divider"]')
@@ -27,5 +27,5 @@ export async function mountResult({ payload, state }) {
27
27
  loader: state.loader
28
28
  });
29
29
 
30
- state.iframeComponents.form.push(iframe);
30
+ state.iframeComponents.form = iframe;
31
31
  }
@@ -16,7 +16,7 @@ export async function mountSummary({ state }) {
16
16
  model
17
17
  });
18
18
  iframe.bindEventListeners({loader: state.loader});
19
- state.iframeComponents.summary.push(iframe);
19
+ state.iframeComponents.summary = iframe;
20
20
  }
21
21
 
22
22
  export async function updateSummary({ state, instrument }) {
@@ -25,10 +25,8 @@ export async function updateSummary({ state, instrument }) {
25
25
  data: state.data.toPostmatesModel(),
26
26
  options: state.options
27
27
  }
28
- Object.values(state.iframeComponents).forEach(components => {
29
- components.forEach(frame => {
30
- frame.component.call('update', updateModel);
31
- });
32
- });
28
+
29
+ state.iframeComponents.summary.component.call('update', updateModel);
30
+ state.iframeComponents.form.component.call('update', updateModel);
33
31
  }
34
32
 
@@ -1,25 +0,0 @@
1
- import BaseIframe from './base-iframe';
2
- import {
3
- dispatchRebillyInsturmentEventHandler,
4
- resizeComponentHandler,
5
- stopLoaderHandler,
6
- displayOverlay,
7
- showErrorHandler
8
- } from './event-listeners';
9
-
10
- export class MethodIframe extends BaseIframe {
11
- constructor(args = {}) {
12
- super(args);
13
- }
14
-
15
- bindEventListeners({ loader, id } = {}) {
16
- dispatchRebillyInsturmentEventHandler(this);
17
- resizeComponentHandler(this);
18
- stopLoaderHandler(this, {
19
- loader,
20
- id
21
- });
22
- displayOverlay(this);
23
- showErrorHandler(this);
24
- }
25
- }