@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
@@ -104,11 +104,11 @@ describe('DeliverySecurityInstructions', () => {
104
104
  expect(textAreaProps.name).toBe('inputId');
105
105
  });
106
106
 
107
- it('set maxLength to 10', () => {
107
+ it('sets maxLength to 10', () => {
108
108
  expect(textAreaProps.maxLength).toBe(10);
109
109
  });
110
110
 
111
- it('set rows to 3', () => {
111
+ it('sets rows to 3', () => {
112
112
  expect(textAreaProps.rows).toBe(3);
113
113
  });
114
114
 
@@ -120,7 +120,7 @@ describe('DeliverySecurityInstructions', () => {
120
120
  expect(textAreaProps.disabled).toBeTrue;
121
121
  });
122
122
 
123
- it('set value', () => {
123
+ it('sets value', () => {
124
124
  expect(textAreaProps.defaultValue).toBe('value');
125
125
  });
126
126
  });
@@ -24,7 +24,7 @@ describe('GraduationDate', () => {
24
24
  ).toBe(true);
25
25
  });
26
26
 
27
- it('should display graduationDateMonth options as English month names', () => {
27
+ it('displays graduationDateMonth options as English month names', () => {
28
28
  const wrapper = shallow(<GraduationDate />);
29
29
  const values = wrapper
30
30
  .find('#graduationDateMonth option')
@@ -45,7 +45,7 @@ describe('GraduationDate', () => {
45
45
  ]);
46
46
  });
47
47
 
48
- it('should store graduationDateMonth values as two-digit numbers', () => {
48
+ it('stores graduationDateMonth values as two-digit numbers', () => {
49
49
  const wrapper = shallow(<GraduationDate />);
50
50
  const values = wrapper
51
51
  .find('#graduationDateMonth option')
@@ -85,13 +85,13 @@ describe('GraduationDate', () => {
85
85
  describe('given a valid value prop is passed', () => {
86
86
  const wrapper = shallow(<GraduationDate value="2020-08-28" />);
87
87
 
88
- it('should set the default month', () => {
88
+ it('sets the default month', () => {
89
89
  expect(wrapper.find('#graduationDateMonth').prop('defaultValue')).toEqual(
90
90
  '08'
91
91
  );
92
92
  });
93
93
 
94
- it('should set the default year', () => {
94
+ it('sets the default year', () => {
95
95
  expect(wrapper.find('#graduationDateYear').prop('defaultValue')).toEqual(
96
96
  '2020'
97
97
  );
@@ -101,7 +101,7 @@ describe('GraduationDate', () => {
101
101
  describe('given an invalid value prop is passed', () => {
102
102
  const wrapper = shallow(<GraduationDate value="invalid" />);
103
103
 
104
- it('should not set any default values', () => {
104
+ it('does not set any default values', () => {
105
105
  expect(
106
106
  wrapper.find('#graduationDateMonth').prop('defaultValue')
107
107
  ).toBeFalsy();
@@ -114,7 +114,7 @@ describe('GraduationDate', () => {
114
114
  describe('given the isDisabled prop is set to true', () => {
115
115
  const wrapper = shallow(<GraduationDate isDisabled={true} />);
116
116
 
117
- it('should disable both select fields', () => {
117
+ it('disables both select fields', () => {
118
118
  expect(
119
119
  wrapper.find('#graduationDateMonth').prop('disabled')
120
120
  ).toBeTruthy();
@@ -125,7 +125,7 @@ describe('GraduationDate', () => {
125
125
  describe('given the isRequired prop is set to true', () => {
126
126
  const wrapper = shallow(<GraduationDate isRequired={true} />);
127
127
 
128
- it('should make both fields required', () => {
128
+ it('makes both fields required', () => {
129
129
  expect(
130
130
  wrapper.find('#graduationDateMonth').prop('required')
131
131
  ).toBeTruthy();
@@ -136,7 +136,7 @@ describe('GraduationDate', () => {
136
136
  describe('given the hasError prop is set to true', () => {
137
137
  const wrapper = shallow(<GraduationDate hasError={true} />);
138
138
 
139
- it('should add an invalid modifier class', () => {
139
+ it('adds an invalid modifier class', () => {
140
140
  expect(wrapper.find('div.o-forms-input').prop('className')).toMatch(
141
141
  'o-forms-input--invalid'
142
142
  );
@@ -134,19 +134,19 @@ describe('PaymentTerm', () => {
134
134
  />
135
135
  );
136
136
 
137
- it('should not include renewal text', () => {
137
+ it('does not include renewal text', () => {
138
138
  expect(
139
139
  wrapper.find('.ncf__payment-term__renews-text').text()
140
140
  ).not.toMatch(/Renews (annually|monthly|quarterly) unless cancelled/);
141
141
  });
142
142
 
143
- it('should render fixed term renewal text in English', () => {
143
+ it('renders fixed term renewal text in English', () => {
144
144
  expect(wrapper.find('.ncf__payment-term__renews-text').text()).toMatch(
145
145
  /This subscription is for 3 months, charged monthly. You can cancel at anytime/
146
146
  );
147
147
  });
148
148
 
149
- it('should render offer name on payment term title', () => {
149
+ it('renders offer name on payment term title', () => {
150
150
  expect(wrapper.find('.ncf__payment-term__title').text()).toMatch(
151
151
  'Mix & Match - Monthly'
152
152
  );
@@ -0,0 +1,11 @@
1
+ const helpers = require('.');
2
+
3
+ describe('helpers', () => {
4
+ it('exports a "ncf-common-data" helper', () => {
5
+ expect(helpers).toHaveProperty('ncf-common-data');
6
+ });
7
+
8
+ it('exports a "ncf-countries" helper', () => {
9
+ expect(helpers).toHaveProperty('ncf-countries');
10
+ });
11
+ });
@@ -0,0 +1,34 @@
1
+ const mockCommonModule = {
2
+ example: 'example',
3
+ another: 'sample',
4
+ nested: {
5
+ property: 'no-problem',
6
+ },
7
+ };
8
+ jest.mock('n-common-static-data', () => mockCommonModule);
9
+ const helper = require('./ncf-common-data');
10
+
11
+ describe('ncf-common-data', () => {
12
+ let stub;
13
+
14
+ beforeEach(() => {
15
+ stub = jest.fn();
16
+ });
17
+
18
+ it('imports and exports the properties as defined', () => {
19
+ const hash = { import: 'example', export: 'whatever' };
20
+ helper({ hash, fn: stub });
21
+ const context = stub.mock.calls[0][0];
22
+ expect(context).toHaveProperty(hash.export, mockCommonModule[hash.import]);
23
+ });
24
+
25
+ it('can import nested properties', () => {
26
+ const hash = { import: 'nested.property', export: 'whatever' };
27
+ helper({ hash, fn: stub });
28
+ const context = stub.mock.calls[0][0];
29
+ expect(context).toHaveProperty(
30
+ hash.export,
31
+ mockCommonModule.nested.property
32
+ );
33
+ });
34
+ });
@@ -0,0 +1,136 @@
1
+ let mockCountries = generateCountryArray(100, { includeAllFrequent: true });
2
+ jest.mock('n-common-static-data', () => {
3
+ return {
4
+ billingCountries: { countries: mockCountries },
5
+ };
6
+ });
7
+ jest.mock('n-common-static-data');
8
+ const helper = require('./ncf-countries');
9
+
10
+ describe('ncf-countries', () => {
11
+ let stub;
12
+
13
+ beforeEach(() => {
14
+ stub = jest.fn();
15
+ });
16
+
17
+ afterEach(() => {
18
+ jest.clearAllMocks();
19
+ });
20
+
21
+ describe('filter', () => {
22
+ it('does not filter if not present', () => {
23
+ helper({ hash: {}, fn: stub });
24
+ const context = stub.mock.calls[0][0];
25
+
26
+ expect(context.countries[1].countries).toEqual(mockCountries);
27
+ });
28
+
29
+ it('does not filter if not an array', () => {
30
+ helper({ hash: { filterList: 'C-1' }, fn: stub });
31
+ const context = stub.mock.calls[0][0];
32
+
33
+ expect(context.countries[1].countries).toEqual(mockCountries);
34
+ });
35
+
36
+ it('filters and return only countries in array', () => {
37
+ helper({ hash: { filterList: ['C-1'] }, fn: stub });
38
+ const context = stub.mock.calls[0][0];
39
+
40
+ expect(context.countries).toEqual([{ code: 'C-1' }]);
41
+ });
42
+ });
43
+
44
+ describe('select', () => {
45
+ it('does not mark any countries selected if no value passed', () => {
46
+ helper({ hash: {}, fn: stub });
47
+ const context = stub.mock.calls[0][0];
48
+
49
+ expect(
50
+ context.countries.find((country) => country.selected)
51
+ ).not.toBeDefined();
52
+ });
53
+
54
+ it('does not mark any countries selected if incorrect value passed', () => {
55
+ helper({ hash: { value: 'Test' }, fn: stub });
56
+ const context = stub.mock.calls[0][0];
57
+
58
+ expect(
59
+ context.countries.find((country) => country.selected)
60
+ ).not.toBeDefined();
61
+ });
62
+
63
+ it('marks a country as selected if value matches code', () => {
64
+ const value = 'C-2';
65
+ helper({ hash: { value }, fn: stub });
66
+ const context = stub.mock.calls[0][0];
67
+
68
+ expect(
69
+ context.countries[1].countries.find((country) => country.code === value)
70
+ ).toBeDefined();
71
+ });
72
+ });
73
+
74
+ describe('group', () => {
75
+ it('does not group if frequently used countries under limit', () => {
76
+ helper({ hash: { filterList: ['CAN', 'FRA', 'JPN', 'USA'] }, fn: stub });
77
+ const context = stub.mock.calls[0][0];
78
+
79
+ expect(context.countries[0]).not.toHaveProperty('label');
80
+ });
81
+
82
+ it('groups countries over limits', () => {
83
+ helper({ hash: {}, fn: stub });
84
+ const context = stub.mock.calls[0][0];
85
+
86
+ expect(context.countries[0]).toHaveProperty('label');
87
+ });
88
+
89
+ it('sorts the group of frequently used countries', () => {
90
+ helper({ hash: {}, fn: stub });
91
+ const context = stub.mock.calls[0][0];
92
+ const correctOrder = ['GBR', 'USA', 'JPN', 'FRA', 'CAN'];
93
+ const frequentlyUsed = context.countries[0].countries;
94
+
95
+ frequentlyUsed.forEach((item, index) => {
96
+ expect(correctOrder[index]).toEqual(item.code);
97
+ });
98
+ });
99
+
100
+ it('selects only one country if it exists in two places', () => {
101
+ const value = 'GBR';
102
+ helper({ hash: { value }, fn: stub });
103
+ const context = stub.mock.calls[0][0];
104
+ const frequentlyUsed = context.countries[0].countries;
105
+ const alphabetical = context.countries[1].countries;
106
+
107
+ expect(frequentlyUsed.find((item) => item.code === value)).toEqual(
108
+ expect.objectContaining({
109
+ selected: true,
110
+ })
111
+ );
112
+ expect(alphabetical.find((item) => item.code === value)).toEqual(
113
+ expect.objectContaining({
114
+ selected: false,
115
+ })
116
+ );
117
+ });
118
+ });
119
+ });
120
+
121
+ function generateCountryArray (length, { includeAllFrequent = true } = {}) {
122
+ return Array.from(Array(length), (item, index) => ({
123
+ code: `C-${index}`,
124
+ })).concat(
125
+ // prettier-ignore
126
+ includeAllFrequent
127
+ ? [
128
+ { code: 'JPN' },
129
+ { code: 'FRA' },
130
+ { code: 'USA' },
131
+ { code: 'CAN' },
132
+ { code: 'GBR' },
133
+ ]
134
+ : []
135
+ );
136
+ }
package/jest.config.js CHANGED
@@ -9,8 +9,11 @@ module.exports = {
9
9
  },
10
10
  testMatch: [
11
11
  '**/components/**/?(*.)+(spec|test).[tj]s?(x)',
12
+ '**/helpers/**/?(*.)+(spec|test).[tj]s?(x)',
12
13
  '**/utils/**/?(*.)+(spec|test).[tj]s?(x)',
13
14
  ],
14
15
  snapshotSerializers: ['jest-serializer-html'],
15
16
  setupFilesAfterEnv: ['<rootDir>/test-jest/helpers/setup.js'],
17
+ resolver: '@financial-times/jest-browser-resolver',
18
+ transformIgnorePatterns: ['/node_modules//(?!(@financial-times)/)'],
16
19
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/n-conversion-forms",
3
- "version": "23.0.5",
3
+ "version": "23.0.6",
4
4
  "description": "Containing jsx components and styles for forms included on Accounts and Acqusition apps (next-signup, next-profile, next-retention, etc).",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -38,6 +38,7 @@
38
38
  "@babel/preset-env": "^7.11.0",
39
39
  "@babel/preset-react": "^7.10.4",
40
40
  "@financial-times/eslint-config-next": "^3.0.0",
41
+ "@financial-times/jest-browser-resolver": "^1.0.2",
41
42
  "@financial-times/n-gage": "^8.2.0",
42
43
  "@storybook/addon-a11y": "^6.0.10",
43
44
  "@storybook/addon-essentials": "6.0.10",
@@ -0,0 +1,68 @@
1
+ const AppBanner = require('./app-banner');
2
+
3
+ describe('Apple Pay', () => {
4
+ let window;
5
+ let element;
6
+ let iosAction;
7
+ let androidAction;
8
+
9
+ beforeEach(() => {
10
+ iosAction = { remove: jest.fn() };
11
+ androidAction = { remove: jest.fn() };
12
+ const classNameMap = {
13
+ '.ncf__app-banner-action--android': androidAction,
14
+ '.ncf__app-banner-action--ios': iosAction,
15
+ };
16
+ element = {
17
+ querySelector: jest.fn((className) => {
18
+ return classNameMap[className];
19
+ }),
20
+ };
21
+ window = {
22
+ document: { querySelector: jest.fn(() => element) },
23
+ navigator: { userAgent: '' },
24
+ };
25
+ });
26
+
27
+ afterEach(() => {
28
+ jest.clearAllMocks();
29
+ });
30
+
31
+ describe('constructor', () => {
32
+ it('throws an error if window not provided', () => {
33
+ expect(() => {
34
+ new AppBanner();
35
+ }).toThrow();
36
+ });
37
+
38
+ it('throws an error if banner not found', () => {
39
+ window.document.querySelector = jest.fn(() => false);
40
+ expect(() => {
41
+ new AppBanner(window);
42
+ }).toThrow();
43
+ });
44
+
45
+ it('does not remove any actions if the user agent is not sniffed', () => {
46
+ new AppBanner(window);
47
+
48
+ expect(iosAction.remove).not.toHaveBeenCalled();
49
+ expect(androidAction.remove).not.toHaveBeenCalled();
50
+ });
51
+
52
+ it('removes ios action if the user agent is android', () => {
53
+ window.navigator.userAgent = 'android';
54
+ new AppBanner(window);
55
+
56
+ expect(iosAction.remove).toHaveBeenCalled();
57
+ expect(androidAction.remove).not.toHaveBeenCalled();
58
+ });
59
+
60
+ it('removes android action if the user agent is ios', () => {
61
+ window.navigator.userAgent = 'iphone';
62
+ new AppBanner(window);
63
+
64
+ expect(iosAction.remove).not.toHaveBeenCalled();
65
+ expect(androidAction.remove).toHaveBeenCalled();
66
+ });
67
+ });
68
+ });
@@ -0,0 +1,177 @@
1
+ const ApplePay = require('./apple-pay');
2
+
3
+ describe('Apple Pay', () => {
4
+ let request;
5
+ let window;
6
+ let event;
7
+ let applePay;
8
+
9
+ beforeEach(() => {
10
+ request = { canMakePayment: function () {}, show: function () {} };
11
+ window = {
12
+ PaymentRequest: function () {
13
+ return request;
14
+ },
15
+ fetch: function () {
16
+ return Promise.resolve({ json: function () {} });
17
+ },
18
+ };
19
+ event = { complete: function () {} };
20
+
21
+ jest.spyOn(request, 'canMakePayment');
22
+ jest.spyOn(request, 'show');
23
+ jest.spyOn(window, 'PaymentRequest');
24
+ jest.spyOn(window, 'fetch');
25
+ jest.spyOn(event, 'complete');
26
+ });
27
+
28
+ afterEach(() => {
29
+ jest.clearAllMocks();
30
+ });
31
+
32
+ describe('public methods', () => {
33
+ describe('constructor', () => {
34
+ it('throws an error if PaymentRequest not available', () => {
35
+ window.PaymentRequest = null;
36
+ expect(() => {
37
+ new ApplePay(window);
38
+ }).toThrow();
39
+ });
40
+
41
+ it('creates the PaymentRequest with the defaults', () => {
42
+ new ApplePay(window);
43
+ expect(window.PaymentRequest).toHaveBeenCalledWith(
44
+ ApplePay.PAYMENT_METHODS,
45
+ ApplePay.PAYMENT_DETAILS,
46
+ ApplePay.PAYMENT_OPTIONS
47
+ );
48
+ });
49
+ });
50
+
51
+ describe('canMakePayment', () => {
52
+ it('proxies canMakePayment to the request', () => {
53
+ applePay = new ApplePay(window);
54
+ applePay.canMakePayment();
55
+ expect(request.canMakePayment).toHaveBeenCalled();
56
+ });
57
+ });
58
+
59
+ describe('show', () => {
60
+ it('proxies show to the request', () => {
61
+ applePay = new ApplePay(window);
62
+ applePay.show();
63
+ expect(request.show).toHaveBeenCalled();
64
+ });
65
+
66
+ it('does not create new request if no details passed', () => {
67
+ applePay = new ApplePay(window);
68
+ applePay.show();
69
+ expect(window.PaymentRequest).toHaveBeenCalledTimes(1);
70
+ });
71
+
72
+ it('creates new request if new details passed', () => {
73
+ applePay = new ApplePay(window);
74
+ applePay.show({ total: { label: 'new' } });
75
+ expect(window.PaymentRequest).toHaveBeenCalledTimes(2);
76
+ });
77
+
78
+ it('creates new request with right parameters', () => {
79
+ applePay = new ApplePay(window);
80
+ applePay.show({ total: { label: 'new' } });
81
+ expect(window.PaymentRequest).toHaveBeenCalledWith(
82
+ applePay.methods,
83
+ applePay.details,
84
+ applePay.options
85
+ );
86
+ });
87
+
88
+ it('setup onmerchantvalidation method', () => {
89
+ delete request.onmerchantvalidation;
90
+ applePay = new ApplePay(window);
91
+ applePay.show();
92
+ expect(request.onmerchantvalidation).toBeInstanceOf(Function);
93
+ });
94
+ });
95
+
96
+ describe('handleMerchantValidation', () => {
97
+ it('uses the production merchant validation URL by default', () => {
98
+ applePay = new ApplePay(window);
99
+ applePay.handleMerchantValidation(event);
100
+ expect(window.fetch).toHaveBeenCalledWith(
101
+ ApplePay.MERCHANT_VALIDATION_URL,
102
+ expect.anything()
103
+ );
104
+ });
105
+
106
+ it('uses the test merchant validation URL', () => {
107
+ applePay = new ApplePay(window, ApplePay.TEST_PAYMENT_METHODS);
108
+ applePay.handleMerchantValidation(event);
109
+ expect(window.fetch).toHaveBeenCalledWith(
110
+ ApplePay.TEST_MERCHANT_VALIDATION_URL,
111
+ expect.anything()
112
+ );
113
+ });
114
+
115
+ it('calls event.complete with response', async () => {
116
+ applePay = new ApplePay(window);
117
+ await applePay.handleMerchantValidation(event);
118
+ expect(event.complete).toHaveBeenCalled();
119
+ });
120
+ });
121
+ });
122
+
123
+ describe('static methods', () => {
124
+ describe('getMerchantId', () => {
125
+ it('returns default merchant id', () => {
126
+ expect(ApplePay.getMerchantId()).toEqual(expect.any(String));
127
+ });
128
+
129
+ it('returns default merchant id if methods has no data', () => {
130
+ const methods = [{}];
131
+ expect(ApplePay.getMerchantId(methods)).toEqual(expect.any(String));
132
+ });
133
+
134
+ it('returns default merchant id if methods data has not got one', () => {
135
+ const methods = [{ data: {} }];
136
+ expect(ApplePay.getMerchantId(methods)).toEqual(expect.any(String));
137
+ });
138
+
139
+ it('returns merchant id from methods data', () => {
140
+ const methods = [{ data: { merchantIdentifier: 'test' } }];
141
+ expect(ApplePay.getMerchantId(methods)).toEqual('test');
142
+ });
143
+ });
144
+
145
+ describe('getMerchantValidationUrl', () => {
146
+ it('returns production merchant validation url', () => {
147
+ expect(ApplePay.getMerchantValidationUrl(ApplePay.MERCHANT_ID)).toEqual(
148
+ ApplePay.MERCHANT_VALIDATION_URL
149
+ );
150
+ });
151
+
152
+ it('returns a differnt merchant validation url for test merchant', () => {
153
+ expect(
154
+ ApplePay.getMerchantValidationUrl(ApplePay.TEST_MERCHANT_ID)
155
+ ).toEqual(ApplePay.TEST_MERCHANT_VALIDATION_URL);
156
+ });
157
+
158
+ it('defaults to the production merchant validation url', () => {
159
+ expect(ApplePay.getMerchantValidationUrl()).toEqual(
160
+ ApplePay.MERCHANT_VALIDATION_URL
161
+ );
162
+ });
163
+ });
164
+
165
+ describe('getPaymentDetails', () => {
166
+ it('formats given values in payment details object', () => {
167
+ const label = 'test';
168
+ const value = 1.01;
169
+ const currency = 'GBP';
170
+ const details = ApplePay.getPaymentDetails(value, currency, label);
171
+
172
+ expect(details.total.label).toEqual(label);
173
+ expect(details.total.amount).toEqual({ value, currency });
174
+ });
175
+ });
176
+ });
177
+ });
@@ -0,0 +1,87 @@
1
+ const BillingCountry = require('./billing-country');
2
+
3
+ describe('BillingCountry', () => {
4
+ let billingCountry;
5
+ let documentStub;
6
+ let elementStub;
7
+
8
+ beforeEach(() => {
9
+ elementStub = {
10
+ addEventListener: jest.fn(),
11
+ selectedIndex: 1,
12
+ options: [{ value: 0 }, { value: 1 }, { value: 2 }],
13
+ };
14
+ documentStub = {
15
+ querySelector: jest.fn(),
16
+ };
17
+ });
18
+
19
+ afterEach(() => {
20
+ jest.clearAllMocks();
21
+ });
22
+
23
+ describe('constructor', () => {
24
+ it('throws an error if nothing passed', () => {
25
+ expect(() => {
26
+ new BillingCountry();
27
+ }).toThrow();
28
+ });
29
+
30
+ it('throws an error if field not present', () => {
31
+ expect(() => {
32
+ documentStub.querySelector.mockReturnValue(false);
33
+ new BillingCountry(documentStub);
34
+ }).toThrow();
35
+ });
36
+ });
37
+
38
+ describe('constructed', () => {
39
+ beforeEach(() => {
40
+ documentStub.querySelector.mockReturnValue(elementStub);
41
+ billingCountry = new BillingCountry(documentStub);
42
+ });
43
+
44
+ describe('onChange', () => {
45
+ it('adds an event listener on change', () => {
46
+ billingCountry.onChange();
47
+ expect(elementStub.addEventListener).toHaveBeenCalledWith(
48
+ 'change',
49
+ expect.anything()
50
+ );
51
+ });
52
+
53
+ it('calls the callback', () => {
54
+ const callback = jest.fn();
55
+ elementStub.addEventListener = (type, callback) => callback();
56
+ billingCountry.onChange(callback);
57
+ expect(callback).toHaveBeenCalled();
58
+ });
59
+ });
60
+
61
+ describe('getSelected', () => {
62
+ it('throws an error if nothing selected', () => {
63
+ elementStub.options = [];
64
+ elementStub.selectedIndex = 0;
65
+ expect(() => {
66
+ billingCountry.getSelected();
67
+ }).toThrow();
68
+ });
69
+
70
+ it('throws an error if something selected that is not there', () => {
71
+ elementStub.selectedIndex = 4;
72
+ expect(() => {
73
+ billingCountry.getSelected();
74
+ }).toThrow();
75
+ });
76
+
77
+ it('returns the selected option', () => {
78
+ expect(billingCountry.getSelected()).toBe(1);
79
+ });
80
+
81
+ it('returns the changed selected option', () => {
82
+ elementStub.selectedIndex = 0;
83
+ expect(billingCountry.getSelected()).toBe(0);
84
+ });
85
+ });
86
+ });
87
+ });