@financial-times/n-conversion-forms 23.0.5 → 23.0.6

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 (36) hide show
  1. package/__mocks__/@financial-times/o-expander.js +9 -0
  2. package/__mocks__/@financial-times/o-forms-input.js +11 -0
  3. package/__mocks__/@financial-times/o-forms.js +40 -0
  4. package/build-state/npm-shrinkwrap.json +74 -167
  5. package/components/delivery-security-instructions.spec.js +3 -3
  6. package/components/graduation-date.spec.js +8 -8
  7. package/components/payment-term.spec.js +3 -3
  8. package/helpers/index.spec.js +11 -0
  9. package/helpers/ncf-common-data.spec.js +34 -0
  10. package/helpers/ncf-countries.spec.js +136 -0
  11. package/jest.config.js +3 -0
  12. package/package.json +2 -1
  13. package/utils/app-banner.spec.js +68 -0
  14. package/utils/apple-pay.spec.js +177 -0
  15. package/utils/billing-country.spec.js +87 -0
  16. package/utils/billing-postcode.spec.js +138 -0
  17. package/utils/company-name.spec.js +3 -7
  18. package/utils/country.spec.js +87 -0
  19. package/utils/delivery-address-type.spec.js +24 -11
  20. package/utils/delivery-option-messages.spec.js +3 -3
  21. package/utils/delivery-option.spec.js +100 -15
  22. package/utils/delivery-postcode.spec.js +138 -0
  23. package/utils/delivery-start-date.spec.js +177 -0
  24. package/utils/email.spec.js +210 -0
  25. package/utils/event-notifier.spec.js +116 -0
  26. package/utils/form-element.spec.js +71 -0
  27. package/utils/loader.spec.js +161 -0
  28. package/utils/password.spec.js +65 -0
  29. package/utils/payment-term.spec.js +198 -0
  30. package/utils/payment-type.spec.js +136 -0
  31. package/utils/postcode.spec.js +122 -0
  32. package/utils/salesforce.spec.js +30 -0
  33. package/utils/submit.spec.js +81 -0
  34. package/utils/tracking.spec.js +174 -0
  35. package/utils/validation.spec.js +234 -0
  36. package/utils/zuora.spec.js +249 -0
@@ -0,0 +1,177 @@
1
+ jest.mock('fetchres');
2
+ const fetchres = require('fetchres');
3
+ const DeliveryStartDate = require('./delivery-start-date');
4
+
5
+ describe('DeliveryStartDate', () => {
6
+ let document;
7
+ let startDateContainer;
8
+ let startDateFieldStub;
9
+ let startDateTextStub;
10
+ let dateTestInput;
11
+ let csrfFieldElement;
12
+
13
+ beforeEach(() => {
14
+ fetchres.json = jest.fn((response) => response.json());
15
+ global.fetch = jest.fn(() =>
16
+ Promise.resolve({
17
+ status: 200,
18
+ })
19
+ );
20
+
21
+ dateTestInput = {
22
+ setAttribute: jest.fn(),
23
+ };
24
+ startDateContainer = {
25
+ classList: { add: jest.fn(), remove: jest.fn() },
26
+ };
27
+ startDateFieldStub = {
28
+ value: '2019-02-16',
29
+ setAttribute: jest.fn(),
30
+ removeAttribute: jest.fn(),
31
+ };
32
+ startDateTextStub = { innerHTML: 'Saturday 16th of February 2019' };
33
+ csrfFieldElement = { value: '1234567890' };
34
+
35
+ document = {
36
+ querySelector: jest.fn((elementIdentifier) => {
37
+ const classMapping = {
38
+ '#deliveryStartDateField .o-forms-input': startDateContainer,
39
+ '#deliveryStartDate': startDateFieldStub,
40
+ '.js-start-date-text': startDateTextStub,
41
+ '.ncf #csrfToken': csrfFieldElement,
42
+ };
43
+
44
+ return classMapping[elementIdentifier] || false;
45
+ }),
46
+ createElement: jest.fn((elementType) => {
47
+ return elementType === 'input' ? dateTestInput : false;
48
+ }),
49
+ };
50
+ });
51
+
52
+ afterEach(() => {
53
+ fetch.mockClear();
54
+ jest.clearAllMocks();
55
+ });
56
+
57
+ describe('constructor', () => {
58
+ it('throws an error if document element is not passed in', () => {
59
+ expect(() => {
60
+ new DeliveryStartDate();
61
+ }).toThrow();
62
+ });
63
+
64
+ it('throws an error if delivery start date element does not exist on the page', () => {
65
+ expect(() => {
66
+ document.querySelector = () => {};
67
+ new DeliveryStartDate(document);
68
+ }).toThrow();
69
+ });
70
+ });
71
+
72
+ describe('handleDeliveryStartDateChange', () => {
73
+ let startDateUtil;
74
+ let startDateChangeResult;
75
+
76
+ async function setup () {
77
+ global.fetch = jest.fn(() =>
78
+ Promise.resolve({
79
+ json: () =>
80
+ Promise.resolve({
81
+ firstDeliveryDate: '2019-04-13',
82
+ firstDeliveryDateString: 'Saturday 13th of April 2019',
83
+ }),
84
+ })
85
+ );
86
+
87
+ startDateUtil = new DeliveryStartDate(document);
88
+ startDateChangeResult = await startDateUtil.handleDeliveryStartDateChange(
89
+ '/api/path',
90
+ () => {
91
+ return { foo: 'bar' };
92
+ }
93
+ );
94
+ }
95
+
96
+ it('does only call the api if the field has a value', async () => {
97
+ delete startDateFieldStub.value;
98
+ await setup();
99
+ expect(fetch).not.toHaveBeenCalled();
100
+ });
101
+
102
+ it('makes a call to the api for the start date', async () => {
103
+ await setup();
104
+ expect(fetch).toHaveBeenCalled();
105
+ expect(fetch).toHaveBeenCalledWith(
106
+ '/api/path',
107
+ expect.objectContaining({
108
+ method: 'POST',
109
+ credentials: 'include',
110
+ headers: {
111
+ 'CSRF-Token': '1234567890',
112
+ 'Content-Type': 'application/json',
113
+ },
114
+ body: JSON.stringify({
115
+ foo: 'bar',
116
+ startDate: '2019-02-16',
117
+ }),
118
+ })
119
+ );
120
+ });
121
+
122
+ it('updates the page according to the response from the API call', async () => {
123
+ await setup();
124
+ expect(startDateFieldStub.value).toEqual('2019-04-13');
125
+ expect(startDateTextStub.innerHTML).toEqual(
126
+ 'Saturday 13th of April 2019'
127
+ );
128
+ });
129
+
130
+ it('clears errors and return true if the fetch call succeeds', async () => {
131
+ await setup();
132
+ expect(startDateContainer.classList.remove).toHaveBeenCalledWith(
133
+ 'o-forms-input--invalid'
134
+ );
135
+ expect(startDateChangeResult).toBe(true);
136
+ });
137
+
138
+ it('displays an error and return false if the fetch call errors', async () => {
139
+ global.fetch = jest.fn(() => Promise.reject('API is down'));
140
+ startDateUtil = new DeliveryStartDate(document);
141
+
142
+ let startDateChangeResult =
143
+ await startDateUtil.handleDeliveryStartDateChange(
144
+ '/api/path',
145
+ () => {}
146
+ );
147
+
148
+ expect(startDateContainer.classList.add).toHaveBeenCalledWith(
149
+ 'o-forms-input--invalid'
150
+ );
151
+ expect(startDateChangeResult).toBe(false);
152
+ });
153
+ });
154
+
155
+ describe('enable', () => {
156
+ it('enables the start date field', () => {
157
+ let startDateUtil = new DeliveryStartDate(document);
158
+ startDateUtil.enable();
159
+
160
+ expect(startDateFieldStub.removeAttribute).toHaveBeenCalledWith(
161
+ 'disabled'
162
+ );
163
+ });
164
+ });
165
+
166
+ describe('disable', () => {
167
+ it('disables the start date field', () => {
168
+ let startDateUtil = new DeliveryStartDate(document);
169
+ startDateUtil.disable();
170
+
171
+ expect(startDateFieldStub.setAttribute).toHaveBeenCalledWith(
172
+ 'disabled',
173
+ 'true'
174
+ );
175
+ });
176
+ });
177
+ });
@@ -0,0 +1,210 @@
1
+ jest.mock('fetchres');
2
+ const fetchres = require('fetchres');
3
+ const Email = require('./email');
4
+
5
+ describe('Email', () => {
6
+ let document;
7
+ let emailElement;
8
+ let emailConfirmElement;
9
+ let emailConfirmFieldElement;
10
+ let csrfFieldElement;
11
+
12
+ beforeEach(() => {
13
+ emailElement = { addEventListener: () => {} };
14
+ emailConfirmElement = { addEventListener: () => {} };
15
+ emailConfirmFieldElement = {
16
+ classList: { add: () => {}, remove: () => {} },
17
+ };
18
+ csrfFieldElement = { value: '1234567890' };
19
+
20
+ document = {
21
+ querySelector: (selector) => {
22
+ if (selector.indexOf('#emailConfirmField') !== -1) {
23
+ return emailConfirmFieldElement;
24
+ } else if (selector.indexOf('#emailConfirm') !== -1) {
25
+ return emailConfirmElement;
26
+ } else if (selector.indexOf('#csrfToken') !== -1) {
27
+ return csrfFieldElement;
28
+ } else {
29
+ return emailElement;
30
+ }
31
+ },
32
+ };
33
+ jest.spyOn(emailElement, 'addEventListener');
34
+ jest.spyOn(emailConfirmElement, 'addEventListener');
35
+ jest.spyOn(emailConfirmFieldElement.classList, 'add');
36
+ jest.spyOn(emailConfirmFieldElement.classList, 'remove');
37
+
38
+ fetchres.json = jest.fn((response) => response.json());
39
+ global.fetch = jest.fn(() =>
40
+ Promise.resolve({
41
+ status: 200,
42
+ })
43
+ );
44
+ });
45
+
46
+ afterEach(() => {
47
+ fetch.mockClear();
48
+ jest.clearAllMocks();
49
+ });
50
+
51
+ describe('constructor', () => {
52
+ it('throws an error if document element is not passed in', () => {
53
+ expect(() => {
54
+ new Email();
55
+ }).toThrow();
56
+ });
57
+
58
+ it('throws an error if email confirm element does not exist on the page', () => {
59
+ expect(() => {
60
+ document.querySelector = () => {};
61
+ new Email(document);
62
+ }).toThrow();
63
+ });
64
+
65
+ it('throws an error if email element does not exist on the page', () => {
66
+ expect(() => {
67
+ document.querySelector = (selector) => {
68
+ if (selector.indexOf('#emailConfirmField') !== -1) {
69
+ return emailConfirmFieldElement;
70
+ }
71
+ };
72
+ new Email(document);
73
+ }).toThrow();
74
+ });
75
+
76
+ it('adds event listener to email element', () => {
77
+ new Email(document);
78
+ expect(emailElement.addEventListener).toHaveBeenCalledTimes(1);
79
+ });
80
+
81
+ it('adds addEventListener to email confirm element', () => {
82
+ new Email(document);
83
+ expect(emailConfirmElement.addEventListener).toHaveBeenCalledTimes(1);
84
+ });
85
+ });
86
+
87
+ describe('checkMatch', () => {
88
+ it('adds the error class if the fields do not match', () => {
89
+ emailElement.value = 'password';
90
+ emailConfirmElement.value = 'pass';
91
+
92
+ let email = new Email(document);
93
+ email.checkMatch();
94
+
95
+ expect(emailConfirmFieldElement.classList.add).toHaveBeenCalledTimes(1);
96
+ });
97
+
98
+ it('removes the error class if the fields match', () => {
99
+ emailElement.value = 'password';
100
+ emailConfirmElement.value = 'password';
101
+
102
+ let email = new Email(document);
103
+ email.checkMatch();
104
+
105
+ expect(emailConfirmFieldElement.classList.remove).toHaveBeenCalledTimes(
106
+ 1
107
+ );
108
+ });
109
+
110
+ it('does only check if valid if emailConfirm field has a value.', () => {
111
+ emailElement.value = 'password';
112
+ emailConfirmElement.value = '';
113
+
114
+ let email = new Email(document);
115
+ email.checkMatch();
116
+
117
+ expect(emailConfirmFieldElement.classList.add).not.toHaveBeenCalled();
118
+ expect(emailConfirmFieldElement.classList.remove).not.toHaveBeenCalled();
119
+ });
120
+ });
121
+
122
+ describe('Email Exists', () => {
123
+ const url = '/foo';
124
+ let email;
125
+ let onFound;
126
+ let onNotFound;
127
+
128
+ beforeEach(() => {
129
+ onFound = jest.fn();
130
+ onNotFound = jest.fn();
131
+
132
+ email = new Email(document);
133
+ });
134
+
135
+ it('adds an additional event listener to the email field', () => {
136
+ email.registerEmailExistsCheck(url, onFound, onNotFound);
137
+ // Check it's called twice since by default we bind a change listener in the constructor.
138
+ expect(emailElement.addEventListener).toHaveBeenCalledTimes(2);
139
+ });
140
+
141
+ it('returns the handler function so it can potentially be unregistered', () => {
142
+ let handler = email.registerEmailExistsCheck(url, onFound, onNotFound);
143
+ expect(handler).toBeInstanceOf(Function);
144
+ });
145
+
146
+ it('calls the onNotFound callback if the email field has no value.', () => {
147
+ email.handleEmailExistsChange(url, onFound, onNotFound);
148
+ expect(onFound).not.toHaveBeenCalled();
149
+ expect(onNotFound).toHaveBeenCalled();
150
+ });
151
+
152
+ it('calls the specified url with the correct params if the email field has a value', () => {
153
+ emailElement.value = 'test@example.com';
154
+ email.handleEmailExistsChange(url, onFound, onNotFound);
155
+
156
+ expect(fetch).toHaveBeenCalledWith(
157
+ url,
158
+ expect.objectContaining({
159
+ method: 'POST',
160
+ credentials: 'include',
161
+ headers: {
162
+ 'Content-Type': 'application/json',
163
+ },
164
+ body: JSON.stringify({
165
+ email: 'test@example.com',
166
+ csrfToken: '1234567890',
167
+ }),
168
+ })
169
+ );
170
+ });
171
+
172
+ it('calls the onNotFound callback if the call to the url fails', async () => {
173
+ global.fetch = jest.fn(() => Promise.reject('API is down'));
174
+
175
+ emailElement.value = 'test@example.com';
176
+ await email.handleEmailExistsChange(url, onFound, onNotFound);
177
+
178
+ expect(onFound).not.toHaveBeenCalled();
179
+ expect(onNotFound).toHaveBeenCalled();
180
+ });
181
+
182
+ it('calls the onFound callback if the user exists', async () => {
183
+ global.fetch = jest.fn(() =>
184
+ Promise.resolve({
185
+ json: () => Promise.resolve(true),
186
+ })
187
+ );
188
+
189
+ emailElement.value = 'test@example.com';
190
+ await email.handleEmailExistsChange(url, onFound, onNotFound);
191
+
192
+ expect(onFound).toHaveBeenCalled();
193
+ expect(onNotFound).not.toHaveBeenCalled();
194
+ });
195
+
196
+ it('calls the onNotFound callback if the user does not exist', async () => {
197
+ global.fetch = jest.fn(() =>
198
+ Promise.resolve({
199
+ json: () => Promise.resolve(false),
200
+ })
201
+ );
202
+
203
+ emailElement.value = 'test@example.com';
204
+ await email.handleEmailExistsChange(url, onFound, onNotFound);
205
+
206
+ expect(onFound).not.toHaveBeenCalled();
207
+ expect(onNotFound).toHaveBeenCalled();
208
+ });
209
+ });
210
+ });
@@ -0,0 +1,116 @@
1
+ const EventNotifier = require('./event-notifier');
2
+
3
+ describe('Event Notifier', () => {
4
+ const defaults = {
5
+ event: 'focusin',
6
+ buffer: 50,
7
+ emitProperty: 'height',
8
+ watchedProperty: 'clientHeight',
9
+ };
10
+ let mockElement;
11
+ let alternativeWindow;
12
+
13
+ beforeEach(() => {
14
+ mockElement = {
15
+ clientWidth: 100,
16
+ clientHeight: 25,
17
+ addEventListener: jest.fn(),
18
+ };
19
+ global.parent = {
20
+ postMessage: jest.fn(),
21
+ };
22
+ alternativeWindow = {
23
+ postMessage: jest.fn(),
24
+ };
25
+ jest.spyOn(parent, 'postMessage');
26
+ });
27
+
28
+ afterEach(() => {
29
+ jest.clearAllMocks();
30
+ delete global.parent;
31
+ });
32
+
33
+ it('does not do anything if element not provided', () => {
34
+ EventNotifier.init();
35
+ expect(parent.postMessage).not.toHaveBeenCalled();
36
+ expect(mockElement.addEventListener).not.toHaveBeenCalled();
37
+ });
38
+
39
+ it('does not do anything if element not valid', () => {
40
+ EventNotifier.init(null);
41
+ expect(parent.postMessage).not.toHaveBeenCalled();
42
+ expect(mockElement.addEventListener).not.toHaveBeenCalled();
43
+ });
44
+
45
+ it('attaches an event listener of the default event to the provided element', () => {
46
+ EventNotifier.init(mockElement);
47
+ expect(mockElement.addEventListener).toHaveBeenCalledTimes(1);
48
+ expect(mockElement.addEventListener).toHaveBeenCalledWith(
49
+ defaults.event,
50
+ expect.anything()
51
+ );
52
+ });
53
+
54
+ it('attaches an event listener of custom event if one provided', () => {
55
+ const customEvent = 'something';
56
+ EventNotifier.init(mockElement, { event: customEvent });
57
+ expect(mockElement.addEventListener).toHaveBeenCalledTimes(1);
58
+ expect(mockElement.addEventListener).toHaveBeenCalledWith(
59
+ customEvent,
60
+ expect.anything()
61
+ );
62
+ });
63
+
64
+ it('does immediately postMessage if "notifyOnStart" is true', () => {
65
+ EventNotifier.init(mockElement, { notifyOnStart: true });
66
+ expect(parent.postMessage).toHaveBeenCalledTimes(1);
67
+ });
68
+
69
+ it('does not immediately postMessage if "notifyOnStart" is false', () => {
70
+ EventNotifier.init(mockElement, { notifyOnStart: false });
71
+ expect(parent.postMessage).not.toHaveBeenCalled();
72
+ });
73
+
74
+ it('does postMessage when the event listener is triggered', () => {
75
+ mockElement.addEventListener = jest.fn(() => {
76
+ parent.postMessage();
77
+ });
78
+ EventNotifier.init(mockElement);
79
+ expect(parent.postMessage).toHaveBeenCalledTimes(2);
80
+ });
81
+
82
+ it('does postMessage when the event listener is triggered even if notifyOnStart is false', () => {
83
+ mockElement.addEventListener = jest.fn(() => {
84
+ parent.postMessage();
85
+ });
86
+ EventNotifier.init(mockElement, { notifyOnStart: false });
87
+ expect(parent.postMessage).toHaveBeenCalledTimes(1);
88
+ });
89
+
90
+ it('does postMessage with the element clientHeight + default buffer value as the message height property by default', () => {
91
+ const expectedPayload = JSON.stringify({
92
+ [defaults.emitProperty]:
93
+ mockElement[defaults.watchedProperty] + defaults.buffer,
94
+ });
95
+ EventNotifier.init(mockElement, { notifyOnStart: true });
96
+ expect(parent.postMessage).toHaveBeenCalledWith(expectedPayload, '*');
97
+ });
98
+
99
+ it('can override emitted, watched and buffer properties', () => {
100
+ const expectedPayload = JSON.stringify({
101
+ width: mockElement.clientWidth + 10,
102
+ });
103
+ EventNotifier.init(mockElement, {
104
+ emitProperty: 'width',
105
+ watchedProperty: 'clientWidth',
106
+ buffer: 10,
107
+ });
108
+ expect(parent.postMessage).toHaveBeenCalledWith(expectedPayload, '*');
109
+ });
110
+
111
+ it('can take an alternative window to notify', () => {
112
+ EventNotifier.init(mockElement, { targetWindow: alternativeWindow });
113
+ expect(alternativeWindow.postMessage).toHaveBeenCalledTimes(1);
114
+ expect(parent.postMessage).not.toHaveBeenCalled();
115
+ });
116
+ });
@@ -0,0 +1,71 @@
1
+ const FormElement = require('./form-element');
2
+
3
+ describe('FormElement', () => {
4
+ let formElement;
5
+ let document;
6
+ let addStub;
7
+ let removeStub;
8
+
9
+ beforeEach(() => {
10
+ addStub = jest.fn();
11
+ removeStub = jest.fn();
12
+ document = {
13
+ querySelector: () => {
14
+ return {
15
+ querySelectorAll: () => {},
16
+ querySelector: () => {
17
+ return {
18
+ value: 'test',
19
+ };
20
+ },
21
+ classList: {
22
+ add: addStub,
23
+ remove: removeStub,
24
+ },
25
+ };
26
+ },
27
+ };
28
+ formElement = new FormElement(document);
29
+ });
30
+
31
+ afterEach(() => {
32
+ jest.clearAllMocks();
33
+ });
34
+
35
+ describe('constructor', () => {
36
+ it('throws an error if document element isn not passed in', () => {
37
+ expect(() => {
38
+ new FormElement();
39
+ }).toThrow();
40
+ });
41
+
42
+ it('throws an error if form element does not exist on the page', () => {
43
+ expect(() => {
44
+ document.querySelector = () => {};
45
+ new FormElement(document);
46
+ }).toThrow();
47
+ });
48
+ });
49
+
50
+ describe('hide', () => {
51
+ it('adds the ncf__hidden class', () => {
52
+ formElement.hide();
53
+
54
+ expect(addStub).toHaveBeenCalledWith('ncf__hidden');
55
+ });
56
+ });
57
+
58
+ describe('show', () => {
59
+ it('removes the ncf__hidden class', () => {
60
+ formElement.show();
61
+
62
+ expect(removeStub).toHaveBeenCalledWith('ncf__hidden');
63
+ });
64
+ });
65
+
66
+ describe('value', () => {
67
+ it('returns the value', () => {
68
+ expect(formElement.value()).toEqual('test');
69
+ });
70
+ });
71
+ });