@rebilly/framepay-react 14.45.6 → 14.46.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/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
- ## 14.45.6 (2026-10-19)
1
+ ## 14.46.0 (2026-10-19)
2
2
 
3
3
  ### 🧱 Updated Dependencies
4
4
 
5
- - Updated framepay-npm to 5.242.6
5
+ - Updated framepay-npm to 5.243.0
package/build/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SUPPORTED_CARD_BRANDS } from './lib/constants';
2
2
  import { withFramePay, withFramePayApplePayComponent, withFramePaySamsungPayComponent, withFramePayBankComponent, withFramePayCardComponent, withFramePayGooglePayComponent, withFramePayIBANComponent, withFramePayPaypalComponent } from './lib/components/injector';
3
3
  import FramePayProvider from './lib/components/provider';
4
- import { FramePayApplePayProps, FramePaySamsungPayProps, FramePayBankProps, FramePayCardProps, FramePayComponentProps, FramePayGooglePayProps, FramePayIBANProps, FramePayPaypalProps } from '../types/injector';
5
- export { SUPPORTED_CARD_BRANDS, FramePayProvider, withFramePay, withFramePayCardComponent, withFramePayBankComponent, withFramePayIBANComponent, withFramePayApplePayComponent, withFramePaySamsungPayComponent, withFramePayGooglePayComponent, withFramePayPaypalComponent, FramePayComponentProps, FramePayCardProps, FramePayBankProps, FramePayIBANProps, FramePayApplePayProps, FramePaySamsungPayProps, FramePayGooglePayProps, FramePayPaypalProps, };
4
+ export { SUPPORTED_CARD_BRANDS, FramePayProvider, withFramePay, withFramePayCardComponent, withFramePayBankComponent, withFramePayIBANComponent, withFramePayApplePayComponent, withFramePaySamsungPayComponent, withFramePayGooglePayComponent, withFramePayPaypalComponent, };
5
+ export type { FramePayApplePayProps, FramePaySamsungPayProps, FramePayBankProps, FramePayCardProps, FramePayComponentProps, FramePayGooglePayProps, FramePayIBANProps, FramePayPaypalProps, } from '../types/injector';
package/build/index.js CHANGED
@@ -17,4 +17,4 @@ Object.defineProperty(exports, "withFramePayIBANComponent", { enumerable: true,
17
17
  Object.defineProperty(exports, "withFramePayPaypalComponent", { enumerable: true, get: function () { return injector_1.withFramePayPaypalComponent; } });
18
18
  const provider_1 = __importDefault(require("./lib/components/provider"));
19
19
  exports.FramePayProvider = provider_1.default;
20
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsK0NBQXdEO0FBMkJwRCxzR0EzQkssaUNBQXFCLE9BMkJMO0FBekJ6Qix3REFTbUM7QUFrQi9CLDZGQTFCQSx1QkFBWSxPQTBCQTtBQUlaLDhHQTdCQSx3Q0FBNkIsT0E2QkE7QUFDN0IsZ0hBN0JBLDBDQUErQixPQTZCQTtBQUgvQiwwR0F6QkEsb0NBQXlCLE9BeUJBO0FBRHpCLDBHQXZCQSxvQ0FBeUIsT0F1QkE7QUFLekIsK0dBM0JBLHlDQUE4QixPQTJCQTtBQUg5QiwwR0F2QkEsb0NBQXlCLE9BdUJBO0FBSXpCLDRHQTFCQSxzQ0FBMkIsT0EwQkE7QUF2Qi9CLHlFQUF5RDtBQWVyRCwyQkFmRyxrQkFBZ0IsQ0FlSCJ9
20
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsK0NBQXdEO0FBZ0JwRCxzR0FoQkssaUNBQXFCLE9BZ0JMO0FBZHpCLHdEQVNtQztBQU8vQiw2RkFmQSx1QkFBWSxPQWVBO0FBSVosOEdBbEJBLHdDQUE2QixPQWtCQTtBQUM3QixnSEFsQkEsMENBQStCLE9Ba0JBO0FBSC9CLDBHQWRBLG9DQUF5QixPQWNBO0FBRHpCLDBHQVpBLG9DQUF5QixPQVlBO0FBS3pCLCtHQWhCQSx5Q0FBOEIsT0FnQkE7QUFIOUIsMEdBWkEsb0NBQXlCLE9BWUE7QUFJekIsNEdBZkEsc0NBQTJCLE9BZUE7QUFaL0IseUVBQXlEO0FBSXJELDJCQUpHLGtCQUFnQixDQUlIIn0=
@@ -49,15 +49,17 @@ exports.REACT_VERSION = React.version;
49
49
  /**
50
50
  * Supported FramePay card types
51
51
  */
52
+ // Literals match SupportedCardBrands. Assertions keep the const enum type
53
+ // without requiring the test compiler to inline the enum.
52
54
  exports.SUPPORTED_CARD_BRANDS = {
53
- Amex: "American Express" /* SupportedCardBrands.Amex */,
54
- AstroPayCard: "AstroPay Card" /* SupportedCardBrands.AstroPayCard */,
55
- Cup: "ChinaUnionPay" /* SupportedCardBrands.Cup */,
56
- DinersClub: "Diners Club" /* SupportedCardBrands.DinersClub */,
57
- Discover: "Discover" /* SupportedCardBrands.Discover */,
58
- JCB: "JCB" /* SupportedCardBrands.JCB */,
59
- Maestro: "Maestro" /* SupportedCardBrands.Maestro */,
60
- MasterCard: "MasterCard" /* SupportedCardBrands.MasterCard */,
61
- Visa: "Visa" /* SupportedCardBrands.Visa */,
55
+ Amex: 'American Express',
56
+ AstroPayCard: 'AstroPay Card',
57
+ Cup: 'ChinaUnionPay',
58
+ DinersClub: 'Diners Club',
59
+ Discover: 'Discover',
60
+ JCB: 'JCB',
61
+ Maestro: 'Maestro',
62
+ MasterCard: 'MasterCard',
63
+ Visa: 'Visa',
62
64
  };
63
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uc3RhbnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2xpYi9jb25zdGFudHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsNkNBQStCO0FBRS9COzs7OztHQUtHO0FBRUg7OztHQUdHO0FBQ1UsUUFBQSxhQUFhLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQztBQUUzQzs7R0FFRztBQUNVLFFBQUEscUJBQXFCLEdBQThCO0lBQzVELElBQUksbURBQTBCO0lBQzlCLFlBQVksd0RBQWtDO0lBQzlDLEdBQUcsK0NBQXlCO0lBQzVCLFVBQVUsb0RBQWdDO0lBQzFDLFFBQVEsK0NBQThCO0lBQ3RDLEdBQUcscUNBQXlCO0lBQzVCLE9BQU8sNkNBQTZCO0lBQ3BDLFVBQVUsbURBQWdDO0lBQzFDLElBQUksdUNBQTBCO0NBQ2pDLENBQUMifQ==
65
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uc3RhbnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2xpYi9jb25zdGFudHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsNkNBQStCO0FBRS9COzs7OztHQUtHO0FBRUg7OztHQUdHO0FBQ1UsUUFBQSxhQUFhLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQztBQUUzQzs7R0FFRztBQUNILDBFQUEwRTtBQUMxRSwwREFBMEQ7QUFDN0MsUUFBQSxxQkFBcUIsR0FBOEI7SUFDNUQsSUFBSSxFQUFFLGtCQUF5QztJQUMvQyxZQUFZLEVBQUUsZUFBc0M7SUFDcEQsR0FBRyxFQUFFLGVBQXNDO0lBQzNDLFVBQVUsRUFBRSxhQUFvQztJQUNoRCxRQUFRLEVBQUUsVUFBaUM7SUFDM0MsR0FBRyxFQUFFLEtBQTRCO0lBQ2pDLE9BQU8sRUFBRSxTQUFnQztJQUN6QyxVQUFVLEVBQUUsWUFBbUM7SUFDL0MsSUFBSSxFQUFFLE1BQTZCO0NBQ3RDLENBQUMifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebilly/framepay-react",
3
- "version": "14.45.6",
3
+ "version": "14.46.0",
4
4
  "description": "A React wrapper for Rebilly's FramePay offering out-of-the-box support for Redux and other common React features",
5
5
  "main": "build/index.js",
6
6
  "author": "Rebilly",
@@ -32,7 +32,8 @@
32
32
  "LICENSE",
33
33
  "env.js",
34
34
  "tsconfig.json",
35
- "tsconfig.spec.json"
35
+ "tsconfig.spec.json",
36
+ "vitest.config.mts"
36
37
  ],
37
38
  "overrides": {
38
39
  "@testing-library/react": {
@@ -52,11 +53,9 @@
52
53
  "devDependencies": {
53
54
  "@playwright/test": "^1.63.0",
54
55
  "@rebilly/playwright-config": "*",
55
- "@fluffy-spoon/substitute": "^1.89.0",
56
56
  "@rebilly/eslint-config": "*",
57
57
  "@small-tech/auto-encrypt-localhost": "^8.4.0",
58
58
  "@testing-library/react": "^15.0.7",
59
- "@types/jest": "^29.5.1",
60
59
  "@types/node": "^24.13.3",
61
60
  "@types/react": "^19.2.7",
62
61
  "core-js": "^3.50.0",
@@ -66,8 +65,7 @@
66
65
  "express": "^5.1.0",
67
66
  "globals": "^17.0.0",
68
67
  "gud": "^1.0.0",
69
- "jest": "^29.5.0",
70
- "jest-environment-jsdom": "^29.7.0",
68
+ "jsdom": "^21.1.1",
71
69
  "npm-run-all2": "^8.0.4",
72
70
  "parcel": "^2.0.0",
73
71
  "portfinder": "^1.0.35",
@@ -76,8 +74,8 @@
76
74
  "react-dom": "^19.2.8",
77
75
  "react-dom-19": "npm:react-dom@^19.2.8",
78
76
  "scheduler": "^0.26.0",
79
- "ts-jest": "^29.4.12",
80
77
  "typescript": "^6.0.3",
78
+ "vitest": "^4.1.0",
81
79
  "warning": "^4.0.3"
82
80
  },
83
81
  "scripts": {
@@ -90,12 +88,14 @@
90
88
  "clean-react-alias": "cross-env REACT_VERSION=clean node ./tests/e2e/switch-react-version",
91
89
  "dotenv": "node ./env.js",
92
90
  "build": "cross-env NODE_ENV=production tsc -p tsconfig.json",
93
- "type:check": "tsc --noEmit -p tsconfig.json",
91
+ "type:check": "tsc --noEmit -p tsconfig.json && pnpm type:check:unit",
92
+ "type:check:unit": "tsc --noEmit -p tsconfig.spec.json",
94
93
  "build:e2e": "cross-env NODE_ENV=production run-s dotenv build clean:e2e && parcel build ./tests/e2e/fixtures/* --no-optimize --no-cache --no-scope-hoist --dist-dir tests/e2e/build",
95
94
  "dev:e2e": "cross-env NODE_ENV=development && run-s dotenv && parcel ./tests/e2e/fixtures/* --no-cache --dist-dir tests/e2e/build",
96
95
  "serve:e2e": "run-s dotenv build:* && node tests/e2e/local-server.mjs",
97
96
  "serve:e2e:no-build": "node tests/e2e/local-server.mjs",
98
- "test:unit": "jest --config ./tests/unit/jest.config.js",
97
+ "test:unit": "vitest run",
98
+ "test:unit:watch": "vitest",
99
99
  "test:e2e:base": "pnpm test:e2e:run",
100
100
  "test:e2e:react-14": "cross-env REACT_VERSION=0.14.0 run-s set-react-14 dotenv build:e2e test:e2e:base",
101
101
  "test:e2e:react-15": "cross-env REACT_VERSION=15.0.0 run-s set-react-15 dotenv build:e2e test:e2e:base",
package/src/index.ts CHANGED
@@ -13,17 +13,6 @@ import {
13
13
 
14
14
  import FramePayProvider from './lib/components/provider';
15
15
 
16
- import {
17
- FramePayApplePayProps,
18
- FramePaySamsungPayProps,
19
- FramePayBankProps,
20
- FramePayCardProps,
21
- FramePayComponentProps,
22
- FramePayGooglePayProps,
23
- FramePayIBANProps,
24
- FramePayPaypalProps,
25
- } from '../types/injector';
26
-
27
16
  export {
28
17
  SUPPORTED_CARD_BRANDS,
29
18
  FramePayProvider,
@@ -35,12 +24,15 @@ export {
35
24
  withFramePaySamsungPayComponent,
36
25
  withFramePayGooglePayComponent,
37
26
  withFramePayPaypalComponent,
38
- FramePayComponentProps,
39
- FramePayCardProps,
40
- FramePayBankProps,
41
- FramePayIBANProps,
27
+ };
28
+
29
+ export type {
42
30
  FramePayApplePayProps,
43
31
  FramePaySamsungPayProps,
32
+ FramePayBankProps,
33
+ FramePayCardProps,
34
+ FramePayComponentProps,
44
35
  FramePayGooglePayProps,
36
+ FramePayIBANProps,
45
37
  FramePayPaypalProps,
46
- };
38
+ } from '../types/injector';
@@ -1,136 +1,122 @@
1
- import { Arg, Substitute } from '@fluffy-spoon/substitute';
2
1
  import { render } from '@testing-library/react';
3
2
  import * as React from 'react';
3
+ import { vi } from 'vitest';
4
4
  import FramePayError from '../../framepay-error';
5
5
  import BankElement from './bank-element';
6
- import type { BankProps } from '../../../../types/elements';
7
- import type { Bban, BankElement as BankElementType } from '@rebilly/framepay';
6
+ import { bankProps, fieldDouble } from '../../../../tests/unit/element-props';
8
7
 
9
8
  describe('BankElement', () => {
10
- it('should not setup the element while api is not ready', () => {
11
- const props = Substitute.for<BankProps>();
9
+ afterEach(() => {
10
+ vi.restoreAllMocks();
11
+ });
12
12
 
13
- const setupElementSpy = jest.spyOn(
14
- BankElement.prototype,
15
- 'setupElement',
16
- );
13
+ it('should not setup the element while api is not ready', () => {
14
+ const setupElementSpy = vi.spyOn(BankElement.prototype, 'setupElement');
17
15
 
18
16
  render(
19
- <BankElement
20
- {...props}
21
- Framepay={{
22
- ...props.Framepay,
23
- bban: props.Framepay.bban,
24
- ready: false,
25
- }}
26
- elementType="routingNumber"
27
- />,
17
+ React.createElement(
18
+ BankElement,
19
+ bankProps('routingNumber', { ready: false }),
20
+ ),
28
21
  );
29
22
 
30
23
  expect(setupElementSpy).not.toHaveBeenCalled();
31
24
  });
32
25
 
33
26
  it('should setup the element when api is ready', () => {
34
- const props = Substitute.for<BankProps>();
35
-
36
- const setupElementSpy = jest.spyOn(
37
- BankElement.prototype,
38
- 'setupElement',
39
- );
27
+ const setupElementSpy = vi.spyOn(BankElement.prototype, 'setupElement');
28
+ const mount = vi.fn(() => fieldDouble());
40
29
 
41
30
  render(
42
- <BankElement
43
- {...props}
44
- Framepay={{
45
- ...props.Framepay,
46
- bban: props.Framepay.bban,
31
+ React.createElement(
32
+ BankElement,
33
+ bankProps('accountType', {
47
34
  ready: true,
48
- }}
49
- elementType="accountType"
50
- />,
35
+ bban: { mount },
36
+ }),
37
+ ),
51
38
  );
52
39
 
53
40
  expect(setupElementSpy).toHaveBeenCalled();
54
41
  });
55
42
 
56
- it('should destroy the element on component unmount', (done) => {
57
- const props = Substitute.for<BankProps>();
58
- const element = Substitute.for<BankElementType>();
59
-
60
- element.destroy().mimicks(() => {
61
- done();
62
- });
63
-
64
- props.Framepay.bban.mount(Arg.any(), Arg.any()).returns(element);
43
+ it('should destroy the element on component unmount', () => {
44
+ const element = fieldDouble();
45
+ const mount = vi.fn(() => element);
65
46
 
66
47
  class TmpComponent extends React.Component {
67
48
  render() {
68
- return (
69
- <BankElement
70
- {...props}
71
- Framepay={{
72
- ...props.Framepay,
73
- bban: props.Framepay.bban,
74
- ready: true,
75
- }}
76
- elementType="routingNumber"
77
- />
49
+ return React.createElement(
50
+ BankElement,
51
+ bankProps('routingNumber', {
52
+ ready: true,
53
+ bban: { mount },
54
+ }),
78
55
  );
79
56
  }
80
57
  }
81
58
 
82
59
  const { unmount } = render(<TmpComponent />);
83
60
  unmount();
61
+ expect(element.destroy).toHaveBeenCalled();
84
62
  });
85
63
 
86
64
  it('should render the empty div element', () => {
87
- const props = Substitute.for<BankProps>();
88
-
89
- props.Framepay.ready.returns(true);
65
+ const mount = vi.fn(() => fieldDouble());
90
66
 
91
67
  const { container } = render(
92
- <BankElement {...props} Framepay={props.Framepay} />,
68
+ React.createElement(
69
+ BankElement,
70
+ bankProps('accountNumber', {
71
+ ready: true,
72
+ bban: { mount },
73
+ }),
74
+ ),
93
75
  );
94
76
  expect(container.firstChild).toMatchInlineSnapshot('<div />');
95
77
  });
96
78
 
97
79
  it('should fail the element mount on remote error', () => {
98
- const props = Substitute.for<BankProps>();
99
-
100
80
  try {
101
81
  render(
102
- <BankElement
103
- {...props}
104
- Framepay={{
105
- ...props.Framepay,
82
+ React.createElement(
83
+ BankElement,
84
+ bankProps('accountNumber', {
85
+ ready: true,
106
86
  bban: {
107
- ...props.Framepay.bban,
108
- mount: null,
87
+ mount: () => {
88
+ throw new Error('remote mount failed');
89
+ },
109
90
  },
110
- ready: true,
111
- }}
112
- elementType="accountNumber"
113
- />,
91
+ }),
92
+ ),
114
93
  );
115
94
  // never
116
95
  expect(true).toEqual(false);
117
96
  } catch (error) {
118
- expect(error.code).toEqual(FramePayError.codes.elementMountError);
97
+ expect(error).toMatchObject({
98
+ code: FramePayError.codes.elementMountError,
99
+ });
119
100
  }
120
101
  });
121
102
 
122
103
  it('mounts its element type through Framepay.bban', () => {
123
- const props = Substitute.for<BankProps>();
124
- const bban = Substitute.for<Bban>();
104
+ const mount = vi.fn(() => fieldDouble());
125
105
 
126
106
  render(
127
- <BankElement
128
- {...props}
129
- Framepay={{ ...props.Framepay, bban, ready: true }}
130
- elementType="accountNumber"
131
- />,
107
+ React.createElement(
108
+ BankElement,
109
+ bankProps('accountNumber', {
110
+ ready: true,
111
+ bban: { mount },
112
+ }),
113
+ ),
132
114
  );
133
115
 
134
- bban.received(1).mount(Arg.any(), 'accountNumber');
116
+ expect(mount).toHaveBeenCalledTimes(1);
117
+ expect(mount).toHaveBeenCalledWith(
118
+ expect.any(HTMLElement),
119
+ 'accountNumber',
120
+ );
135
121
  });
136
122
  });
@@ -1,114 +1,102 @@
1
- import { Substitute } from '@fluffy-spoon/substitute';
2
1
  import { render } from '@testing-library/react';
3
2
  import * as React from 'react';
3
+ import { vi } from 'vitest';
4
4
  import FramePayError from '../../framepay-error';
5
5
  import CardElement from './card-element';
6
- import type { CardElement as CardElementType } from '@rebilly/framepay';
6
+ import { cardProps, fieldDouble } from '../../../../tests/unit/element-props';
7
7
 
8
8
  describe('CardElement', () => {
9
- it('should not setup the element while api is not ready', () => {
10
- const props = Substitute.for<CardProps>();
11
-
12
- props.Framepay.ready.returns(false);
9
+ afterEach(() => {
10
+ vi.restoreAllMocks();
11
+ });
13
12
 
14
- const setupElementSpy = jest.spyOn(
15
- CardElement.prototype,
16
- 'setupElement',
17
- );
13
+ it('should not setup the element while api is not ready', () => {
14
+ const setupElementSpy = vi.spyOn(CardElement.prototype, 'setupElement');
18
15
 
19
- render(<CardElement {...props} Framepay={props.Framepay} />);
16
+ render(React.createElement(CardElement, cardProps({ ready: false })));
20
17
 
21
18
  expect(setupElementSpy).not.toHaveBeenCalled();
22
19
  });
23
20
 
24
21
  it('should setup the element when api is ready', () => {
25
- const props = Substitute.for<CardProps>();
26
- const setupElementSpy = jest.spyOn(
27
- CardElement.prototype,
28
- 'setupElement',
29
- );
22
+ const setupElementSpy = vi.spyOn(CardElement.prototype, 'setupElement');
23
+ const mount = vi.fn(() => fieldDouble());
30
24
 
31
25
  const { rerender } = render(
32
- <CardElement
33
- Framepay={{
34
- ready: false,
35
- }}
36
- />,
26
+ React.createElement(CardElement, cardProps({ ready: false })),
37
27
  );
38
28
 
39
29
  expect(setupElementSpy).toHaveBeenCalledTimes(0);
40
30
 
41
31
  rerender(
42
- <CardElement
43
- Framepay={{
44
- card: props.Framepay.card,
32
+ React.createElement(
33
+ CardElement,
34
+ cardProps({
45
35
  ready: true,
46
- }}
47
- />,
36
+ card: { mount },
37
+ }),
38
+ ),
48
39
  );
49
40
 
50
41
  expect(setupElementSpy).toHaveBeenCalledTimes(1);
51
42
  });
52
43
 
53
44
  it('should render the empty div element', () => {
54
- const props = Substitute.for<CardProps>();
45
+ const mount = vi.fn(() => fieldDouble());
55
46
  const { container } = render(
56
- <CardElement {...props} Framepay={props.Framepay} />,
47
+ React.createElement(
48
+ CardElement,
49
+ cardProps({
50
+ ready: true,
51
+ card: { mount },
52
+ }),
53
+ ),
57
54
  );
58
55
  expect(container.firstChild).toMatchInlineSnapshot('<div />');
59
56
  });
60
57
 
61
- it('should destroy the element on component unmount', (done) => {
62
- const props = Substitute.for<CardProps>();
63
- const element = Substitute.for<CardElementType>();
64
-
65
- element.destroy().mimicks(() => {
66
- done();
67
- });
58
+ it('should destroy the element on component unmount', () => {
59
+ const element = fieldDouble();
60
+ const mount = vi.fn(() => element);
68
61
 
69
62
  class TmpComponent extends React.Component {
70
63
  render() {
71
- return (
72
- <CardElement
73
- {...props}
74
- Framepay={{
75
- ...props.Framepay,
76
- card: {
77
- ...props.Framepay.card,
78
- mount: () => element,
79
- },
80
- ready: true,
81
- }}
82
- />
64
+ return React.createElement(
65
+ CardElement,
66
+ cardProps({
67
+ ready: true,
68
+ card: { mount },
69
+ }),
83
70
  );
84
71
  }
85
72
  }
86
73
 
87
74
  const { unmount } = render(<TmpComponent />);
88
75
  unmount();
76
+ expect(element.destroy).toHaveBeenCalled();
89
77
  });
90
78
 
91
79
  it('should fail the element mount on remote error', () => {
92
- const props = Substitute.for<CardProps>();
93
-
94
80
  try {
95
81
  render(
96
- <CardElement
97
- {...props}
98
- Framepay={{
99
- ...props.Framepay,
82
+ React.createElement(
83
+ CardElement,
84
+ cardProps({
85
+ ready: true,
100
86
  card: {
101
- ...props.Framepay.card,
102
- mount: null,
87
+ mount: () => {
88
+ throw new Error('remote mount failed');
89
+ },
103
90
  },
104
- ready: true,
105
- }}
106
- />,
91
+ }),
92
+ ),
107
93
  );
108
94
  // never
109
95
  expect(true).toEqual(false);
110
96
  } catch (error) {
111
- expect(error.code).toEqual(FramePayError.codes.elementMountError);
97
+ expect(error).toMatchObject({
98
+ code: FramePayError.codes.elementMountError,
99
+ });
112
100
  }
113
101
  });
114
102
  });
@@ -1,105 +1,101 @@
1
- import { Substitute } from '@fluffy-spoon/substitute';
2
1
  import { render } from '@testing-library/react';
3
2
  import * as React from 'react';
3
+ import { vi } from 'vitest';
4
4
  import FramePayError from '../../framepay-error';
5
5
  import IBANElement from './iban-element';
6
- import type { IbanElement as IbanElementType } from '@rebilly/framepay';
6
+ import { fieldDouble, ibanProps } from '../../../../tests/unit/element-props';
7
7
 
8
8
  describe('IBANElement', () => {
9
- it('should not setup the element while api is not ready', () => {
10
- const props = Substitute.for<IBANProps>();
11
-
12
- props.Framepay.ready.returns(false);
9
+ afterEach(() => {
10
+ vi.restoreAllMocks();
11
+ });
13
12
 
14
- const spy = jest.spyOn(IBANElement.prototype, 'setupElement');
13
+ it('should not setup the element while api is not ready', () => {
14
+ const spy = vi.spyOn(IBANElement.prototype, 'setupElement');
15
15
 
16
- render(<IBANElement {...props} Framepay={props.Framepay} />);
16
+ render(React.createElement(IBANElement, ibanProps({ ready: false })));
17
17
  expect(spy).not.toHaveBeenCalled();
18
18
  });
19
19
 
20
20
  it('should setup the element when api is ready', () => {
21
- const props = Substitute.for<IBANProps>();
22
- const spy = jest.spyOn(IBANElement.prototype, 'setupElement');
23
-
24
- props.Framepay.ready.returns(false);
21
+ const spy = vi.spyOn(IBANElement.prototype, 'setupElement');
22
+ const mount = vi.fn(() => fieldDouble());
25
23
 
26
24
  const { rerender } = render(
27
- <IBANElement {...props} Framepay={props.Framepay} />,
25
+ React.createElement(IBANElement, ibanProps({ ready: false })),
28
26
  );
29
27
 
30
28
  expect(spy).toHaveBeenCalledTimes(0);
31
29
 
32
- const nextProps = Substitute.for<IBANProps>();
33
-
34
30
  rerender(
35
- <IBANElement
36
- {...nextProps}
37
- Framepay={{ iban: nextProps.Framepay.iban, ready: true }}
38
- />,
31
+ React.createElement(
32
+ IBANElement,
33
+ ibanProps({
34
+ ready: true,
35
+ iban: { mount },
36
+ }),
37
+ ),
39
38
  );
40
39
 
41
40
  expect(spy).toHaveBeenCalledTimes(1);
42
41
  });
43
42
 
44
43
  it('should render the empty div element', () => {
45
- const props = Substitute.for<IBANProps>();
44
+ const mount = vi.fn(() => fieldDouble());
46
45
  const { container } = render(
47
- <IBANElement {...props} Framepay={props.Framepay} />,
46
+ React.createElement(
47
+ IBANElement,
48
+ ibanProps({
49
+ ready: true,
50
+ iban: { mount },
51
+ }),
52
+ ),
48
53
  );
49
54
  expect(container.firstChild).toMatchInlineSnapshot('<div />');
50
55
  });
51
56
 
52
- it('should destroy the element on component unmount', (done) => {
53
- const props = Substitute.for<IBANProps>();
54
- const element = Substitute.for<IbanElementType>();
55
-
56
- element.destroy().mimicks(() => {
57
- done();
58
- });
57
+ it('should destroy the element on component unmount', () => {
58
+ const element = fieldDouble();
59
+ const mount = vi.fn(() => element);
59
60
 
60
61
  class TmpComponent extends React.Component {
61
62
  render() {
62
- return (
63
- <IBANElement
64
- {...props}
65
- Framepay={{
66
- ...props.Framepay,
67
- iban: {
68
- ...props.Framepay.iban,
69
- mount: () => element,
70
- },
71
- ready: true,
72
- }}
73
- />
63
+ return React.createElement(
64
+ IBANElement,
65
+ ibanProps({
66
+ ready: true,
67
+ iban: { mount },
68
+ }),
74
69
  );
75
70
  }
76
71
  }
77
72
 
78
73
  const { unmount } = render(<TmpComponent />);
79
74
  unmount();
75
+ expect(element.destroy).toHaveBeenCalled();
80
76
  });
81
77
 
82
78
  it('should fail the element mount on remote error', () => {
83
- const props = Substitute.for<IBANProps>();
84
-
85
79
  try {
86
80
  render(
87
- <IBANElement
88
- {...props}
89
- Framepay={{
90
- ...props.Framepay,
81
+ React.createElement(
82
+ IBANElement,
83
+ ibanProps({
84
+ ready: true,
91
85
  iban: {
92
- ...props.Framepay.iban,
93
- mount: null,
86
+ mount: () => {
87
+ throw new Error('remote mount failed');
88
+ },
94
89
  },
95
- ready: true,
96
- }}
97
- />,
90
+ }),
91
+ ),
98
92
  );
99
93
  // never
100
94
  expect(true).toEqual(false);
101
95
  } catch (error) {
102
- expect(error.code).toEqual(FramePayError.codes.elementMountError);
96
+ expect(error).toMatchObject({
97
+ code: FramePayError.codes.elementMountError,
98
+ });
103
99
  }
104
100
  });
105
101
  });
@@ -1,4 +1,3 @@
1
- import { Substitute } from '@fluffy-spoon/substitute';
2
1
  import { render } from '@testing-library/react';
3
2
  import * as React from 'react';
4
3
 
@@ -7,6 +6,7 @@ import { withFramePay } from '../..';
7
6
  import type { FramePayComponentProps } from '../../../types/injector';
8
7
 
9
8
  import { ContextProvider } from '../context';
9
+ import { framepayContext } from '../../../tests/unit/element-props';
10
10
 
11
11
  describe('Injector', () => {
12
12
  it('withFramePay HOC the context data should be provided', () => {
@@ -29,20 +29,15 @@ describe('Injector', () => {
29
29
  }
30
30
  }
31
31
 
32
- const context = Substitute.for<FramePayContext>();
33
32
  const TMPWrapper = withFramePay(TMPComponent);
34
33
 
35
34
  class App extends React.Component {
36
35
  render() {
37
36
  return (
38
37
  <ContextProvider
39
- value={{
40
- ...context,
41
- error: 'withFramePay',
42
- ready: true,
43
- }}
38
+ value={framepayContext('withFramePay', true)}
44
39
  >
45
- <TMPWrapper />
40
+ <TMPWrapper someProperty="value" />
46
41
  </ContextProvider>
47
42
  );
48
43
  }
@@ -79,20 +74,18 @@ describe('Injector', () => {
79
74
  }
80
75
  }
81
76
 
82
- const context = Substitute.for<FramePayContext>();
83
77
  const TMPWrapper = withFramePay(TMPComponent);
84
78
 
85
79
  class App extends React.Component {
86
80
  render() {
87
81
  return (
88
82
  <ContextProvider
89
- value={{
90
- ...context,
91
- error: 'withFramePayCardComponent',
92
- ready: true,
93
- }}
83
+ value={framepayContext(
84
+ 'withFramePayCardComponent',
85
+ true,
86
+ )}
94
87
  >
95
- <TMPWrapper />
88
+ <TMPWrapper someProperty="value" />
96
89
  </ContextProvider>
97
90
  );
98
91
  }
@@ -129,20 +122,18 @@ describe('Injector', () => {
129
122
  }
130
123
  }
131
124
 
132
- const context = Substitute.for<FramePayContext>();
133
125
  const TMPWrapper = withFramePay(TMPComponent);
134
126
 
135
127
  class App extends React.Component {
136
128
  render() {
137
129
  return (
138
130
  <ContextProvider
139
- value={{
140
- ...context,
141
- error: 'withFramePayBankComponent',
142
- ready: true,
143
- }}
131
+ value={framepayContext(
132
+ 'withFramePayBankComponent',
133
+ true,
134
+ )}
144
135
  >
145
- <TMPWrapper />
136
+ <TMPWrapper someProperty="value" />
146
137
  </ContextProvider>
147
138
  );
148
139
  }
@@ -1,5 +1,6 @@
1
1
  import { act, render } from '@testing-library/react';
2
2
  import * as React from 'react';
3
+ import { vi, type Mock } from 'vitest';
3
4
  import Provider from './provider';
4
5
 
5
6
  describe('Provider', () => {
@@ -57,10 +58,10 @@ describe('Provider', () => {
57
58
  describe('Provider on a page where framepay.js is loaded', () => {
58
59
  interface Api {
59
60
  initialized: boolean;
60
- initialize: jest.Mock;
61
- update: jest.Mock;
62
- on: jest.Mock;
63
- off: jest.Mock;
61
+ initialize: Mock;
62
+ update: Mock;
63
+ on: Mock;
64
+ off: Mock;
64
65
  }
65
66
 
66
67
  const page = window as unknown as { Framepay?: Api };
@@ -74,10 +75,10 @@ describe('Provider on a page where framepay.js is loaded', () => {
74
75
  beforeEach(() => {
75
76
  api = {
76
77
  initialized: false,
77
- initialize: jest.fn(() => Promise.resolve()),
78
- update: jest.fn(() => Promise.resolve()),
79
- on: jest.fn(),
80
- off: jest.fn(),
78
+ initialize: vi.fn(() => Promise.resolve()),
79
+ update: vi.fn(() => Promise.resolve()),
80
+ on: vi.fn(),
81
+ off: vi.fn(),
81
82
  };
82
83
  page.Framepay = api;
83
84
  });
@@ -104,7 +105,7 @@ describe('Provider on a page where framepay.js is loaded', () => {
104
105
  handler();
105
106
  }
106
107
  });
107
- const onReady = jest.fn();
108
+ const onReady = vi.fn();
108
109
  const transactionData = { amount: 5, currency: 'USD', label: 'Plan' };
109
110
 
110
111
  render(
@@ -132,8 +133,8 @@ describe('Provider on a page where framepay.js is loaded', () => {
132
133
  });
133
134
  const rejection = { code: 'invalid-methods-configuration' };
134
135
  api.update.mockImplementation(() => Promise.reject(rejection));
135
- const onReady = jest.fn();
136
- const onError = jest.fn();
136
+ const onReady = vi.fn();
137
+ const onError = vi.fn();
137
138
 
138
139
  render(
139
140
  <Provider
@@ -170,15 +171,24 @@ describe('Provider on a page where framepay.js is loaded', () => {
170
171
  handler();
171
172
  }
172
173
  });
173
- let settle: { resolve: () => void; reject: (error: object) => void };
174
- api.update.mockImplementation(
175
- () =>
176
- new Promise<void>((resolve, reject) => {
177
- settle = { resolve, reject };
178
- }),
179
- );
180
- const onReady = jest.fn();
181
- const onError = jest.fn();
174
+ const pendingUpdate = () => {
175
+ let captured:
176
+ | { resolve: () => void; reject: (error: object) => void }
177
+ | undefined;
178
+ const promise = new Promise<void>((resolve, reject) => {
179
+ captured = { resolve, reject };
180
+ });
181
+ if (captured === undefined) {
182
+ throw new Error(
183
+ 'FramePay update did not capture its settlement',
184
+ );
185
+ }
186
+ return { promise, settle: captured };
187
+ };
188
+ let pending = pendingUpdate();
189
+ api.update.mockImplementation(() => pending.promise);
190
+ const onReady = vi.fn();
191
+ const onError = vi.fn();
182
192
 
183
193
  const { unmount } = render(
184
194
  <Provider
@@ -189,17 +199,13 @@ describe('Provider on a page where framepay.js is loaded', () => {
189
199
  );
190
200
  await flush();
191
201
  unmount();
192
- settle.resolve();
202
+ pending.settle.resolve();
193
203
  await flush();
194
204
 
195
205
  expect(onReady).not.toHaveBeenCalled();
196
206
 
197
- api.update.mockImplementation(
198
- () =>
199
- new Promise<void>((resolve, reject) => {
200
- settle = { resolve, reject };
201
- }),
202
- );
207
+ pending = pendingUpdate();
208
+ api.update.mockImplementation(() => pending.promise);
203
209
  const second = render(
204
210
  <Provider
205
211
  publishableKey={publishableKey}
@@ -209,7 +215,7 @@ describe('Provider on a page where framepay.js is loaded', () => {
209
215
  );
210
216
  await flush();
211
217
  second.unmount();
212
- settle.reject({ code: 'invalid-methods-configuration' });
218
+ pending.settle.reject({ code: 'invalid-methods-configuration' });
213
219
  await flush();
214
220
 
215
221
  expect(onError).not.toHaveBeenCalled();
@@ -231,8 +237,8 @@ describe('Provider on a page where framepay.js is loaded', () => {
231
237
  }
232
238
  },
233
239
  );
234
- const onError = jest.fn();
235
- const onReady = jest.fn();
240
+ const onError = vi.fn();
241
+ const onReady = vi.fn();
236
242
 
237
243
  render(
238
244
  <Provider
@@ -16,14 +16,16 @@ export const REACT_VERSION = React.version;
16
16
  /**
17
17
  * Supported FramePay card types
18
18
  */
19
+ // Literals match SupportedCardBrands. Assertions keep the const enum type
20
+ // without requiring the test compiler to inline the enum.
19
21
  export const SUPPORTED_CARD_BRANDS: SupportedCardBrandsObject = {
20
- Amex: SupportedCardBrands.Amex,
21
- AstroPayCard: SupportedCardBrands.AstroPayCard,
22
- Cup: SupportedCardBrands.Cup,
23
- DinersClub: SupportedCardBrands.DinersClub,
24
- Discover: SupportedCardBrands.Discover,
25
- JCB: SupportedCardBrands.JCB,
26
- Maestro: SupportedCardBrands.Maestro,
27
- MasterCard: SupportedCardBrands.MasterCard,
28
- Visa: SupportedCardBrands.Visa,
22
+ Amex: 'American Express' as SupportedCardBrands,
23
+ AstroPayCard: 'AstroPay Card' as SupportedCardBrands,
24
+ Cup: 'ChinaUnionPay' as SupportedCardBrands,
25
+ DinersClub: 'Diners Club' as SupportedCardBrands,
26
+ Discover: 'Discover' as SupportedCardBrands,
27
+ JCB: 'JCB' as SupportedCardBrands,
28
+ Maestro: 'Maestro' as SupportedCardBrands,
29
+ MasterCard: 'MasterCard' as SupportedCardBrands,
30
+ Visa: 'Visa' as SupportedCardBrands,
29
31
  };
@@ -0,0 +1,78 @@
1
+ import { vi, type Mock } from 'vitest';
2
+ import type { FramePay } from '@rebilly/framepay';
3
+ import type { FramePayContext } from '../../types/context';
4
+ import type {
5
+ BankProps,
6
+ CardProps,
7
+ FramepayProps,
8
+ IBANProps,
9
+ } from '../../types/elements';
10
+
11
+ export type FieldDouble = {
12
+ readonly on: Mock;
13
+ readonly destroy: Mock;
14
+ };
15
+
16
+ export function fieldDouble(): FieldDouble {
17
+ return {
18
+ on: vi.fn(),
19
+ destroy: vi.fn(),
20
+ };
21
+ }
22
+
23
+ type Mount = (node: HTMLElement, fieldType?: string) => FieldDouble;
24
+
25
+ export type FramepayStub = {
26
+ readonly ready: boolean;
27
+ readonly bban?: {
28
+ readonly mount: Mount;
29
+ };
30
+ readonly card?: {
31
+ readonly mount: Mount;
32
+ };
33
+ readonly iban?: {
34
+ readonly mount: (node: HTMLElement) => FieldDouble;
35
+ };
36
+ };
37
+
38
+ /**
39
+ * FramepayProps extends the full FramePay API. These tests only touch
40
+ * `ready` and the mount method for one payment method.
41
+ */
42
+ function framepayProps(stub: FramepayStub): FramepayProps {
43
+ return stub as unknown as FramepayProps;
44
+ }
45
+
46
+ export function bankProps(
47
+ elementType: BankProps['elementType'],
48
+ framepay: FramepayStub,
49
+ ): BankProps {
50
+ return {
51
+ Framepay: framepayProps(framepay),
52
+ elementType,
53
+ } as unknown as BankProps;
54
+ }
55
+
56
+ export function cardProps(framepay: FramepayStub): CardProps {
57
+ return {
58
+ Framepay: framepayProps(framepay),
59
+ } as unknown as CardProps;
60
+ }
61
+
62
+ export function ibanProps(framepay: FramepayStub): IBANProps {
63
+ return {
64
+ Framepay: framepayProps(framepay),
65
+ elementType: 'iban',
66
+ } as unknown as IBANProps;
67
+ }
68
+
69
+ export function framepayContext(
70
+ error: string,
71
+ ready: boolean,
72
+ ): FramePayContext {
73
+ return {
74
+ api: {} as unknown as FramePay,
75
+ error,
76
+ ready,
77
+ };
78
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vitest/globals" />
@@ -1,13 +1,15 @@
1
1
  {
2
- "extends": "./tsconfig",
2
+ "extends": "./tsconfig.json",
3
3
  "compilerOptions": {
4
- "strictNullChecks": false
4
+ "noEmit": true,
5
+ "rootDir": "."
5
6
  },
6
7
  "include": [
7
8
  "src/**/*.ts",
8
9
  "src/**/*.tsx",
9
- "tests/**/*.ts",
10
- "tests/**/*.tsx",
10
+ "tests/unit/**/*.ts",
11
+ "tests/unit/**/*.tsx",
11
12
  "types/**/*.d.ts"
12
- ]
13
+ ],
14
+ "exclude": ["node_modules/**"]
13
15
  }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'jsdom',
6
+ globals: true,
7
+ exclude: [
8
+ '**/node_modules/**',
9
+ '**/dist/**',
10
+ '**/build/**',
11
+ '**/e2e/**',
12
+ ],
13
+ },
14
+ });
@@ -1,18 +0,0 @@
1
- module.exports = {
2
- rootDir: '../..',
3
- testEnvironment: 'jsdom',
4
- testPathIgnorePatterns: ['.*/e2e/.*'],
5
- transform: {
6
- '.(ts|tsx)': [
7
- 'ts-jest',
8
- {
9
- tsconfig: 'tsconfig.spec.json',
10
- diagnostics: {
11
- warnOnly: true,
12
- },
13
- },
14
- ],
15
- },
16
- moduleFileExtensions: ['js', 'json', 'ts', 'tsx'],
17
- moduleDirectories: ['node_modules', 'src', 'types'],
18
- };
@@ -1,144 +0,0 @@
1
- import { Arg, Substitute } from '@fluffy-spoon/substitute';
2
- import { render } from '@testing-library/react';
3
- import * as React from 'react';
4
-
5
- /**
6
- * @see https://www.npmjs.com/package/@fluffy-spoon/substitute
7
- */
8
- describe('Mock functionality works correctly', () => {
9
- it('String property of interface', () => {
10
- interface Example {
11
- readonly bar: string;
12
- }
13
-
14
- const baz = 'baz';
15
- const foo = Substitute.for<Example>();
16
-
17
- foo.bar.returns(baz);
18
- const call1 = foo.bar;
19
- const call2 = foo.bar;
20
- expect(call1).toEqual(baz);
21
- expect(call2).toEqual(baz);
22
- });
23
-
24
- it('Can mock parent interface', () => {
25
- interface NestedApi {
26
- readonly nestedMethod: () => boolean;
27
- }
28
-
29
- interface SuperParent {
30
- readonly property1: string;
31
- readonly method1: () => number;
32
- readonly api: NestedApi;
33
- }
34
-
35
- interface Parent extends SuperParent {
36
- readonly property2: string;
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- readonly method2: (arg: any) => string;
39
- }
40
-
41
- interface Child extends Parent {
42
- readonly bar: string;
43
- }
44
-
45
- const params = Substitute.for<Child>();
46
-
47
- params.property1.returns('property1-value');
48
- params.property2.returns('property2-value');
49
- params.method1().returns(10);
50
- params.method2(Arg.any()).returns('method2-value');
51
-
52
- params.api.nestedMethod().returns(false);
53
-
54
- expect(params.property1).toEqual('property1-value');
55
- expect(params.property2).toEqual('property2-value');
56
- expect(params.method1()).toEqual(10);
57
- expect(params.method2(10)).toEqual('method2-value');
58
-
59
- expect(params.api.nestedMethod()).toEqual(false);
60
- });
61
-
62
- it('Extended calls on the same level works correctly', () => {
63
- interface Element {
64
- readonly destroy: () => void;
65
- }
66
-
67
- interface ParentMethod {
68
- readonly mount: () => Element;
69
- }
70
-
71
- interface ChildMethod1 extends ParentMethod {
72
- readonly name: string;
73
- }
74
-
75
- interface ChildMethod2 extends ParentMethod {
76
- readonly name: string;
77
- }
78
-
79
- interface Api {
80
- readonly child1: ChildMethod1;
81
- readonly child2: ChildMethod2;
82
- }
83
-
84
- const api = Substitute.for<Api>();
85
- const spy1 = jest.fn();
86
- const spy2 = jest.fn();
87
-
88
- api.child1.mount().returns(Substitute.for<Element>());
89
-
90
- api.child2.mount().returns(Substitute.for<Element>());
91
-
92
- api.child1
93
- .mount()
94
- .destroy()
95
-
96
- .mimicks(spy1);
97
-
98
- api.child2
99
- .mount()
100
- .destroy()
101
-
102
- .mimicks(spy2);
103
-
104
- const el = api.child1.mount();
105
- el.destroy();
106
-
107
- expect(spy1).toBeCalledTimes(1);
108
- expect(spy2).not.toHaveBeenCalled();
109
-
110
- interface ComponentProps {
111
- readonly api: Api;
112
- }
113
-
114
- interface ComponentState {
115
- readonly element: Element;
116
- }
117
-
118
- class TmpParentComponent extends React.Component<
119
- ComponentProps,
120
- ComponentState
121
- > {
122
- componentDidMount() {
123
- this.setState({ element: this.props.api.child1.mount() });
124
- }
125
-
126
- componentWillUnmount() {
127
- this.state.element.destroy();
128
- }
129
-
130
- render() {
131
- return <div />;
132
- }
133
- }
134
-
135
- const { unmount } = render(<TmpParentComponent api={api} />);
136
- expect(spy1).toBeCalledTimes(1);
137
- expect(spy2).not.toHaveBeenCalled();
138
-
139
- unmount();
140
-
141
- expect(spy1).toBeCalledTimes(2);
142
- expect(spy2).not.toHaveBeenCalled();
143
- });
144
- });