@financial-times/n-conversion-forms 26.0.0 → 27.0.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.
Files changed (111) hide show
  1. package/.circleci/config.yml +4 -4
  2. package/.eslintignore +1 -0
  3. package/.github/workflows/gh-pages-deploy.yml +1 -0
  4. package/.storybook/main.js +0 -1
  5. package/__mocks__/@financial-times/o-expander.js +9 -0
  6. package/__mocks__/@financial-times/o-forms-input.js +11 -0
  7. package/__mocks__/@financial-times/o-forms.js +40 -0
  8. package/build-state/npm-shrinkwrap.json +294 -6334
  9. package/components/__snapshots__/b2c-partnership-confirmation.spec.js.snap +1 -1
  10. package/components/__snapshots__/confirmation.spec.js.snap +0 -3
  11. package/components/__snapshots__/debug.spec.js.snap +25 -13
  12. package/components/__snapshots__/delivery-postcode.spec.js.snap +1 -1
  13. package/components/__snapshots__/payment-term.spec.js.snap +1 -1
  14. package/components/__snapshots__/payment-type.spec.js.snap +19 -0
  15. package/components/__snapshots__/registration-confirmation.spec.js.snap +230 -74
  16. package/components/accept-terms.jsx +57 -36
  17. package/components/accept-terms.stories.js +28 -1
  18. package/components/b2c-partnership-confirmation.jsx +1 -1
  19. package/components/confirmation.jsx +15 -8
  20. package/components/confirmation.spec.js +1 -1
  21. package/components/debug.jsx +19 -12
  22. package/components/debug.stories.js +13 -0
  23. package/components/delivery-address.jsx +21 -58
  24. package/components/delivery-address.stories.js +7 -0
  25. package/components/delivery-city.jsx +4 -2
  26. package/components/delivery-city.stories.js +9 -0
  27. package/components/delivery-option.jsx +5 -3
  28. package/components/delivery-option.stories.js +7 -1
  29. package/components/delivery-postcode.jsx +32 -13
  30. package/components/delivery-postcode.stories.js +2 -1
  31. package/components/delivery-security-instructions.spec.js +3 -3
  32. package/components/graduation-date.spec.js +8 -8
  33. package/components/index.jsx +1 -1
  34. package/components/message.jsx +1 -1
  35. package/components/payment-term.jsx +152 -25
  36. package/components/payment-term.spec.js +46 -4
  37. package/components/payment-term.stories.js +69 -14
  38. package/components/payment-type.jsx +3 -1
  39. package/components/position.jsx +6 -3
  40. package/components/position.spec.js +15 -5
  41. package/components/registration-confirmation.jsx +111 -32
  42. package/components/responsibility.jsx +6 -3
  43. package/components/responsibility.spec.js +15 -5
  44. package/components/text-input.jsx +73 -0
  45. package/components/text-input.spec.js +118 -0
  46. package/components/text-input.stories.js +31 -0
  47. package/dist/accept-terms.js +15 -7
  48. package/dist/b2c-partnership-confirmation.js +1 -1
  49. package/dist/confirmation.js +11 -2
  50. package/dist/debug.js +6 -6
  51. package/dist/delivery-address.js +19 -32
  52. package/dist/delivery-city.js +4 -2
  53. package/dist/delivery-option.js +5 -2
  54. package/dist/delivery-postcode.js +31 -12
  55. package/dist/index.js +9 -9
  56. package/dist/message.js +1 -1
  57. package/dist/payment-term.js +117 -11
  58. package/dist/payment-type.js +5 -2
  59. package/dist/position.js +6 -2
  60. package/dist/registration-confirmation.js +87 -29
  61. package/dist/responsibility.js +6 -2
  62. package/dist/text-input.js +84 -0
  63. package/helpers/constants.js +7 -0
  64. package/helpers/deliveryAddressMap.js +167 -0
  65. package/helpers/index.js +7 -0
  66. package/helpers/index.spec.js +11 -0
  67. package/helpers/ncf-common-data.spec.js +34 -0
  68. package/helpers/ncf-countries.spec.js +136 -0
  69. package/helpers/supportedCountries.js +76 -0
  70. package/helpers/supportedPostcodeExamples.js +57 -0
  71. package/helpers/supportedPostcodeValidators.js +53 -0
  72. package/helpers/utilities.js +14 -0
  73. package/jest.config.js +8 -1
  74. package/main.scss +461 -0
  75. package/package.json +6 -3
  76. package/styles/confirmation.scss +122 -0
  77. package/styles/payment-term.scss +3 -0
  78. package/utils/app-banner.spec.js +68 -0
  79. package/utils/apple-pay.spec.js +177 -0
  80. package/utils/billing-country.spec.js +87 -0
  81. package/utils/billing-postcode.spec.js +138 -0
  82. package/utils/company-name.spec.js +3 -7
  83. package/utils/country.spec.js +87 -0
  84. package/utils/delivery-address-type.spec.js +24 -11
  85. package/utils/delivery-option-messages.js +15 -0
  86. package/utils/delivery-option-messages.spec.js +3 -3
  87. package/utils/delivery-option.spec.js +100 -15
  88. package/utils/delivery-postcode.spec.js +138 -0
  89. package/utils/delivery-start-date.spec.js +177 -0
  90. package/utils/email.spec.js +210 -0
  91. package/utils/event-notifier.spec.js +116 -0
  92. package/utils/form-element.spec.js +71 -0
  93. package/utils/loader.spec.js +161 -0
  94. package/utils/password.spec.js +65 -0
  95. package/utils/payment-term.js +25 -1
  96. package/utils/payment-term.spec.js +198 -0
  97. package/utils/payment-type.js +1 -1
  98. package/utils/payment-type.spec.js +136 -0
  99. package/utils/postcode.spec.js +122 -0
  100. package/utils/salesforce.spec.js +30 -0
  101. package/utils/submit.spec.js +81 -0
  102. package/utils/tracking.spec.js +174 -0
  103. package/utils/validation.js +2 -2
  104. package/utils/validation.spec.js +234 -0
  105. package/utils/zuora.spec.js +249 -0
  106. package/components/__snapshots__/b2c-partnership-payment-term.spec.js.snap +0 -193
  107. package/components/b2c-partnership-payment-term.jsx +0 -126
  108. package/components/b2c-partnership-payment-term.spec.js +0 -52
  109. package/components/b2c-partnership-payment-term.stories.js +0 -44
  110. package/dist/b2c-partnership-payment-term.js +0 -91
  111. package/styles/b2c-partnership-payment-term.scss +0 -20
@@ -0,0 +1,174 @@
1
+ const Tracking = require('./tracking');
2
+
3
+ describe('Tracking', () => {
4
+ let element;
5
+ let window;
6
+ let tracking;
7
+
8
+ beforeEach(() => {
9
+ element = { dispatchEvent: () => {} };
10
+ window = {
11
+ CustomEvent: function () {},
12
+ Image: function () {},
13
+ Date: function () {},
14
+ };
15
+ tracking = new Tracking(window, element);
16
+
17
+ jest.spyOn(tracking, 'dispatchCustomEvent');
18
+ jest.spyOn(tracking, 'dispatchImage');
19
+ jest.spyOn(window, 'CustomEvent');
20
+ });
21
+
22
+ afterEach(() => {
23
+ jest.clearAllMocks();
24
+ });
25
+
26
+ describe('constructor', () => {
27
+ it('throws an error if window not passed', () => {
28
+ expect(() => {
29
+ new Tracking();
30
+ }).toThrow();
31
+ });
32
+
33
+ it('throws an error if element not passed', () => {
34
+ expect(() => {
35
+ new Tracking(window);
36
+ }).toThrow();
37
+ });
38
+ });
39
+
40
+ describe('dispatch', () => {
41
+ describe('parameter validation', () => {
42
+ it('throws an error if nothing passed', () => {
43
+ expect(() => {
44
+ tracking.dispatch();
45
+ }).toThrow();
46
+ });
47
+
48
+ it('throws an error if action is missing', () => {
49
+ expect(() => {
50
+ tracking.dispatch('test');
51
+ }).toThrow();
52
+ });
53
+ });
54
+
55
+ describe('dispatch events', () => {
56
+ it('calls dispatchEvent', () => {
57
+ tracking.dispatch('test', 'test');
58
+ expect(tracking.dispatchCustomEvent).toHaveBeenCalled();
59
+ });
60
+
61
+ it('fallbacks to dispatchImage if dispatchEvent errors', () => {
62
+ tracking.dispatchCustomEvent.mockRestore();
63
+ tracking.dispatchCustomEvent = jest.fn().mockImplementation(() => {
64
+ throw new Error();
65
+ });
66
+ tracking.dispatch('test', 'test');
67
+ expect(tracking.dispatchImage).toHaveBeenCalled();
68
+ });
69
+ });
70
+
71
+ describe('extra data', () => {
72
+ it('merges extra tracking data', () => {
73
+ const data = { extra: 'data' };
74
+ tracking.dispatch('test', 'test', data);
75
+ expect(tracking.dispatchCustomEvent).toHaveBeenCalledWith(
76
+ expect.objectContaining(data)
77
+ );
78
+ });
79
+
80
+ it('does not overwrite the given action and test', () => {
81
+ const data = { action: 'bad', category: 'bad' };
82
+ tracking.dispatch('test', 'test', data);
83
+ expect(tracking.dispatchCustomEvent).not.toHaveBeenCalledWith({
84
+ action: 'bad',
85
+ category: 'bad',
86
+ });
87
+ });
88
+ });
89
+
90
+ describe('empty properties', () => {
91
+ const data = {
92
+ testUndefined: undefined,
93
+ testNull: null,
94
+ testEmptyString: '',
95
+ testZero: 0,
96
+ testFalse: false,
97
+ };
98
+
99
+ it('does not send undefined properties', () => {
100
+ tracking.dispatch('test', 'test', data);
101
+ expect(tracking.dispatchCustomEvent).toHaveBeenCalledWith(
102
+ expect.not.objectContaining({ testUndefined: undefined })
103
+ );
104
+ });
105
+
106
+ it('does not send null properties', () => {
107
+ tracking.dispatch('test', 'test', data);
108
+ expect(tracking.dispatchCustomEvent).toHaveBeenCalledWith(
109
+ expect.not.objectContaining({ testNull: null })
110
+ );
111
+ });
112
+
113
+ it('does not send empty string properties', () => {
114
+ tracking.dispatch('test', 'test', data);
115
+ expect(tracking.dispatchCustomEvent).toHaveBeenCalledWith(
116
+ expect.not.objectContaining({ testEmptyString: '' })
117
+ );
118
+ });
119
+
120
+ it('sends zero properties', () => {
121
+ tracking.dispatch('test', 'test', data);
122
+ expect(tracking.dispatchCustomEvent).toHaveBeenCalledWith(
123
+ expect.objectContaining({ testZero: 0 })
124
+ );
125
+ });
126
+
127
+ it('sends false properties', () => {
128
+ tracking.dispatch('test', 'test', data);
129
+
130
+ expect(tracking.dispatchCustomEvent).toHaveBeenCalledWith(
131
+ expect.objectContaining({ testFalse: false })
132
+ );
133
+ });
134
+ });
135
+ });
136
+
137
+ describe('dispatchCustomEvent', () => {
138
+ const eventData = { action: 'test', category: 'test' };
139
+
140
+ it('does not throw an error', () => {
141
+ expect(() => {
142
+ tracking.dispatchCustomEvent(eventData);
143
+ }).not.toThrow();
144
+ });
145
+
146
+ it('passes event data to the CustomEvent detail parameter', () => {
147
+ tracking.dispatchCustomEvent(eventData);
148
+ expect(window.CustomEvent).toHaveBeenCalledWith(
149
+ expect.anything(),
150
+ expect.objectContaining({ detail: eventData })
151
+ );
152
+ });
153
+
154
+ it('writes debug data', () => {
155
+ tracking.dispatchCustomEvent(eventData);
156
+ expect(tracking.getDebugData()[0].data).toMatchObject(eventData);
157
+ });
158
+ });
159
+
160
+ describe('dispatchImage', () => {
161
+ const eventData = { action: 'test', category: 'test' };
162
+
163
+ it('does not throw an error', () => {
164
+ expect(() => {
165
+ tracking.dispatchImage(eventData);
166
+ }).not.toThrow();
167
+ });
168
+
169
+ it('writes debug data', () => {
170
+ tracking.dispatchImage(eventData);
171
+ expect(tracking.getDebugData()[0].data).toMatchObject(eventData);
172
+ });
173
+ });
174
+ });
@@ -1,5 +1,5 @@
1
- const OForms = require('o-forms').default;
2
- const Input = require('o-forms/src/js/input').default;
1
+ const OForms = require('@financial-times/o-forms').default;
2
+ const Input = require('@financial-times/o-forms/src/js/input').default;
3
3
 
4
4
  class Validation {
5
5
  /**
@@ -0,0 +1,234 @@
1
+ jest.mock('@financial-times/o-forms');
2
+ jest.mock('@financial-times/o-forms/src/js/input');
3
+ const OForms = require('@financial-times/o-forms');
4
+ const Input = require('@financial-times/o-forms/src/js/input').default;
5
+ const ValidationUtil = require('./validation');
6
+
7
+ describe('Validation - Util', () => {
8
+ let validation;
9
+ beforeEach(() => {
10
+ document.body.innerHTML = `
11
+ <!DOCTYPE html>
12
+ <html>
13
+ <head></head>
14
+ <body>
15
+ <form class="ncf"></form>
16
+ </body>
17
+ </html>`;
18
+
19
+ validation = new ValidationUtil(document);
20
+ jest.spyOn(validation, 'checkFormValidity');
21
+ validation.init();
22
+ });
23
+
24
+ afterEach(() => {
25
+ jest.clearAllMocks();
26
+ });
27
+
28
+ describe('constructor', () => {
29
+ it('calls oForms to setup client side validation', () => {
30
+ expect(OForms.default.init).toHaveBeenCalled();
31
+ });
32
+
33
+ it('checks validation status on init', () => {
34
+ expect(validation.checkFormValidity).toHaveBeenCalled();
35
+ });
36
+
37
+ it('has a $form property exposing the form element', () => {
38
+ expect(validation.$form).toBeDefined();
39
+ });
40
+ });
41
+
42
+ describe('init', () => {
43
+ it('adds an event listener to required elements', () => {
44
+ expect(
45
+ validation.$requiredEls[0].input.addEventListener
46
+ ).toHaveBeenCalled();
47
+ });
48
+
49
+ it('binds to onbeforeunload by default', () => {
50
+ expect(global.window.onbeforeunload).toBeDefined();
51
+ expect(global.window.onbeforeunload).toBeInstanceOf(Function);
52
+ });
53
+
54
+ it('does not bind to onbeforeunload if mutePromptBeforeLeaving = true', () => {
55
+ delete global.window.onbeforeunload;
56
+ const validationTest = new ValidationUtil({
57
+ mutePromptBeforeLeaving: true,
58
+ });
59
+ validationTest.init();
60
+
61
+ expect(global.window.onbeforeunload).not.toBeDefined();
62
+ });
63
+ });
64
+
65
+ describe('onbeforeunload', () => {
66
+ it('returns null by default', () => {
67
+ expect(global.window.onbeforeunload()).toBe(null);
68
+ });
69
+
70
+ it('returns true if the form has changed', () => {
71
+ validation.formChanged = true;
72
+
73
+ expect(global.window.onbeforeunload()).toBe(true);
74
+ });
75
+ });
76
+
77
+ describe('checkElementValidity', () => {
78
+ let $el;
79
+
80
+ beforeEach(() => {
81
+ $el = { foo: true };
82
+ });
83
+
84
+ it('does not call validateInput if custom validation fails', () => {
85
+ jest.spyOn(validation, 'checkCustomValidation').mockReturnValue(false);
86
+ validation.checkElementValidity($el);
87
+
88
+ expect(Input).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it('calls input.validate for the element.', () => {
92
+ validation.checkElementValidity($el);
93
+
94
+ expect(Input).toHaveBeenCalledWith($el);
95
+ });
96
+ });
97
+
98
+ describe('checkFormValidity', () => {
99
+ it('sets the form as invalid if there are invalid elements.', () => {
100
+ validation.formValid = true;
101
+ validation.$requiredEls[0].input.checkValidity.mockReturnValue(false);
102
+
103
+ validation.checkFormValidity();
104
+
105
+ expect(validation.formValid).toBe(false);
106
+ });
107
+
108
+ it('sets the form as valid if there are no invalid elements.', () => {
109
+ validation.formValid = false;
110
+
111
+ validation.checkFormValidity();
112
+
113
+ expect(validation.formValid).toBe(true);
114
+ });
115
+ });
116
+
117
+ describe('Custom Validation', () => {
118
+ let field;
119
+
120
+ beforeEach(() => {
121
+ field = validation.oForms.formInputs.find(
122
+ (el) => el.input.name === 'foo'
123
+ );
124
+ });
125
+
126
+ describe('addCustomValidation', () => {
127
+ it('stores a custom validation function', () => {
128
+ validation.addCustomValidation({
129
+ errorMessage: 'Oops, something custom went wrong!',
130
+ field,
131
+ validator: jest.fn(),
132
+ });
133
+ expect(validation.customValidation.size).toBe(1);
134
+ });
135
+
136
+ it('throws if a custom validation function has already been specified for a particular field', () => {
137
+ validation.addCustomValidation({
138
+ errorMessage: 'Oops, something custom went wrong!',
139
+ field,
140
+ validator: jest.fn(),
141
+ });
142
+
143
+ expect(() => {
144
+ validation.addCustomValidation({
145
+ errorMessage: 'Oops, something else custom went wrong!',
146
+ field,
147
+ });
148
+ }).toThrow();
149
+ });
150
+
151
+ it('stores a custom validation function that will show a custom validation message when validation fails', async () => {
152
+ jest.spyOn(validation, 'showCustomFieldValidationError');
153
+ jest.spyOn(validation, 'clearCustomFieldValidationError');
154
+
155
+ validation.addCustomValidation({
156
+ errorMessage: 'Oops, something custom went wrong!',
157
+ field,
158
+ validator: jest.fn().mockReturnValue(false),
159
+ });
160
+
161
+ // Run the stored custom validation function
162
+ await validation.customValidation.get('foo')();
163
+
164
+ expect(validation.showCustomFieldValidationError).toHaveBeenCalled();
165
+ expect(
166
+ validation.clearCustomFieldValidationError
167
+ ).not.toHaveBeenCalled();
168
+ });
169
+
170
+ it('stores a custom validation function that will clear a custom validation message when validation passes', async () => {
171
+ jest.spyOn(validation, 'showCustomFieldValidationError');
172
+ jest.spyOn(validation, 'clearCustomFieldValidationError');
173
+
174
+ validation.addCustomValidation({
175
+ errorMessage: 'Oops, something custom went wrong!',
176
+ field,
177
+ validator: jest.fn().mockReturnValue(true),
178
+ });
179
+
180
+ // Run the stored custom validation function
181
+ await validation.customValidation.get('foo')();
182
+
183
+ expect(validation.clearCustomFieldValidationError).toHaveBeenCalled();
184
+ });
185
+ });
186
+
187
+ describe('showCustomFieldValidationError', () => {
188
+ let messageStub = { foo: 'bar' };
189
+
190
+ it('adds the o-form--error class to the parent', () => {
191
+ jest.spyOn(field.input.parentNode.classList, 'add');
192
+ validation.showCustomFieldValidationError(field.input, messageStub);
193
+
194
+ expect(field.input.parentNode.classList.add).toHaveBeenCalledWith(
195
+ 'o-forms-input--invalid'
196
+ );
197
+ });
198
+
199
+ it('adds the message to the parent', () => {
200
+ jest.spyOn(global.document, 'querySelector').mockReturnValue(null);
201
+ validation.showCustomFieldValidationError(field.input, messageStub);
202
+
203
+ expect(field.input.parentNode.insertBefore).toHaveBeenCalledWith(
204
+ messageStub,
205
+ null
206
+ );
207
+ });
208
+ });
209
+
210
+ describe('clearCustomFieldValidationError', () => {
211
+ beforeEach(() => {
212
+ validation.$form = document.createElement('form');
213
+ });
214
+
215
+ it('removes the message from the page', () => {
216
+ jest
217
+ .spyOn(validation.$form, 'querySelector')
218
+ .mockReturnValue(field.input);
219
+ validation.clearCustomFieldValidationError(field.input);
220
+
221
+ expect(field.parentNode.removeChild).toHaveBeenCalledWith(field.input);
222
+ });
223
+
224
+ it('re-checks the element validity for standard validation rules', () => {
225
+ jest.spyOn(validation, 'checkElementValidity');
226
+ validation.clearCustomFieldValidationError(field.input);
227
+
228
+ expect(validation.checkElementValidity).toHaveBeenCalledWith(
229
+ field.input
230
+ );
231
+ });
232
+ });
233
+ });
234
+ });
@@ -0,0 +1,249 @@
1
+ jest.mock('./form-element');
2
+ const PaymentType = require('./payment-type');
3
+ const Zuora = require('./zuora');
4
+
5
+ let fixtures = {
6
+ render: {
7
+ code: 'firstNameRequired',
8
+ key: 'firstName',
9
+ message: 'firstName required',
10
+ },
11
+ };
12
+
13
+ describe('Zuora', () => {
14
+ let window;
15
+ let zuora;
16
+
17
+ beforeEach(() => {
18
+ window = {
19
+ Z: {
20
+ prepopulate: jest.fn(),
21
+ runAfterRender: jest.fn(),
22
+ renderWithErrorHandler: jest.fn(),
23
+ sendErrorMessageToHpm: jest.fn(),
24
+ setEventHandler: jest.fn(),
25
+ submit: jest.fn(),
26
+ validate: jest.fn(),
27
+ },
28
+ };
29
+ zuora = new Zuora(window);
30
+ });
31
+
32
+ afterEach(() => {
33
+ jest.clearAllMocks();
34
+ });
35
+
36
+ describe('constructor', () => {
37
+ it('sets up global listeners', () => {
38
+ expect(window.Z.setEventHandler).toHaveBeenNthCalledWith(
39
+ 1,
40
+ 'blur_mode_enabled',
41
+ expect.anything()
42
+ );
43
+ expect(window.Z.setEventHandler).toHaveBeenNthCalledWith(
44
+ 2,
45
+ 'blur_mode_disabled',
46
+ expect.anything()
47
+ );
48
+ });
49
+
50
+ it('sets up dom elements', () => {
51
+ expect(zuora.iframe).toBeDefined();
52
+ expect(zuora.overlay).toBeDefined();
53
+ });
54
+
55
+ it('gets Z from window and adds to class scope', () => {
56
+ expect(zuora.Z).toEqual(window.Z);
57
+ });
58
+ });
59
+
60
+ describe('render', () => {
61
+ const params = { foo: 'bar' };
62
+ const prepopulatedFields = { firstName: 'John' };
63
+ const renderCallback = () => {};
64
+
65
+ it('calls relevant Zuora functions', async () => {
66
+ await zuora.render({ params, prepopulatedFields, renderCallback });
67
+
68
+ expect(window.Z.renderWithErrorHandler).toHaveBeenCalled();
69
+ });
70
+
71
+ it('sets up error handler that calls sendErrorMessageToHpm', async () => {
72
+ await zuora.render({ params, prepopulatedFields, renderCallback });
73
+ const handler = window.Z.renderWithErrorHandler.mock.calls[0][3];
74
+
75
+ handler();
76
+ expect(window.Z.sendErrorMessageToHpm).toHaveBeenCalled();
77
+ });
78
+
79
+ it('sets up error handler that calls sendErrorMessageToHpm with correct error', async () => {
80
+ await zuora.render({ params, prepopulatedFields, renderCallback });
81
+ const handler = window.Z.renderWithErrorHandler.mock.calls[0][3];
82
+
83
+ handler(
84
+ fixtures.render.key,
85
+ fixtures.render.code,
86
+ fixtures.render.message
87
+ );
88
+ expect(window.Z.sendErrorMessageToHpm).toHaveBeenCalledWith(
89
+ 'firstName',
90
+ 'First Name is invalid'
91
+ );
92
+ });
93
+
94
+ it('binds the renderCallback to Zuora`s runAfterRender function', async () => {
95
+ await zuora.render({ params, prepopulatedFields, renderCallback });
96
+
97
+ expect(window.Z.runAfterRender).toHaveBeenCalled();
98
+ });
99
+ });
100
+
101
+ describe('submit', () => {
102
+ it('rejects if paymentType is not valid', async () => {
103
+ let error;
104
+ try {
105
+ await zuora.submit('');
106
+ } catch (e) {
107
+ error = e;
108
+ }
109
+
110
+ expect(error instanceof Zuora.ZuoraErrorInvalidPaymentType).toBe(true);
111
+ });
112
+
113
+ describe(PaymentType.CREDITCARD, () => {
114
+ it('calls validate', async () => {
115
+ window.Z.validate.mockImplementation((callback) =>
116
+ callback({ success: true })
117
+ );
118
+ await zuora.submit(PaymentType.CREDITCARD);
119
+
120
+ expect(window.Z.validate).toHaveBeenCalled();
121
+ });
122
+
123
+ it('rejects if the validation fails', async () => {
124
+ let error;
125
+ window.Z.validate.mockImplementation((callback) =>
126
+ callback({ success: false })
127
+ );
128
+ try {
129
+ await zuora.submit(PaymentType.CREDITCARD);
130
+ } catch (e) {
131
+ error = e;
132
+ }
133
+
134
+ expect(error instanceof Zuora.ZuoraErrorValidation).toBe(true);
135
+ });
136
+
137
+ it('calls submit if validation passes', async () => {
138
+ window.Z.validate.mockImplementation((callback) =>
139
+ callback({ success: true })
140
+ );
141
+ await zuora.submit(PaymentType.CREDITCARD);
142
+
143
+ expect(window.Z.submit).toHaveBeenCalled();
144
+ });
145
+ });
146
+
147
+ describe(PaymentType.DIRECTDEBIT, () => {
148
+ it('calls validate', async () => {
149
+ zuora.onDirectDebitConfirmation = jest.fn((callback) => callback(true));
150
+ window.Z.validate.mockImplementation((callback) =>
151
+ callback({ success: true })
152
+ );
153
+ await zuora.submit(PaymentType.DIRECTDEBIT);
154
+
155
+ expect(window.Z.validate).toHaveBeenCalled();
156
+ });
157
+
158
+ it('rejects if the validation fails', async () => {
159
+ let error;
160
+ zuora.onDirectDebitConfirmation = jest.fn((callback) => callback(true));
161
+ window.Z.validate.mockImplementation((callback) =>
162
+ callback({ success: false })
163
+ );
164
+ try {
165
+ await zuora.submit(PaymentType.DIRECTDEBIT);
166
+ } catch (e) {
167
+ error = e;
168
+ }
169
+
170
+ expect(error instanceof Zuora.ZuoraErrorValidation).toBe(true);
171
+ });
172
+
173
+ it('calls submit if validation passes', async () => {
174
+ zuora.onDirectDebitConfirmation = jest.fn((callback) => callback(true));
175
+ window.Z.validate.mockImplementation((callback) =>
176
+ callback({ success: true })
177
+ );
178
+ await zuora.submit(PaymentType.DIRECTDEBIT);
179
+
180
+ expect(window.Z.submit).toHaveBeenCalled();
181
+ });
182
+
183
+ it('rejects if the direct debit is not confirmed', async () => {
184
+ let error;
185
+ zuora.onDirectDebitConfirmation = jest.fn((callback) =>
186
+ callback(false)
187
+ );
188
+ window.Z.validate.mockImplementation((callback) =>
189
+ callback({ success: true })
190
+ );
191
+ try {
192
+ await zuora.submit(PaymentType.DIRECTDEBIT);
193
+ } catch (e) {
194
+ error = e;
195
+ }
196
+
197
+ expect(error instanceof Zuora.ZuoraErrorMandateCancel).toBe(true);
198
+ });
199
+ });
200
+ });
201
+
202
+ describe('onAgreementCheckboxChange', () => {
203
+ let callbackStub;
204
+
205
+ beforeEach(() => {
206
+ callbackStub = jest.fn();
207
+ });
208
+
209
+ it('sets up agreement checkbox event handlers', () => {
210
+ zuora.onAgreementCheckboxChange(callbackStub);
211
+
212
+ // The first 2 calls to this happen in setupListeners
213
+ expect(window.Z.setEventHandler).toHaveBeenNthCalledWith(
214
+ 3,
215
+ 'agreement_checked',
216
+ expect.any(Function)
217
+ );
218
+ expect(window.Z.setEventHandler).toHaveBeenNthCalledWith(
219
+ 4,
220
+ 'agreement_unchecked',
221
+ expect.any(Function)
222
+ );
223
+ });
224
+ });
225
+
226
+ describe('onDirectDebitConfirmation', () => {
227
+ let callbackStub;
228
+
229
+ beforeEach(() => {
230
+ callbackStub = jest.fn();
231
+ });
232
+
233
+ it('sets up mandate confirmation event handlers', () => {
234
+ zuora.onDirectDebitConfirmation(callbackStub);
235
+
236
+ // The first 2 calls to this happen in setupListeners
237
+ expect(window.Z.setEventHandler).toHaveBeenNthCalledWith(
238
+ 3,
239
+ 'mandate_confirmed',
240
+ expect.any(Function)
241
+ );
242
+ expect(window.Z.setEventHandler).toHaveBeenNthCalledWith(
243
+ 4,
244
+ 'mandate_cancelled',
245
+ expect.any(Function)
246
+ );
247
+ });
248
+ });
249
+ });