diff --git a/.changeset/cool-hotels-remember.md b/.changeset/cool-hotels-remember.md deleted file mode 100644 index efd0ccdafb..0000000000 --- a/.changeset/cool-hotels-remember.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@adyen/adyen-web': patch ---- - -For all PaymentMethodItems we were adding a class `adyen-checkout__payment-method--{fundingSource}` (where fundingSource was either "credit" or "debit") - -This is meant to be a Card PM specific class to indicate, in the paymentMethods list, whether the card is a credit or debit card. diff --git a/.github/workflows/validate-remote-assets.yml b/.github/workflows/validate-remote-assets.yml index b23e308426..04e5b90d4d 100644 --- a/.github/workflows/validate-remote-assets.yml +++ b/.github/workflows/validate-remote-assets.yml @@ -32,7 +32,7 @@ jobs: - name: Comment PR that request failed if: failure() - uses: actions/github-script@v6.4.1 + uses: actions/github-script@v7.0.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -42,7 +42,7 @@ jobs: - name: Comment PR that request succeeded if: success() - uses: actions/github-script@v6.4.1 + uses: actions/github-script@v7.0.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/package.json b/package.json index a03cf45798..b28c46232e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,6 @@ "@changesets/cli": "^2.26.1", "@changesets/get-github-info": "^0.5.2", "dotenv": "^16.0.3", - "prettier": "2.8.8" + "prettier": "3.1.1" } } diff --git a/packages/e2e-playwright/package.json b/packages/e2e-playwright/package.json index 12097b5e69..1a2bafa7f5 100644 --- a/packages/e2e-playwright/package.json +++ b/packages/e2e-playwright/package.json @@ -26,6 +26,6 @@ "webpack-dev-server": "4.15.1" }, "dependencies": { - "@adyen/adyen-web": "5.53.3" + "@adyen/adyen-web": "5.56.1" } } diff --git a/packages/e2e/package.json b/packages/e2e/package.json index dac3741e1b..fd8e400616 100644 --- a/packages/e2e/package.json +++ b/packages/e2e/package.json @@ -41,6 +41,6 @@ "whatwg-fetch": "^3.6.2" }, "dependencies": { - "@adyen/adyen-web": "5.53.3" + "@adyen/adyen-web": "5.56.1" } } diff --git a/packages/e2e/tests/utils/commonUtils.js b/packages/e2e/tests/utils/commonUtils.js index eeef1d3c68..26c787f051 100644 --- a/packages/e2e/tests/utils/commonUtils.js +++ b/packages/e2e/tests/utils/commonUtils.js @@ -68,6 +68,7 @@ export const deleteDigitsFromIFrame = async (t, iframeSelector, iFrameNum, iFram }; export const checkIframeInputContainsValue = async (t, iframeSelector, iFrameNum, iFrameInputSelector, valueToCheck) => { + // prettier-ignore return t .switchToMainWindow() .switchToIframe(iframeSelector.nth(iFrameNum)) diff --git a/packages/lib/CHANGELOG.md b/packages/lib/CHANGELOG.md index 2259ac80ce..89ddd73b08 100644 --- a/packages/lib/CHANGELOG.md +++ b/packages/lib/CHANGELOG.md @@ -1,5 +1,67 @@ # @adyen/adyen-web +## 5.56.1 + +### Patch Changes + +- For the regular card payment, in case of a zero-auth transaction, the pay button label is changed to `Save details`, and the `Save for my next payment` checkbox is removed. ([#2514](https://github.com/Adyen/adyen-web/pull/2514)) + + The drop-in component shows `Details saved` as the success message for such transaction. + +## 5.56.0 + +### Minor Changes + +- Add support for the PayMe payment method. ([#2476](https://github.com/Adyen/adyen-web/pull/2476)) + +### Patch Changes + +- Improve the payment status check call for QR payments. ([#2506](https://github.com/Adyen/adyen-web/pull/2506)) + +- Fix incorrect grammar German pay button ([#2505](https://github.com/Adyen/adyen-web/pull/2505)) + +- update sessionData after /setup call ([#2504](https://github.com/Adyen/adyen-web/pull/2504)) + +- Detect when the value of a data-cse attribute is not supported, and don't create a SF for it ([#2495](https://github.com/Adyen/adyen-web/pull/2495)) + +## 5.55.1 + +### Patch Changes + +- Loading the logo images properly when handling qrCode/await actions ([#2490](https://github.com/Adyen/adyen-web/pull/2490)) + +## 5.55.0 + +### Minor Changes + +- Bumping sf version to 4.8.0 which includes a more comprehensive startup log (to help with debugging) ([#2488](https://github.com/Adyen/adyen-web/pull/2488)) + +### Patch Changes + +- Renaming detectInIframe to the more accurate, but lengthy, detectInIframeInSameOrigin. ([#2475](https://github.com/Adyen/adyen-web/pull/2475)) + + Now the functionality only considers itself to be running in an iframe _if_ it is possible to access the parent domain and thus be able to redirect the top, parent, window + +## 5.54.0 + +### Minor Changes + +- feature: adds new onAddressSelected to fill data when an item is selected in AddressSearch ([#2406](https://github.com/Adyen/adyen-web/pull/2406)) + +- Click to Pay - Enabling MC/Visa to drop cookies if the shopper gives consent ([#2409](https://github.com/Adyen/adyen-web/pull/2409)) + +- Click to Pay - Replacing loading gif by animated SVGs ([#2435](https://github.com/Adyen/adyen-web/pull/2435)) + +### Patch Changes + +- For all PaymentMethodItems we were adding a class `adyen-checkout__payment-method--{fundingSource}` (where fundingSource was either "credit" or "debit") ([#2465](https://github.com/Adyen/adyen-web/pull/2465)) + + This is meant to be a Card PM specific class to indicate, in the paymentMethods list, whether the card is a credit or debit card. + +- Fixed Klarna B2B logo for Drop-in ([#2458](https://github.com/Adyen/adyen-web/pull/2458)) + +- Pass the `browserInfo` in the `state.data` for the Redirect payments, in order to fix the mobile web integration for some redirect payments. ([#2469](https://github.com/Adyen/adyen-web/pull/2469)) + ## 5.53.3 ### Patch Changes diff --git a/packages/lib/config/setupTests.ts b/packages/lib/config/setupTests.ts index adda8c8840..978f16be44 100644 --- a/packages/lib/config/setupTests.ts +++ b/packages/lib/config/setupTests.ts @@ -1,4 +1,5 @@ import { configure } from 'enzyme'; +import '@testing-library/jest-dom/jest-globals'; import '@testing-library/jest-dom'; import Adapter from 'enzyme-adapter-preact-pure'; import './testMocks/matchMedia'; diff --git a/packages/lib/package.json b/packages/lib/package.json index 19bfe50df9..e084d718fb 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -1,6 +1,6 @@ { "name": "@adyen/adyen-web", - "version": "5.53.3", + "version": "5.56.1", "license": "MIT", "homepage": "https://docs.adyen.com/checkout", "type": "module", @@ -87,39 +87,41 @@ "@rollup/plugin-replace": "5.0.2", "@rollup/plugin-terser": "0.4.3", "@size-limit/preset-big-lib": "9.0.0", - "@storybook/addon-a11y": "7.4.6", - "@storybook/addon-essentials": "7.4.6", - "@storybook/manager-api": "7.4.6", - "@storybook/preact": "7.4.6", - "@storybook/preact-vite": "7.4.6", + "@storybook/addon-a11y": "^7.6.6", + "@storybook/addon-essentials": "^7.6.6", + "@storybook/manager-api": "^7.6.6", + "@storybook/preact": "^7.6.6", + "@storybook/preact-vite": "^7.6.6", "@swc/core": "1.3.86", - "@testing-library/jest-dom": "6.1.4", + "@testing-library/jest-dom": "^6.2.0", "@testing-library/preact": "3.2.3", "@testing-library/preact-hooks": "1.1.0", "@testing-library/user-event": "14.5.1", - "@types/jest": "29.5.10", + "@types/jest": "^29.5.11", "@typescript-eslint/eslint-plugin": "6.7.0", "@typescript-eslint/parser": "6.7.0", + "add": "^2.0.6", "autoprefixer": "10.4.16", "cross-env": "^7.0.3", "cssnano": "6.0.1", "dotenv": "^16.0.3", "enzyme": "3.11.0", "enzyme-adapter-preact-pure": "4.1.0", - "eslint": "8.52.0", + "eslint": "8.53.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-react": "^7.31.8", - "eslint-plugin-storybook": "0.6.14", + "eslint-plugin-storybook": "0.6.15", "eslint-plugin-testing-library": "6.2.0", "husky": "^8.0.1", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-mock-extended": "3.0.5", - "lint-staged": "^13.0.3", + "lint-staged": "^15.0.0", "postcss": "8.4.31", "react": "^18.2.0", "react-dom": "^18.2.0", + "remove": "^0.1.5", "rollup": "3.25.2", "rollup-plugin-dts": "6.0.2", "rollup-plugin-postcss": "4.0.2", @@ -128,15 +130,16 @@ "rollup-plugin-visualizer": "5.9.2", "sass": "1.69.5", "size-limit": "^9.0.0", - "storybook": "^7.4.6", + "storybook": "^7.6.6", "stylelint": "15.11.0", "stylelint-config-standard-scss": "7.0.1", "ts-jest": "^29.1.1", "tslib": "2.6.2", "typescript": "5.2.2", - "vite": "4.5.0", + "vite": "4.5.1", "vite-plugin-stylelint": "^4.3.0", - "whatwg-fetch": "^3.6.19" + "whatwg-fetch": "^3.6.19", + "yarn": "^1.22.21" }, "dependencies": { "@adyen/bento-design-tokens": "0.5.4", diff --git a/packages/lib/src/components/ANCV/ANCV.tsx b/packages/lib/src/components/ANCV/ANCV.tsx index 2cae6954a3..590fb7ffea 100644 --- a/packages/lib/src/components/ANCV/ANCV.tsx +++ b/packages/lib/src/components/ANCV/ANCV.tsx @@ -40,7 +40,7 @@ export class ANCVElement extends UIElement { * Called when the /paymentDetails endpoint returns PartiallyAuthorised. The /paymentDetails happens once the /status * returns PartiallyAuthorised * - * @param order + * @param order - */ protected handleOrder = ({ order }: PaymentResponseData) => { this.updateParent({ order }); diff --git a/packages/lib/src/components/Card/Card.test.ts b/packages/lib/src/components/Card/Card.test.tsx similarity index 60% rename from packages/lib/src/components/Card/Card.test.ts rename to packages/lib/src/components/Card/Card.test.tsx index 2583f8d447..ff42dd7d2e 100644 --- a/packages/lib/src/components/Card/Card.test.ts +++ b/packages/lib/src/components/Card/Card.test.tsx @@ -1,4 +1,9 @@ +import { h } from 'preact'; import { CardElement } from './Card'; +import { render, screen } from '@testing-library/preact'; +import CoreProvider from '../../core/Context/CoreProvider'; +import Language from '../../language'; +import { Resources } from '../../core/Context/Resources'; describe('Card', () => { describe('formatProps', function () { @@ -12,6 +17,40 @@ describe('Card', () => { const card = new CardElement({ core: global.core, countryCode: 'KR' }); expect(card.props.countryCode).toEqual('kr'); }); + + test('should return false for enableStoreDetails in case of zero-auto transaction', () => { + const card = new CardElement({ core: global.core, amount: { value: 0, currency: 'eur' }, enableStoreDetails: true }); + expect(card.props.enableStoreDetails).toEqual(false); + }); + }); + + describe('payButton', () => { + describe('Zero auth transaction', () => { + const i18n = new Language('en-US'); + const props = { core: global.core, amount: { value: 0, currency: 'eur' }, enableStoreDetails: true, i18n }; + const customRender = (ui: h.JSX.Element) => { + return render( + // @ts-ignore ignore + + {ui} + + ); + }; + + test('should show the label "Save details" for the regular card', async () => { + const card = new CardElement(props); + // @ts-ignore ignore + customRender(card.payButton()); + expect(await screen.findByRole('button', { name: 'Save details' })).toBeTruthy(); + }); + + test('should show the label "Confirm preauthorization" for the stored card', async () => { + const card = new CardElement({ ...props, storedPaymentMethodId: 'test' }); + // @ts-ignore ignore + customRender(card.payButton()); + expect(await screen.findByRole('button', { name: 'Confirm preauthorization' })).toBeTruthy(); + }); + }); }); describe('get data', () => { diff --git a/packages/lib/src/components/Card/Card.tsx b/packages/lib/src/components/Card/Card.tsx index cf8064da37..6538fd4497 100644 --- a/packages/lib/src/components/Card/Card.tsx +++ b/packages/lib/src/components/Card/Card.tsx @@ -1,5 +1,4 @@ import { h } from 'preact'; -import { UIElement } from '../internal/UIElement/UIElement'; import CardInput from './components/CardInput'; import CoreProvider from '../../core/Context/CoreProvider'; import collectBrowserInfo from '../../utils/browserInfo'; @@ -14,6 +13,9 @@ import ClickToPayWrapper from './components/ClickToPayWrapper'; import SRPanelProvider from '../../core/Errors/SRPanelProvider'; import { TxVariants } from '../tx-variants'; import { UIElementStatus } from '../internal/UIElement/types'; +import UIElement from '../internal/UIElement'; +import PayButton from '../internal/PayButton'; +import { PayButtonProps } from '../internal/PayButton/PayButton'; export class CardElement extends UIElement { public static type = TxVariants.scheme; @@ -55,6 +57,9 @@ export class CardElement extends UIElement { }; formatProps(props: CardConfiguration) { + const isZeroAuth = props.amount?.value === 0; + const enableStoreDetails = isZeroAuth ? false : props.session?.configuration?.enableStoreDetails || props.enableStoreDetails; + return { ...props, // Mismatch between hasHolderName & holderNameRequired which can mean card can never be valid @@ -77,7 +82,7 @@ export class CardElement extends UIElement { icon: props.icon || props.configuration?.icon, // installmentOptions of a session should be used before falling back to the merchant configuration installmentOptions: props.session?.configuration?.installmentOptions || props.installmentOptions, - enableStoreDetails: props.session?.configuration?.enableStoreDetails || props.enableStoreDetails, + enableStoreDetails, /** * Click to Pay configuration * - If email is set explicitly in the configuration, then it can override the one used in the session creation @@ -202,6 +207,20 @@ export class CardElement extends UIElement { get browserInfo() { return collectBrowserInfo(); } + // Override + protected payButton = (props: PayButtonProps) => { + const isZeroAuth = this.props.amount?.value === 0; + const isStoredCard = this.props.storedPaymentMethodId?.length > 0; + return ( + + ); + }; private renderCardInput(isCardPrimaryInput = true): h.JSX.Element { return ( diff --git a/packages/lib/src/components/Card/components/CardInput/CardInput.tsx b/packages/lib/src/components/Card/components/CardInput/CardInput.tsx index 2f1bf6f2f4..75a42e1aaa 100644 --- a/packages/lib/src/components/Card/components/CardInput/CardInput.tsx +++ b/packages/lib/src/components/Card/components/CardInput/CardInput.tsx @@ -491,6 +491,8 @@ const CardInput = (props: CardInputProps) => { partialAddressSchema={partialAddressSchema} handleAddress={handleAddress} onAddressLookup={props.onAddressLookup} + onAddressSelected={props.onAddressSelected} + addressSearchDebounceMs={props.addressSearchDebounceMs} // iOSFocusedField={iOSFocusedField} /> diff --git a/packages/lib/src/components/Card/components/CardInput/components/CardFieldsWrapper.tsx b/packages/lib/src/components/Card/components/CardInput/components/CardFieldsWrapper.tsx index 5cf21cda6f..6e786b6bd1 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/CardFieldsWrapper.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/CardFieldsWrapper.tsx @@ -45,6 +45,8 @@ export const CardFieldsWrapper = ({ setAddressRef, partialAddressSchema, onAddressLookup, + onAddressSelected, + addressSearchDebounceMs, // For this comp (props passed through from CardInput) amount, billingAddressRequired, @@ -161,6 +163,8 @@ export const CardFieldsWrapper = ({ iOSFocusedField={iOSFocusedField} onAddressLookup={onAddressLookup} showContextualElement={showContextualElement} + onAddressSelected={onAddressSelected} + addressSearchDebounceMs={addressSearchDebounceMs} /> )} diff --git a/packages/lib/src/components/Card/components/CardInput/handlers.ts b/packages/lib/src/components/Card/components/CardInput/handlers.ts index 0f875cf874..57408cd420 100644 --- a/packages/lib/src/components/Card/components/CardInput/handlers.ts +++ b/packages/lib/src/components/Card/components/CardInput/handlers.ts @@ -8,7 +8,6 @@ import { CbObjOnFocus } from '../../../internal/SecuredFields/lib/types'; export const setFocusOnFirstField = (isValidating, sfp, fieldToFocus) => { if (isValidating) { // If not a cardInput related securedField - find field and set focus on it - // if (!ALL_RELATED_SECURED_FIELDS.includes(who)) { if (!CREDIT_CARD_SF_FIELDS.includes(fieldToFocus)) { setFocusOnNonSF(fieldToFocus, sfp); } else { diff --git a/packages/lib/src/components/Card/components/CardInput/types.ts b/packages/lib/src/components/Card/components/CardInput/types.ts index 0a1c8722e8..1f8c8a4577 100644 --- a/packages/lib/src/components/Card/components/CardInput/types.ts +++ b/packages/lib/src/components/Card/components/CardInput/types.ts @@ -11,7 +11,7 @@ import { SRPanel } from '../../../../core/Errors/SRPanel'; import Analytics from '../../../../core/Analytics'; import RiskElement from '../../../../core/RiskModule'; import { DisclaimerMsgObject } from '../../../internal/DisclaimerMessage/DisclaimerMessage'; -import { OnAddressLookupType } from '../../../internal/Address/components/AddressSearch'; +import { OnAddressLookupType, OnAddressSelectedType } from '../../../internal/Address/components/AddressSearch'; import { ComponentMethodsRef } from '../../../internal/UIElement/types'; import { AddressData, PaymentAmount } from '../../../../types/global-types'; @@ -114,6 +114,8 @@ export interface CardInputProps { onFocus?: (e) => {}; onLoad?: () => {}; onAddressLookup?: OnAddressLookupType; + onAddressSelected?: OnAddressSelectedType; + addressSearchDebounceMs?: number; payButton?: (obj) => {}; placeholders?: Placeholders; positionHolderNameOnTop?: boolean; diff --git a/packages/lib/src/components/CustomCard/CustomCard.tsx b/packages/lib/src/components/CustomCard/CustomCard.tsx index c7c32d9c6f..69903498eb 100644 --- a/packages/lib/src/components/CustomCard/CustomCard.tsx +++ b/packages/lib/src/components/CustomCard/CustomCard.tsx @@ -94,10 +94,6 @@ export class CustomCard extends UIElement { return !!this.state.isValid; } - get icon() { - return this.resources.getImage()(this.props.type); - } - get browserInfo() { return collectBrowserInfo(); } diff --git a/packages/lib/src/components/Doku/Doku.tsx b/packages/lib/src/components/Doku/Doku.tsx index 291b2bb628..7d3df714c0 100644 --- a/packages/lib/src/components/Doku/Doku.tsx +++ b/packages/lib/src/components/Doku/Doku.tsx @@ -44,10 +44,6 @@ export class DokuElement extends UIElement { }; } - get icon() { - return this.resources.getImage()(this.props.type); - } - render() { return ( diff --git a/packages/lib/src/components/Dropin/components/DropinComponent.tsx b/packages/lib/src/components/Dropin/components/DropinComponent.tsx index 05084db049..dfd904d912 100644 --- a/packages/lib/src/components/Dropin/components/DropinComponent.tsx +++ b/packages/lib/src/components/Dropin/components/DropinComponent.tsx @@ -122,7 +122,7 @@ export class DropinComponent extends Component; + return ; case 'error': return ; diff --git a/packages/lib/src/components/Econtext/Econtext.tsx b/packages/lib/src/components/Econtext/Econtext.tsx index d21746fd3a..ec9d7e6764 100644 --- a/packages/lib/src/components/Econtext/Econtext.tsx +++ b/packages/lib/src/components/Econtext/Econtext.tsx @@ -40,10 +40,6 @@ export class EcontextElement extends UIElement { }; } - get icon() { - return this.resources.getImage()(this.props.type); - } - render() { return ( diff --git a/packages/lib/src/components/Giropay/Giropay.tsx b/packages/lib/src/components/Giropay/Giropay.tsx index b5e7c0122e..a9ae6c995e 100644 --- a/packages/lib/src/components/Giropay/Giropay.tsx +++ b/packages/lib/src/components/Giropay/Giropay.tsx @@ -14,17 +14,6 @@ class GiropayElement extends RedirectElement { }; } - /** - * Formats the component data output - */ - formatData() { - return { - paymentMethod: { - type: GiropayElement.type - } - }; - } - get displayName() { return this.props.name || this.constructor['type']; } diff --git a/packages/lib/src/components/Klarna/components/KlarnaWidget/KlarnaWidget.test.tsx b/packages/lib/src/components/Klarna/components/KlarnaWidget/KlarnaWidget.test.tsx new file mode 100644 index 0000000000..e0f186a0ed --- /dev/null +++ b/packages/lib/src/components/Klarna/components/KlarnaWidget/KlarnaWidget.test.tsx @@ -0,0 +1,141 @@ +import { h } from 'preact'; +import { fireEvent, render, screen } from '@testing-library/preact'; +import { KlarnaWidget } from './KlarnaWidget'; +import Script from '../../../../utils/Script'; +import { KLARNA_WIDGET_URL } from '../../constants'; +import { KlarnaWidgetAuthorizeResponse } from '../../types'; + +jest.mock('../../../../utils/Script', () => { + return jest.fn().mockImplementation(() => { + return { load: mockScriptLoaded, remove: jest.fn() }; + }); +}); +const mockScriptLoaded = jest.fn().mockImplementation(() => { + window.klarnaAsyncCallback(); +}); + +let klarnaObj; +const originalLocation = window; +const klarnaInit = jest.fn(); +const getKlarnaActionImp = (res: Partial = {}) => + jest.fn().mockImplementation((_, onAction) => { + onAction(res); + }); + +describe('KlarnaWidget', () => { + const onLoaded = jest.fn(); + const onComplete = jest.fn(); + const onError = jest.fn(); + const paymentData = 'test'; + const paymentMethodType = 'klarna'; + const sdkData = { client_token: '123', payment_method_category: 'paynow' }; + const payButton = props => ( + + ); + const props = { + onLoaded, + onComplete, + onError, + paymentData, + paymentMethodType, + sdkData, + payButton + }; + + beforeAll(() => { + klarnaObj = { + Payments: { + init: klarnaInit, + load: jest.fn(), + authorize: jest.fn() + } + }; + Object.defineProperty(window, 'Klarna', { + value: klarnaObj + }); + }); + + afterAll(() => { + jest.restoreAllMocks(); + Object.defineProperty(globalThis, 'window', { + value: originalLocation + }); + }); + + describe('Klarna widget initialization', () => { + test('should download Klarna widget script on init', () => { + render(); + expect(Script).toHaveBeenCalledWith(KLARNA_WIDGET_URL); + expect(mockScriptLoaded).toHaveBeenCalledTimes(1); + }); + + test('should show pay button on init', async () => { + render(); + expect(await screen.findByTestId('pay-with-klarna')).toBeTruthy(); + }); + + test('should call Klarna init with client_token', () => { + render(); + expect(klarnaInit).toHaveBeenCalledWith({ client_token: sdkData.client_token }); + }); + + test('should call props onLoaded if Klarna pre-authorization passed', () => { + klarnaObj.Payments.load = getKlarnaActionImp({ show_form: true }); + render(); + expect(onLoaded).toHaveBeenCalled(); + }); + + test('should call props onComplete if Klarna pre-authorization failed', () => { + klarnaObj.Payments.load = getKlarnaActionImp({ show_form: false }); + render(); + expect(onComplete).toHaveBeenCalledWith({ + data: { + paymentData, + details: {} + } + }); + }); + }); + + describe('Pay with Klarna widget', () => { + test('should call the onComplete if the payment is authorized', async () => { + const authRes = { approved: true, show_form: true, authorization_token: 'abc' }; + klarnaObj.Payments.load = getKlarnaActionImp({ show_form: true }); + klarnaObj.Payments.authorize = getKlarnaActionImp(authRes); + render(); + fireEvent.click(await screen.findByTestId(/pay-with-klarna/i)); + expect(onComplete).toHaveBeenCalledWith({ + data: { + paymentData, + details: { + token: authRes.authorization_token, + authorization_token: authRes.authorization_token + } + } + }); + }); + test('should call the onError if the payment is not authorized temporarily', async () => { + klarnaObj.Payments.load = getKlarnaActionImp({ show_form: true }); + const authRes = { approved: false, show_form: true }; + klarnaObj.Payments.authorize = getKlarnaActionImp(authRes); + render(); + fireEvent.click(await screen.findByTestId(/pay-with-klarna/i)); + expect(onError).toHaveBeenCalledWith(authRes); + }); + + test('should call the onComplete if the payment is not authorized permanently', async () => { + klarnaObj.Payments.load = getKlarnaActionImp({ show_form: true }); + klarnaObj.Payments.authorize = getKlarnaActionImp({ show_form: false }); + render(); + fireEvent.click(await screen.findByTestId(/pay-with-klarna/i)); + expect(onComplete).toHaveBeenCalledWith({ + data: { + paymentData, + details: {} + } + }); + }); + }); +}); diff --git a/packages/lib/src/components/OnlinebankingPL/OnlineBankingPL.test.ts b/packages/lib/src/components/OnlinebankingPL/OnlineBankingPL.test.ts index c8cbf7f583..a3ac5f5208 100644 --- a/packages/lib/src/components/OnlinebankingPL/OnlineBankingPL.test.ts +++ b/packages/lib/src/components/OnlinebankingPL/OnlineBankingPL.test.ts @@ -23,10 +23,12 @@ test('should show regulations and information obligation links', async () => { const regulationLink = await screen.findByRole('link', { name: 'regulations' }); expect(regulationLink).toBeTruthy(); + // @ts-ignore FIX TYPES expect(regulationLink).toHaveAttribute('href', 'https://www.przelewy24.pl/regulamin'); const obligationLink = await screen.findByRole('link', { name: 'information obligation' }); expect(obligationLink).toBeTruthy(); + // @ts-ignore FIX TYPES expect(obligationLink).toHaveAttribute('href', 'https://www.przelewy24.pl/obowiazek-informacyjny-rodo-platnicy'); }); test('should show regulations and information obligation links', async () => { @@ -44,9 +46,11 @@ test('should show regulations and information obligation links', async () => { const regulationLink = await screen.findByRole('link', { name: 'regulations' }); expect(regulationLink).toBeTruthy(); + // @ts-ignore FIX TYPES expect(regulationLink).toHaveAttribute('href', 'https://www.przelewy24.pl/regulamin'); const obligationLink = await screen.findByRole('link', { name: 'information obligation' }); expect(obligationLink).toBeTruthy(); + // @ts-ignore FIX TYPES expect(obligationLink).toHaveAttribute('href', 'https://www.przelewy24.pl/obowiazek-informacyjny-rodo-platnicy'); }); diff --git a/packages/lib/src/components/PayMe/Instructions.scss b/packages/lib/src/components/PayMe/Instructions.scss new file mode 100644 index 0000000000..be6baf7949 --- /dev/null +++ b/packages/lib/src/components/PayMe/Instructions.scss @@ -0,0 +1,15 @@ +@use '../../style/index'; + +.adyen-checkout-payme-instructions { + font-size: var(--adyen-checkout-font-size-small); + text-align: center; + color: #5c687c; + line-height: 20px; + + &__steps { + list-style-position: inside; + padding-inline-start: 0; + margin: 16px 0; + padding-bottom: 8px; + } +} diff --git a/packages/lib/src/components/PayMe/Instructions.test.tsx b/packages/lib/src/components/PayMe/Instructions.test.tsx new file mode 100644 index 0000000000..1463cfaf04 --- /dev/null +++ b/packages/lib/src/components/PayMe/Instructions.test.tsx @@ -0,0 +1,23 @@ +import { render, screen } from '@testing-library/preact'; +import { h } from 'preact'; +import { Resources } from '../../core/Context/Resources'; +import CoreProvider from '../../core/Context/CoreProvider'; +import Instructions from './Instructions'; + +describe('Instructions', () => { + const customRender = (ui: h.JSX.Element) => { + return render( + + {ui} + + ); + }; + + test('should see a list of instructions and footnote', async () => { + customRender(); + expect(await screen.findByText('Open the PayMe app', { exact: false })).toBeInTheDocument(); + expect(await screen.findByText('Scan the QR code', { exact: false })).toBeInTheDocument(); + expect(await screen.findByText('Complete the payment in the app', { exact: false })).toBeInTheDocument(); + expect(await screen.findByText('Please do not close this page before the payment is completed', { exact: false })).toBeInTheDocument(); + }); +}); diff --git a/packages/lib/src/components/PayMe/Instructions.tsx b/packages/lib/src/components/PayMe/Instructions.tsx new file mode 100644 index 0000000000..b4d107799b --- /dev/null +++ b/packages/lib/src/components/PayMe/Instructions.tsx @@ -0,0 +1,20 @@ +import useCoreContext from '../../core/Context/useCoreContext'; +import { h } from 'preact'; +import './Instructions.scss'; + +export default function Instructions() { + const { i18n } = useCoreContext(); + const steps = i18n.get('payme.instructions.steps'); + const footnote = i18n.get('payme.instructions.footnote'); + + return ( +
+
    + {steps.split('%@').map((step, index) => ( +
  1. {step}
  2. + ))} +
+ {footnote} +
+ ); +} diff --git a/packages/lib/src/components/PayMe/PayMe.ts b/packages/lib/src/components/PayMe/PayMe.ts new file mode 100644 index 0000000000..f98a434008 --- /dev/null +++ b/packages/lib/src/components/PayMe/PayMe.ts @@ -0,0 +1,23 @@ +import QRLoaderContainer from '../helpers/QRLoaderContainer'; +import Instructions from './Instructions'; + +class PayMeElement extends QRLoaderContainer { + public static type = 'payme'; + private static defaultCountdown = 10; // min + private static defaultDelay = 2000; // ms + + formatProps(props) { + return { + delay: PayMeElement.defaultDelay, + countdownTime: PayMeElement.defaultCountdown, + redirectIntroduction: 'payme.openPayMeApp', + introduction: 'payme.scanQrCode', + timeToPay: 'payme.timeToPay', + buttonLabel: 'payme.redirectButtonLabel', + instructions: Instructions, + ...super.formatProps(props) + }; + } +} + +export default PayMeElement; diff --git a/packages/lib/src/components/PayMe/index.ts b/packages/lib/src/components/PayMe/index.ts new file mode 100644 index 0000000000..c8ecaa2506 --- /dev/null +++ b/packages/lib/src/components/PayMe/index.ts @@ -0,0 +1 @@ +export { default } from './PayMe'; diff --git a/packages/lib/src/components/Redirect/Redirect.test.tsx b/packages/lib/src/components/Redirect/Redirect.test.tsx index 44bc069e4b..0e3160b6a0 100644 --- a/packages/lib/src/components/Redirect/Redirect.test.tsx +++ b/packages/lib/src/components/Redirect/Redirect.test.tsx @@ -2,8 +2,9 @@ import { mount } from 'enzyme'; import { h } from 'preact'; import Redirect from './Redirect'; import RedirectShopper from './components/RedirectShopper'; +import RedirectElement from './Redirect'; -jest.mock('../../utils/detectInIframe', () => { +jest.mock('../../utils/detectInIframeInSameOrigin', () => { return jest.fn().mockImplementation(() => { return true; }); @@ -21,6 +22,7 @@ describe('Redirect', () => { test('Accepts a POST redirect status', () => { window.HTMLFormElement.prototype.submit = jest.fn(); + // @ts-ignore ignore const wrapper = mount(); expect(wrapper.find('form')).toHaveLength(1); @@ -32,6 +34,7 @@ describe('Redirect', () => { test('Accepts a POST redirect status, setting target to _top, when the config prop tells it to', () => { window.HTMLFormElement.prototype.submit = jest.fn(); + // @ts-ignore ignore const wrapper = mount(); expect(wrapper.find('form')).toHaveLength(1); @@ -39,4 +42,11 @@ describe('Redirect', () => { setTimeout(() => expect(window.HTMLFormElement.prototype.submit).toHaveBeenCalled(), 0); }); }); + + describe('Redirect formatData', () => { + test('should send browserInfo in the data', () => { + const redirectElement = new RedirectElement({ core: global.core }); + expect(redirectElement.formatData().browserInfo).not.toBeNull(); + }); + }); }); diff --git a/packages/lib/src/components/Redirect/Redirect.tsx b/packages/lib/src/components/Redirect/Redirect.tsx index 3ec54315c9..4d905441f2 100644 --- a/packages/lib/src/components/Redirect/Redirect.tsx +++ b/packages/lib/src/components/Redirect/Redirect.tsx @@ -5,6 +5,7 @@ import RedirectShopper from './components/RedirectShopper'; import RedirectButton from '../internal/RedirectButton'; import { TxVariants } from '../tx-variants'; import { RedirectConfiguration } from './types'; +import collectBrowserInfo from '../../utils/browserInfo'; class RedirectElement extends UIElement { public static type = TxVariants.redirect; @@ -17,7 +18,8 @@ class RedirectElement extends UIElement { return { paymentMethod: { type: this.type - } + }, + browserInfo: this.browserInfo }; } @@ -25,8 +27,8 @@ class RedirectElement extends UIElement { return true; } - get icon() { - return this.resources.getImage()(this.props.type); + get browserInfo() { + return collectBrowserInfo(); } render() { diff --git a/packages/lib/src/components/Redirect/components/RedirectShopper/RedirectShopper.tsx b/packages/lib/src/components/Redirect/components/RedirectShopper/RedirectShopper.tsx index ac69bd52a8..2748a556be 100644 --- a/packages/lib/src/components/Redirect/components/RedirectShopper/RedirectShopper.tsx +++ b/packages/lib/src/components/Redirect/components/RedirectShopper/RedirectShopper.tsx @@ -1,5 +1,5 @@ import { Component, h } from 'preact'; -import detectInIframe from '../../../../utils/detectInIframe'; +import detectInIframeInSameOrigin from '../../../../utils/detectInIframeInSameOrigin'; interface RedirectShopperProps { beforeRedirect: (resolve, reject, url) => Promise; @@ -21,7 +21,7 @@ class RedirectShopper extends Component { if (this.postForm) { this.postForm.submit(); } else { - if (this.props.redirectFromTopWhenInIframe && detectInIframe()) { + if (this.props.redirectFromTopWhenInIframe && detectInIframeInSameOrigin()) { // if in an iframe and the config prop allows it - try to redirect from the top level window window.top.location.assign?.(this.props.url); } else { @@ -51,7 +51,7 @@ class RedirectShopper extends Component { ref={ref => { this.postForm = ref; }} - {...(this.props.redirectFromTopWhenInIframe && detectInIframe() && { target: '_top' })} + {...(this.props.redirectFromTopWhenInIframe && detectInIframeInSameOrigin() && { target: '_top' })} > {Object.keys(data).map(key => ( diff --git a/packages/lib/src/components/helpers/QRLoaderContainer/QRLoaderContainer.tsx b/packages/lib/src/components/helpers/QRLoaderContainer/QRLoaderContainer.tsx index d9e49a2773..3370796665 100644 --- a/packages/lib/src/components/helpers/QRLoaderContainer/QRLoaderContainer.tsx +++ b/packages/lib/src/components/helpers/QRLoaderContainer/QRLoaderContainer.tsx @@ -7,7 +7,7 @@ import SRPanelProvider from '../../../core/Errors/SRPanelProvider'; import { QRLoaderConfiguration } from './types'; class QRLoaderContainer extends UIElement { - // Using the generic here allow to fully extend the QRLoaderContainer (including its props) + // Using the generic here allow to fully extend the QRLoaderContainer (including it's props) protected static defaultProps = { qrCodeImage: '', amount: null, diff --git a/packages/lib/src/components/helpers/QRLoaderContainer/types.ts b/packages/lib/src/components/helpers/QRLoaderContainer/types.ts index 1aee1fc5f6..66de326ec4 100644 --- a/packages/lib/src/components/helpers/QRLoaderContainer/types.ts +++ b/packages/lib/src/components/helpers/QRLoaderContainer/types.ts @@ -1,4 +1,5 @@ import { UIElementProps } from '../../internal/UIElement/types'; +import { h } from 'preact'; export interface QRLoaderConfiguration extends UIElementProps { /** @@ -17,6 +18,8 @@ export interface QRLoaderConfiguration extends UIElementProps { qrCodeImage?: string; paymentData?: string; introduction?: string; - instructions?: string; + redirectIntroduction?: string; + timeToPay?: string; + instructions?: string | (() => h.JSX.Element); copyBtn?: boolean; } diff --git a/packages/lib/src/components/index.ts b/packages/lib/src/components/index.ts index 7b9ac35ed6..cd360f6e28 100644 --- a/packages/lib/src/components/index.ts +++ b/packages/lib/src/components/index.ts @@ -76,6 +76,7 @@ export { default as WeChat } from './WeChat'; export { default as PromptPay } from './PromptPay'; export { default as PayNow } from './PayNow'; export { default as DuitNow } from './DuitNow'; +export { default as PayMe } from './PayMe'; /** Await */ export { default as Blik } from './Blik'; diff --git a/packages/lib/src/components/internal/Address/Address.tsx b/packages/lib/src/components/internal/Address/Address.tsx index 4ea83bb6eb..2f9264df70 100644 --- a/packages/lib/src/components/internal/Address/Address.tsx +++ b/packages/lib/src/components/internal/Address/Address.tsx @@ -1,5 +1,5 @@ import { Fragment, h } from 'preact'; -import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks'; import Fieldset from '../FormFields/Fieldset'; import ReadOnlyAddress from './components/ReadOnlyAddress'; import { getAddressValidationRules } from './validate'; @@ -49,22 +49,25 @@ export default function Address(props: AddressProps) { formatters: addressFormatters }); - const setSearchData = selectedAddress => { - const propsKeysToProcess = ADDRESS_SCHEMA; - propsKeysToProcess.forEach(propKey => { - // Make sure the data provided by the merchant is always strings - const providedValue = selectedAddress[propKey]; - if (providedValue === null || providedValue === undefined) return; - // Cast everything to string - setData(propKey, String(providedValue)); + const setSearchData = useCallback( + (selectedAddress: AddressData) => { + const propsKeysToProcess = ADDRESS_SCHEMA; + propsKeysToProcess.forEach(propKey => { + // Make sure the data provided by the merchant is always strings + const providedValue = selectedAddress[propKey]; + if (providedValue === null || providedValue === undefined) return; + // Cast everything to string + setData(propKey, String(providedValue)); + }); triggerValidation(); - }); - setHasSelectedAddress(true); - }; + setHasSelectedAddress(true); + }, + [setHasSelectedAddress, triggerValidation, setData] + ); - const onManualAddress = () => { + const onManualAddress = useCallback(() => { setUseManualAddress(true); - }; + }, []); // Expose method expected by (parent) Address.tsx addressRef.current.showValidation = () => { @@ -172,12 +175,14 @@ export default function Address(props: AddressProps) { {showAddressSearch && ( )} {showAddressFields && ( diff --git a/packages/lib/src/components/internal/Address/components/AddressSearch.test.tsx b/packages/lib/src/components/internal/Address/components/AddressSearch.test.tsx new file mode 100644 index 0000000000..b63a7cd631 --- /dev/null +++ b/packages/lib/src/components/internal/Address/components/AddressSearch.test.tsx @@ -0,0 +1,201 @@ +import { h } from 'preact'; +import userEvent from '@testing-library/user-event'; +import { render, screen, waitFor } from '@testing-library/preact'; +import AddressSearch from './AddressSearch'; +import CoreProvider from '../../../../core/Context/CoreProvider'; +import { Resources } from '../../../../core/Context/Resources'; + +const ADDRESS_LOOKUP_RESULT = [ + { + id: 1, + name: 'Road 1, 2000, UK', + street: 'Road 1' + }, + { + id: 2, + name: 'Road 2, 2500, UK', + street: 'Road 2' + }, + { + id: 3, + name: 'Road 3, 3000, UK', + street: 'Road 3' + } +]; + +const customRender = (ui: h.JSX.Element) => { + return render( + + {ui} + + ); +}; + +const ADDRESS_SELECT_RESULT = { + id: 1, + name: 'Road 1, 2000, UK', + street: 'Road 1', + city: 'London', + houseNumberOrName: '1', + postalCode: '2000', + country: 'GB', + raw: { + id: 1, + raw: 'RAW_DATA_MOCK' + } +}; +const onAddressLookupMockFn = async (value, { resolve }) => { + resolve(ADDRESS_LOOKUP_RESULT); +}; +const onAddressSelectMockFn = async (value, { resolve }) => { + resolve(ADDRESS_SELECT_RESULT); +}; + +const onAddressSelectMockFnReject = + rejectReason => + async (value, { reject }) => { + reject(rejectReason); + }; + +test('onAddressLookupMock should be triggered when typing', async () => { + const user = userEvent.setup({ delay: 100 }); + + const onAddressLookupMock = jest.fn(onAddressLookupMockFn); + + customRender( + {}} + onManualAddress={() => {}} + externalErrorMessage={'failed'} + onAddressLookup={onAddressLookupMock} + hideManualButton={true} + addressSearchDebounceMs={0} + /> + ); + + // Helps to make sure all tests ran + expect.assertions(5); + + // Get the input + const searchBar = screen.getByRole('combobox'); + await user.click(searchBar); + expect(searchBar).toHaveFocus(); + + await user.keyboard('Test'); + expect(searchBar).toHaveValue('Test'); + + // Test if onAddressLookup is called with the correct values + await waitFor(() => expect(onAddressLookupMock).toHaveBeenCalledTimes(4)); + await waitFor(() => expect(onAddressLookupMock.mock.lastCall[0]).toBe('Test')); + // Test if the return of the function is displayed + const resultList = screen.getByRole('listbox'); + expect(resultList).toHaveTextContent('Road 1, 2000, UK'); +}); + +test('onSelect is triggered with correct data', async () => { + const user = userEvent.setup({ delay: 100 }); + + const onAddressLookupMock = jest.fn(onAddressLookupMockFn); + const onAddressSelectMock = jest.fn(onAddressSelectMockFn); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const internalSetDataMock = jest.fn(data => {}); + + customRender( + {}} + externalErrorMessage={'failed'} + onAddressLookup={onAddressLookupMock} + onAddressSelected={onAddressSelectMock} + hideManualButton={true} + addressSearchDebounceMs={0} + /> + ); + const searchBar = screen.getByRole('combobox'); + + await user.click(searchBar); + await user.keyboard('Test'); + + // Move down with the keyboard and select the first option + await user.keyboard('[ArrowDown][Enter]'); + await waitFor(() => expect(onAddressSelectMock).toHaveBeenCalledTimes(1)); + await waitFor(() => + expect(onAddressSelectMock.mock.lastCall[0]).toStrictEqual({ + id: 1, + name: 'Road 1, 2000, UK', + street: 'Road 1' + }) + ); + + // CHeck if the parents select function is called with full data + await waitFor(() => expect(internalSetDataMock.mock.lastCall[0]).toStrictEqual(ADDRESS_SELECT_RESULT)); +}); + +test('rejecting onAddressLookupMock should not trigger error', async () => { + const user = userEvent.setup({ delay: 100 }); + + const onAddressLookupMock = jest.fn(onAddressSelectMockFnReject({})); + + customRender( + {}} + onManualAddress={() => {}} + externalErrorMessage={'failed'} + onAddressLookup={onAddressLookupMock} + hideManualButton={true} + addressSearchDebounceMs={0} + /> + ); + + // Helps to make sure all tests ran + expect.assertions(3); + + // Get the input + const searchBar = screen.getByRole('combobox'); + await user.click(searchBar); + await user.keyboard('Test'); + + // Still test if correct values are being called + await waitFor(() => expect(onAddressLookupMock).toHaveBeenCalledTimes(4)); + await waitFor(() => expect(onAddressLookupMock.mock.lastCall[0]).toBe('Test')); + + // Test if no options are displayed + const resultList = screen.getByRole('listbox'); + expect(resultList).toHaveTextContent('No options found'); + + // TODO fix this + // const resultError = screen.getByText('failed'); + // expect(resultError).not.toBeVisible(); +}); + +test('rejecting onAddressLookupMock with errorMessage displays error and message', async () => { + const user = userEvent.setup({ delay: 100 }); + + const onAddressLookupMock = jest.fn(onAddressSelectMockFnReject({ errorMessage: 'Refused Mock' })); + + customRender( + {}} + onManualAddress={() => {}} + externalErrorMessage={'failed'} + onAddressLookup={onAddressLookupMock} + hideManualButton={true} + addressSearchDebounceMs={0} + /> + ); + + // Helps to make sure all tests ran + expect.assertions(3); + + // Get the input + const searchBar = screen.getByRole('combobox'); + await user.click(searchBar); + await user.keyboard('Test'); + + await waitFor(() => expect(onAddressLookupMock).toHaveBeenCalledTimes(4)); + await waitFor(() => expect(onAddressLookupMock.mock.lastCall[0]).toBe('Test')); + + // Test if no options are displayed + const resultError = screen.getByText('Refused Mock'); + expect(resultError).toBeVisible(); +}); diff --git a/packages/lib/src/components/internal/Address/components/AddressSearch.tsx b/packages/lib/src/components/internal/Address/components/AddressSearch.tsx index c0aac26b77..9c9ff16d51 100644 --- a/packages/lib/src/components/internal/Address/components/AddressSearch.tsx +++ b/packages/lib/src/components/internal/Address/components/AddressSearch.tsx @@ -1,11 +1,12 @@ import Field from '../../FormFields/Field'; -import { Fragment, h } from 'preact'; +import { h } from 'preact'; import { AddressLookupItem } from '../types'; -import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'; +import { useCallback, useEffect, useState, useMemo } from 'preact/hooks'; import './AddressSearch.scss'; import useCoreContext from '../../../../core/Context/useCoreContext'; import { debounce } from '../utils'; import Select from '../../FormFields/Select'; +import { AddressData } from '../../../../types'; export type OnAddressLookupType = ( value: string, @@ -15,27 +16,43 @@ export type OnAddressLookupType = ( } ) => Promise; +export type OnAddressSelectedType = ( + value: string, + actions: { + resolve: (value: AddressLookupItem) => void; + reject: (reason?: any) => void; + } +) => Promise; + interface AddressSearchProps { onAddressLookup?: OnAddressLookupType; - onSelect: any; //TODO + onAddressSelected?: OnAddressSelectedType; + onSelect: (addressItem: AddressData) => void; onManualAddress: any; externalErrorMessage: string; hideManualButton: boolean; showContextualElement?: boolean; contextualText?: string; placeholder?: string; + addressSearchDebounceMs?: number; +} + +interface RejectionReason { + errorMessage: string; } export default function AddressSearch({ onAddressLookup, + onAddressSelected, onSelect, onManualAddress, externalErrorMessage, hideManualButton, showContextualElement, contextualText, - placeholder -}: AddressSearchProps) { + placeholder, + addressSearchDebounceMs +}: Readonly) { const [formattedData, setFormattedData] = useState([]); const [originalData, setOriginalData] = useState([]); @@ -44,20 +61,23 @@ export default function AddressSearch({ const { i18n } = useCoreContext(); const mapDataToSelect = data => data.map(({ id, name }) => ({ id, name })); - const onInput = useCallback( - async event => { + const handlePromiseReject = useCallback((reason: RejectionReason) => { + if (reason?.errorMessage) { + setErrorMessage(reason.errorMessage); + } + }, []); + + const onTextInput = useCallback( + async (inputValue: string) => { new Promise>((resolve, reject) => { - onAddressLookup(event, { resolve, reject }); + onAddressLookup(inputValue, { resolve, reject }); }) - .then(data => { - setOriginalData(data); - setFormattedData(mapDataToSelect(data)); + .then(searchArray => { + setOriginalData(searchArray); + setFormattedData(mapDataToSelect(searchArray)); setErrorMessage(''); }) - .catch(reason => { - setErrorMessage(reason); - console.error('error', reason); - }); + .catch(reason => handlePromiseReject(reason)); }, [onAddressLookup] ); @@ -67,51 +87,65 @@ export default function AddressSearch({ setErrorMessage(externalErrorMessage); }, [externalErrorMessage]); - const onChange = event => { + const onSelectItem = async event => { if (!event.target.value) { setErrorMessage(i18n.get('address.errors.incomplete')); return; } const value = originalData.find(item => item.id === event.target.value); - onSelect(value); - setFormattedData([]); + + // 1. in case we don't get a function just select item + if (typeof onAddressSelected !== 'function') { + onSelect(value); + setFormattedData([]); + return; + } + + // 2. in case callback is provided, create and call onAddressSelected + new Promise((resolve, reject) => { + onAddressSelected(value, { resolve, reject }); + }) + .then(fullData => { + onSelect(fullData); + setFormattedData([]); + }) + .catch(reason => handlePromiseReject(reason)); }; - const debounceInputHandler = useMemo(() => debounce(onInput), []); + const debounceInputHandler = useMemo(() => debounce(onTextInput, addressSearchDebounceMs), []); return ( - -
- + + - - {!hideManualButton && ( - - - - )} -
-
+ className={'adyen-checkout__address-search__dropdown'} + placeholder={placeholder} + onInput={debounceInputHandler} + items={formattedData} + onChange={onSelectItem} + disableTextFilter={true} + blurOnClose={true} + /> + + {!hideManualButton && ( + + + + )} + ); } diff --git a/packages/lib/src/components/internal/Address/types.ts b/packages/lib/src/components/internal/Address/types.ts index 885a01ad7e..721752fc01 100644 --- a/packages/lib/src/components/internal/Address/types.ts +++ b/packages/lib/src/components/internal/Address/types.ts @@ -2,7 +2,7 @@ import { AddressField, AddressData } from '../../../types/global-types'; import Specifications from './Specifications'; import { ValidatorRules } from '../../../utils/Validator/types'; import { ValidationRuleResult } from '../../../utils/Validator/ValidationRuleResult'; -import { OnAddressLookupType } from './components/AddressSearch'; +import { OnAddressLookupType, OnAddressSelectedType } from './components/AddressSearch'; // Describes an object with unknown keys whose value is always a string export type StringObject = { @@ -16,6 +16,8 @@ export interface AddressProps { label?: string; onChange: (newState) => void; onAddressLookup?: OnAddressLookupType; + onAddressSelected?: OnAddressSelectedType; + addressSearchDebounceMs?: number; requiredFields?: string[]; ref?: any; specifications?: AddressSpecifications; diff --git a/packages/lib/src/components/internal/Await/Await.test.tsx b/packages/lib/src/components/internal/Await/Await.test.tsx new file mode 100644 index 0000000000..e40ebafedd --- /dev/null +++ b/packages/lib/src/components/internal/Await/Await.test.tsx @@ -0,0 +1,171 @@ +import { h } from 'preact'; +import checkPaymentStatus from '../../../core/Services/payment-status'; +import Await from './Await'; +import { fireEvent, render, screen, waitFor } from '@testing-library/preact'; +import CoreProvider from '../../../core/Context/CoreProvider'; +import { Resources } from '../../../core/Context/Resources'; +import { AwaitComponentProps } from './types'; +import AdyenCheckoutError from '../../../core/Errors/AdyenCheckoutError'; +import SRPanelProvider from '../../../core/Errors/SRPanelProvider'; +import { SRPanel } from '../../../core/Errors/SRPanel'; + +jest.mock('../../../core/Services/payment-status'); + +describe('Await', () => { + const assignSpy = jest.fn(); + const defaultProps: AwaitComponentProps = { + countdownTime: 0, + onActionHandled: jest.fn(), + onComplete: jest.fn(), + onError: jest.fn(), + showCountdownTimer: true, + throttleInterval: 0, + throttleTime: 0, + brandLogo: 'https://example.com', + clientKey: 'test_client_key', + messageText: 'test', + paymentData: 'dummy', + ref: null, + type: 'mbway', + awaitText: 'test' + }; + const srPanel = new SRPanel({ core: global.core }); + const renderAwait = (props: AwaitComponentProps) => { + return render( + // @ts-ignore ignore + + + + + + ); + }; + + beforeAll(() => { + Object.defineProperty(window, 'location', { + value: { assign: assignSpy } + }); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('In progress', () => { + let checkPaymentStatusValue; + beforeEach(() => { + checkPaymentStatusValue = { payload: 'Ab02b4c0!', resultCode: 'pending', type: 'complete' }; + (checkPaymentStatus as jest.Mock).mockResolvedValue(checkPaymentStatusValue); + }); + + test('should show the spinner', async () => { + renderAwait(defaultProps); + expect(await screen.findByTestId('spinner')).toBeTruthy(); + }); + + test('should show brand logo', async () => { + renderAwait(defaultProps); + const image = (await screen.findByAltText(defaultProps.type)) as HTMLImageElement; + expect(image.src).toContain(defaultProps.brandLogo); + }); + + test('should show a countdown timer', async () => { + renderAwait(defaultProps); + expect(await screen.findByRole('timer')).toBeTruthy(); + }); + + test('should show redirect button', async () => { + renderAwait({ ...defaultProps, url: 'redirect-url' }); + expect(await screen.findByRole('button')).toBeTruthy(); + }); + + test('click the redirect button should call location.assign', async () => { + renderAwait({ ...defaultProps, url: 'redirect-url' }); + fireEvent.click(await screen.findByRole('button')); + expect(assignSpy).toHaveBeenCalled(); + }); + }); + + describe('Expired', () => { + let checkPaymentStatusValue; + beforeEach(() => { + checkPaymentStatusValue = { error: 'Unkown error', payload: 'Ab02b4c0!' }; + (checkPaymentStatus as jest.Mock).mockResolvedValue(checkPaymentStatusValue); + }); + + test('should show an error image', async () => { + renderAwait(defaultProps); + const image = await screen.findByAltText(/payment.*? failed/i); + expect(image).toBeTruthy(); + }); + + test('should call onComplete if there is a payload', async () => { + renderAwait(defaultProps); + await waitFor(() => + expect(defaultProps.onComplete).toHaveBeenCalledWith( + { + data: { + details: { payload: checkPaymentStatusValue.payload }, + paymentData: defaultProps.paymentData + } + }, + expect.any(Object) + ) + ); + }); + + test('should call onError if there is not a payload', async () => { + (checkPaymentStatus as jest.Mock).mockReset(); + checkPaymentStatusValue = { error: 'Un-known error' }; + (checkPaymentStatus as jest.Mock).mockResolvedValue(checkPaymentStatusValue); + renderAwait(defaultProps); + await waitFor(() => + expect(defaultProps.onError).toHaveBeenCalledWith(new AdyenCheckoutError('ERROR', 'error result with no payload in response')) + ); + }); + }); + + describe('Completed', () => { + let checkPaymentStatusValue; + beforeEach(() => { + checkPaymentStatusValue = { payload: 'Ab02b4c0!', resultCode: 'authorised', type: 'complete' }; + (checkPaymentStatus as jest.Mock).mockResolvedValue(checkPaymentStatusValue); + }); + + test('should show a success image', async () => { + renderAwait(defaultProps); + const image = await screen.findByAltText(/payment.*? successful/i); + expect(image).toBeTruthy(); + }); + + test('should call onComplete if there is a payload', () => { + renderAwait(defaultProps); + expect(defaultProps.onComplete).toHaveBeenCalledWith( + { + data: { + details: { payload: checkPaymentStatusValue.payload }, + paymentData: defaultProps.paymentData + } + }, + expect.any(Object) + ); + }); + }); + + describe('Loading', () => { + beforeEach(() => { + const checkPaymentStatusValue = { payload: 'Ab02b4c0!', resultCode: 'pending', type: 'complete' }; + (checkPaymentStatus as jest.Mock).mockResolvedValue(checkPaymentStatusValue); + }); + + test('should show the spinner on init', async () => { + renderAwait(defaultProps); + expect(await screen.findByTestId('spinner')).toBeTruthy(); + }); + + test('should show brand logo', async () => { + renderAwait(defaultProps); + const image = (await screen.findByAltText(defaultProps.type)) as HTMLImageElement; + expect(image.src).toContain(defaultProps.brandLogo); + }); + }); +}); diff --git a/packages/lib/src/components/internal/Await/Await.tsx b/packages/lib/src/components/internal/Await/Await.tsx index 0f6812dd06..f1971dc65b 100644 --- a/packages/lib/src/components/internal/Await/Await.tsx +++ b/packages/lib/src/components/internal/Await/Await.tsx @@ -76,14 +76,14 @@ function Await(props: AwaitComponentProps) { }; const checkStatus = (): void => { - const { paymentData, clientKey } = props; + const { paymentData, clientKey, throttleInterval } = props; if (!hasCalledActionHandled) { props.onActionHandled({ componentType: props.type, actionDescription: 'polling-started' }); setHasCalledActionHandled(true); } - checkPaymentStatus(paymentData, clientKey, loadingContext) + checkPaymentStatus(paymentData, clientKey, loadingContext, throttleInterval) .then(processResponse) .catch(({ message, ...response }) => ({ type: 'network-error', diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.scss b/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.scss index 9a968c9508..c10d9483d8 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.scss +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.scss @@ -1,14 +1,173 @@ -.adyen-checkout-ctp__loading { - &-image { - display: block; - margin: 30px auto auto; +@keyframes card-bounce { + 0% { + -webkit-translate: 0 -60%; + -moz-translate: 0 -60%; + -ms-translate: 0 -60%; + translate: 0 -60%; + animation-timing-function: cubic-bezier(0.17, 0.17, 0.32, 1); } - &-subtitle { - font-size: var(--adyen-checkout-spacer-070); - line-height: var(--adyen-checkout-text-caption-line-height); - max-width: 280px; - text-align: center; - margin: 0 auto 58px; + 9.70874% { + -webkit-translate: 0 8.5%; + -moz-translate: 0 8.5%; + -ms-translate: 0 8.5%; + translate: 0 8.5%; + animation-timing-function: cubic-bezier(0.41, 0, 0.49, 1); } + + 23.4466% { + -webkit-translate: 0 -22.5%; + -moz-translate: 0 -22.5%; + -ms-translate: 0 -22.5%; + translate: 0 -22.5%; + animation-timing-function: cubic-bezier(0.29, 0, 0.26, 1); + } + + 47.7184% { + -webkit-translate: 0; + -moz-translate: 0; + -ms-translate: 0; + translate: 0; + } + + 63.8835% { + -webkit-translate: 0; + -moz-translate: 0; + -ms-translate: 0; + translate: 0; + animation-timing-function: cubic-bezier(1, 0, 0.78, 1); + } + + 86.5534% { + -webkit-translate: 0 40.7%; + -moz-translate: 0 40.7%; + -ms-translate: 0 40.7%; + translate: 0 40.7%; + } + + 100% { + -webkit-translate: 0 40.7%; + -moz-translate: 0 40.7%; + -ms-translate: 0 40.7%; + translate: 0 40.7%; + } +} + +@keyframes card-fade-100 { + 0% { + opacity: 0; + animation-timing-function: linear; + } + + 8.1068% { + opacity: 1; + } + + 78.4466% { + opacity: 1; + } + + 86.5534% { + opacity: 0; + } + + 100% { + opacity: 0; + } +} + +@keyframes card-fade-60 { + 0% { + opacity: 0; + animation-timing-function: linear; + } + + 8.1068% { + opacity: 0.6; + } + + 78.4466% { + opacity: 0.6; + } + + 86.5534% { + opacity: 0; + } + + 100% { + opacity: 0; + } +} + +@keyframes card-fade-20 { + 0% { + opacity: 0; + animation-timing-function: linear; + } + + 8.1068% { + opacity: 0.2; + } + + 78.4466% { + opacity: 0.2; + } + + 86.5534% { + opacity: 0; + } + + 100% { + opacity: 0; + } +} + +.adyen-checkout-ctp__card-animation { + position: relative; + max-width: 100%; + aspect-ratio: 1/0.4380; + filter: grayscale(1); + width: 140px; + margin: 40px auto 50px; +} + +.adyen-checkout-ctp__card-animation >.adyen-checkout-ctp__card-animation-layer { + opacity: 0; + width: 100%; + height: 100%; + position: absolute; + background-size: contain!important; +} + +.adyen-checkout-ctp__card-animation >.adyen-checkout-ctp__card-animation-layer:nth-of-type(1) { + z-index: 3; + opacity: 1; + background: url('data:image/svg+xml,') left top no-repeat,url('data:image/svg+xml,') left top no-repeat; + animation: card-bounce 2060ms linear infinite both, card-fade-100 2060ms linear infinite both; + animation-delay: 100ms; +} + +.adyen-checkout-ctp__card-animation >.adyen-checkout-ctp__card-animation-layer:nth-of-type(2) { + z-index: 2; + opacity: 0.6; + transform: translateY(25%); + background: url('data:image/svg+xml,') left top no-repeat; + animation: card-bounce 2060ms linear infinite both, card-fade-60 2060ms linear infinite both; + animation-delay: 50ms; +} + +.adyen-checkout-ctp__card-animation >.adyen-checkout-ctp__card-animation-layer:nth-of-type(3) { + z-index: 1; + opacity: 0.2; + transform: translateY(50%); + background: url('data:image/svg+xml,') left top no-repeat; + animation: card-bounce 2060ms linear infinite both, card-fade-20 2060ms linear infinite both; +} + +.adyen-checkout-ctp__loading-subtitle { + font-size: var(--adyen-checkout-spacer-070); + line-height: var(--adyen-checkout-text-caption-line-height); + max-width: 280px; + text-align: center; + margin: 0 auto 58px; } diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.tsx b/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.tsx index 0177b82c13..719073158a 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.tsx +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.tsx @@ -1,21 +1,18 @@ import { Fragment, h } from 'preact'; import useCoreContext from '../../../../../core/Context/useCoreContext'; -import useImage from '../../../../../core/Context/useImage'; -import Img from '../../../Img'; import './CtPLoader.scss'; const CtPLoader = (): h.JSX.Element => { const { i18n } = useCoreContext(); - const getImage = useImage(); return ( - +
+
+
+
+
+
{i18n.get('ctp.loading.intro')}
); diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPLogin/CtPLoginInput.tsx b/packages/lib/src/components/internal/ClickToPay/components/CtPLogin/CtPLoginInput.tsx index 244b8df5fe..4a89bedc77 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPLogin/CtPLoginInput.tsx +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPLogin/CtPLoginInput.tsx @@ -4,7 +4,7 @@ import { loginValidationRules } from './validate'; import useCoreContext from '../../../../../core/Context/useCoreContext'; import useForm from '../../../../../utils/useForm'; import Field from '../../../FormFields/Field'; -import InputText from '../../../FormFields/InputText'; +import InputEmail from '../../../FormFields/InputEmail'; type OnChangeProps = { data: CtPLoginInputDataState; valid; errors; isValid: boolean }; @@ -73,7 +73,7 @@ const CtPLoginInput = (props: CtPLoginInputProps): h.JSX.Element => { errorMessage={isLoginInputDirty ? props.errorMessage || !!errors.shopperLogin : null} classNameModifiers={['shopperLogin']} > - { + return render( + + {/* eslint-disable-next-line react/no-children-prop */} + + + ); +}; + +test('should set CTP to primary payment method if shopper interacts with the login input', async () => { + const user = userEvent.setup(); + + const contextProps = mock(); + contextProps.isCtpPrimaryPaymentMethod = false; + contextProps.schemes = ['mc', 'visa']; + contextProps.setIsCtpPrimaryPaymentMethod.mockImplementation(isPrimary => { + contextProps.isCtpPrimaryPaymentMethod = isPrimary; + }); + + const { rerender } = customRender(, contextProps); + + let button = await screen.findByRole('button', { name: 'Continue' }); + expect(button).toHaveClass('adyen-checkout__button--secondary'); + + const input = await screen.findByLabelText('Email'); + + await user.click(input); + await user.keyboard('shopper@example.com'); + + rerender(customRender(, contextProps)); + + expect(contextProps.setIsCtpPrimaryPaymentMethod).toHaveBeenCalledWith(true); + + button = await screen.findByRole('button', { name: 'Continue' }); + expect(button).not.toHaveClass('adyen-checkout__button--secondary'); +}); + +test('should not start the user login if the email is invalid', async () => { + const user = userEvent.setup(); + + const contextProps = mock(); + contextProps.isCtpPrimaryPaymentMethod = true; + contextProps.setIsCtpPrimaryPaymentMethod.mockImplementation(() => {}); + contextProps.verifyIfShopperIsEnrolled.mockRejectedValue( + new SrciError({ reason: 'ID_FORMAT_UNSUPPORTED', message: '' }, 'verifyIfShopperIsEnrolled', 'visa') + ); + contextProps.schemes = ['mc', 'visa']; + + customRender(, contextProps); + + const input = await screen.findByLabelText('Email'); + await user.click(input); + await user.keyboard('my.invalid.email@example'); + + const button = await screen.findByRole('button', { name: 'Continue' }); + await user.click(button); + + expect(contextProps.startIdentityValidation).toHaveBeenCalledTimes(0); + expect(input).toBeInvalid(); + expect(await screen.findByText('Format not supported')).toBeInTheDocument(); + expect(button).not.toHaveClass('adyen-checkout__button--loading'); +}); + +test('should display not found if the email is not registered', async () => { + const user = userEvent.setup(); + + const contextProps = mock(); + contextProps.isCtpPrimaryPaymentMethod = true; + contextProps.setIsCtpPrimaryPaymentMethod.mockImplementation(() => {}); + contextProps.verifyIfShopperIsEnrolled.mockResolvedValue({ isEnrolled: false }); + contextProps.startIdentityValidation.mockImplementation(); + contextProps.schemes = ['mc', 'visa']; + + customRender(, contextProps); + + const input = await screen.findByLabelText('Email'); + await user.click(input); + await user.keyboard('my.invalid.email@example'); + + const button = await screen.findByRole('button', { name: 'Continue' }); + await user.click(button); + + expect(contextProps.startIdentityValidation).toHaveBeenCalledTimes(0); + expect(input).toBeInvalid(); + expect(await screen.findByText('No account found, enter a valid email or continue using manual card entry')).toBeInTheDocument(); + expect(button).not.toHaveClass('adyen-checkout__button--loading'); +}); + +test('should start the identity validation if the user is enrolled', async () => { + const user = userEvent.setup(); + + const contextProps = mock(); + contextProps.isCtpPrimaryPaymentMethod = true; + contextProps.setIsCtpPrimaryPaymentMethod.mockImplementation(() => {}); + contextProps.verifyIfShopperIsEnrolled.mockResolvedValue({ isEnrolled: true }); + contextProps.startIdentityValidation.mockImplementation(); + contextProps.schemes = ['mc', 'visa']; + + customRender(, contextProps); + + const input = await screen.findByLabelText('Email'); + await user.click(input); + await user.keyboard('shopper@email.com'); + + const button = await screen.findByRole('button', { name: 'Continue' }); + await user.click(button); + + expect(contextProps.startIdentityValidation).toHaveBeenCalledTimes(1); + expect(button).toHaveClass('adyen-checkout__button--loading'); +}); diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.test.tsx b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.test.tsx new file mode 100644 index 0000000000..5f852cf057 --- /dev/null +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.test.tsx @@ -0,0 +1,71 @@ +import { h } from 'preact'; +import { render, screen } from '@testing-library/preact'; +import CoreProvider from '../../../../../core/Context/CoreProvider'; +import ClickToPayProvider from '../../context/ClickToPayProvider'; +import { IClickToPayService } from '../../services/types'; +import { mock } from 'jest-mock-extended'; +import { Resources } from '../../../../../core/Context/Resources'; +import Language from '../../../../../language'; +import userEvent from '@testing-library/user-event'; +import CtPOneTimePassword from './CtPOneTimePassword'; + +const customRender = (ui, { clickToPayService = mock(), configuration = {} } = {}) => { + return render( + + + {ui} + + + ); +}; + +describe('Click to Pay - CtPOneTimePassword', () => { + test('should set to store the cookie if shopper ticks the checkbox', async () => { + const user = userEvent.setup(); + const ctpServiceMock = mock(); + ctpServiceMock.schemes = ['visa', 'mc']; + // const onResendCodeMock = jest.fn(); + + customRender(, { clickToPayService: ctpServiceMock }); + + // Default false + const checkbox = (await screen.findByLabelText('Skip verification next time')) as HTMLInputElement; + expect(checkbox.checked).toBe(false); + + // Checked + await user.click(checkbox); + expect(checkbox.checked).toBe(true); + expect(ctpServiceMock.updateStoreCookiesConsent).toHaveBeenCalledWith(true); + expect(ctpServiceMock.updateStoreCookiesConsent).toHaveBeenCalledTimes(1); + + // Unchecked + await user.click(checkbox); + expect(checkbox.checked).toBe(false); + expect(ctpServiceMock.updateStoreCookiesConsent).toHaveBeenCalledWith(false); + expect(ctpServiceMock.updateStoreCookiesConsent).toHaveBeenCalledTimes(2); + }); + + test('should pass OTP to ClickToPay service', async () => { + const user = userEvent.setup({ delay: 100 }); + const ctpServiceMock = mock(); + ctpServiceMock.schemes = ['visa', 'mc']; + + customRender(, { clickToPayService: ctpServiceMock }); + + await screen.findByLabelText('One time code', { exact: false }); + + await user.keyboard('654321'); + await user.keyboard('[Enter]'); + + expect(ctpServiceMock.finishIdentityValidation).toHaveBeenCalledWith('654321'); + }); +}); diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.tsx b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.tsx index 825c9173be..576aa2380b 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.tsx +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.tsx @@ -8,6 +8,7 @@ import { CtPInfo } from '../CtPInfo'; import CtPSection from '../CtPSection'; import useCoreContext from '../../../../../core/Context/useCoreContext'; import './CtPOneTimePassword.scss'; +import CtPSaveCookiesCheckbox from './CtPSaveCookiesCheckbox'; type CtPOneTimePasswordProps = { onDisplayCardComponent?(): void; @@ -80,6 +81,9 @@ const CtPOneTimePassword = ({ onDisplayCardComponent }: CtPOneTimePasswordProps) onResendCode={onResendCode} isValidatingOtp={isValidatingOtp} /> + + + + + ) : ( + {i18n.get('ctp.otp.saveCookiesCheckbox.information')} + )} +

+ + ); +} + +export default CtPSaveCookiesCheckbox; diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPSaveCookiesCheckbox/index.ts b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPSaveCookiesCheckbox/index.ts new file mode 100644 index 0000000000..bfaacd0df1 --- /dev/null +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPSaveCookiesCheckbox/index.ts @@ -0,0 +1 @@ +export { default } from './CtPSaveCookiesCheckbox'; diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.test.tsx b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.test.tsx new file mode 100644 index 0000000000..fe9feab751 --- /dev/null +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.test.tsx @@ -0,0 +1,88 @@ +import { ComponentChildren, h } from 'preact'; +import { mock } from 'jest-mock-extended'; +import { render, screen } from '@testing-library/preact'; +import userEvent from '@testing-library/user-event'; +import { ClickToPayContext, IClickToPayContext } from '../../context/ClickToPayContext'; +import CtPLogoutLink from './CtPLogoutLink'; +import { CtpState } from '../../services/ClickToPayService'; +import ShopperCard from '../../models/ShopperCard'; +import CoreProvider from '../../../../../core/Context/CoreProvider'; + +const customRender = (children: ComponentChildren, providerProps: IClickToPayContext) => { + return render( + // @ts-ignore TODO: Fix this weird complain + + {/* eslint-disable-next-line react/no-children-prop */} + + + ); +}; + +test('should not render if shopper is not recognized', async () => { + const contextProps = mock(); + contextProps.ctpState = CtpState.Login; + + const { container } = customRender(, contextProps); + + // @ts-ignore FIX TYPES + expect(container).toBeEmptyDOMElement(); +}); + +test('should render i18n message of ctp.logout.notYourCards if there are multiple cards available', async () => { + const user = userEvent.setup(); + + const contextProps = mock(); + contextProps.ctpState = CtpState.Ready; + contextProps.cards = [mock(), mock(), mock()]; + contextProps.logoutShopper.mockImplementation(); + + customRender(, contextProps); + expect(await screen.findByRole('button', { name: 'Not your cards?' })).toBeTruthy(); + + await user.click(screen.getByRole('button', { name: 'Not your cards?' })); + expect(contextProps.logoutShopper).toHaveBeenCalledTimes(1); +}); + +test('should render i18n message of ctp.logout.notYourCard if there is only one card available', async () => { + const user = userEvent.setup(); + + const contextProps = mock(); + contextProps.ctpState = CtpState.Ready; + contextProps.cards = [mock()]; + contextProps.logoutShopper.mockImplementation(); + + customRender(, contextProps); + expect(await screen.findByRole('button', { name: 'Not your card?' })).toBeTruthy(); + + await user.click(screen.getByRole('button', { name: 'Not your card?' })); + expect(contextProps.logoutShopper).toHaveBeenCalledTimes(1); +}); + +test('should render i18n message of ctp.logout.notYourProfile if there is no card available', async () => { + const user = userEvent.setup(); + + const contextProps = mock(); + contextProps.ctpState = CtpState.Ready; + contextProps.cards = []; + contextProps.logoutShopper.mockImplementation(); + + customRender(, contextProps); + expect(await screen.findByRole('button', { name: 'Not your profile?' })).toBeTruthy(); + + await user.click(screen.getByRole('button', { name: 'Not your profile?' })); + expect(contextProps.logoutShopper).toHaveBeenCalledTimes(1); +}); + +test('should render i18n message of ctp.logout.notYou if the shopper is going through OTP ', async () => { + const user = userEvent.setup(); + + const contextProps = mock(); + contextProps.ctpState = CtpState.OneTimePassword; + contextProps.logoutShopper.mockImplementation(); + + customRender(, contextProps); + expect(await screen.findByRole('button', { name: 'Not you?' })).toBeTruthy(); + + await user.click(screen.getByRole('button', { name: 'Not you?' })); + expect(contextProps.logoutShopper).toHaveBeenCalledTimes(1); +}); diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.tsx b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.tsx index 877985b9e9..34e4bd14dc 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.tsx +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.tsx @@ -3,10 +3,10 @@ import useClickToPayContext from '../../context/useClickToPayContext'; import { CtpState } from '../../services/ClickToPayService'; import classnames from 'classnames'; import { useMemo } from 'preact/hooks'; -import './CtPLogoutLink.scss'; import useCoreContext from '../../../../../core/Context/useCoreContext'; +import './CtPLogoutLink.scss'; -const CtPLogoutLink = (): h.JSX.Element => { +const CtPLogoutLink = () => { const { ctpState, logoutShopper, status, cards } = useClickToPayContext(); const { i18n } = useCoreContext(); diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPSection.scss b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPSection.scss index 9dd556017c..26661d53f6 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPSection.scss +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPSection.scss @@ -1,7 +1,7 @@ .adyen-checkout-ctp__section { position: relative; background-color: white; - box-shadow: 0 var(--adyen-checkout-spacer-040) var(--adyen-checkout-spacer-090) rgb(0 0 0 / 15%); + box-shadow: 0 6px 12px rgb(0 17 44 / 8%), 0 2px 4px rgb(0 17 44 / 4%); border-radius: var(--adyen-checkout-spacer-060); padding: var(--adyen-checkout-spacer-070); @@ -9,7 +9,7 @@ display: flex; align-items: center; height: 18px; - margin-bottom: 14px; + margin-bottom: 16px; } &--standalone { @@ -29,8 +29,8 @@ font-size: 17px; font-weight: var(--adyen-checkout-text-title-font-weight); line-height: 22px; - margin: 0 0 var(--adyen-checkout-spacer-020); padding: 0; + margin: 0; width: auto; @media screen and (max-width: 400px) { diff --git a/packages/lib/src/components/internal/ClickToPay/context/ClickToPayContext.ts b/packages/lib/src/components/internal/ClickToPay/context/ClickToPayContext.ts index 82fd483a01..b838a1c348 100644 --- a/packages/lib/src/components/internal/ClickToPay/context/ClickToPayContext.ts +++ b/packages/lib/src/components/internal/ClickToPay/context/ClickToPayContext.ts @@ -11,8 +11,10 @@ export interface IClickToPayContext extends Pick { isStandaloneComponent: boolean; isCtpPrimaryPaymentMethod: boolean; + isStoringCookies: boolean; setIsCtpPrimaryPaymentMethod(isPrimary: boolean): void; logoutShopper(): Promise; + updateStoreCookiesConsent(shouldStore: boolean): void; ctpState: CtpState; cards: ShopperCard[]; schemes: string[]; @@ -37,8 +39,10 @@ const ClickToPayContext = createContext({ configuration: null, isStandaloneComponent: null, isCtpPrimaryPaymentMethod: null, + isStoringCookies: false, setIsCtpPrimaryPaymentMethod: null, logoutShopper: null, + updateStoreCookiesConsent: null, ctpState: null, cards: [], schemes: [], diff --git a/packages/lib/src/components/internal/ClickToPay/context/ClickToPayProvider.tsx b/packages/lib/src/components/internal/ClickToPay/context/ClickToPayProvider.tsx index 41e3da4443..357f88d118 100644 --- a/packages/lib/src/components/internal/ClickToPay/context/ClickToPayProvider.tsx +++ b/packages/lib/src/components/internal/ClickToPay/context/ClickToPayProvider.tsx @@ -90,6 +90,13 @@ const ClickToPayProvider = ({ await ctpService?.logout(); }, [ctpService]); + const updateStoreCookiesConsent = useCallback( + (shouldStore: boolean) => { + ctpService.updateStoreCookiesConsent(shouldStore); + }, + [ctpService] + ); + return ( diff --git a/packages/lib/src/components/internal/ClickToPay/services/ClickToPayService.test.ts b/packages/lib/src/components/internal/ClickToPay/services/ClickToPayService.test.ts index b543b90723..1ecd73f515 100644 --- a/packages/lib/src/components/internal/ClickToPay/services/ClickToPayService.test.ts +++ b/packages/lib/src/components/internal/ClickToPay/services/ClickToPayService.test.ts @@ -4,8 +4,94 @@ import { ISrcSdkLoader } from './sdks/SrcSdkLoader'; import VisaSdk from './sdks/VisaSdk'; import MastercardSdk from './sdks/MastercardSdk'; import { IdentityLookupParams, SchemesConfiguration } from './types'; -import { SrciIdentityLookupResponse, SrcProfile } from './sdks/types'; +import { SrciCheckoutResponse, SrciIdentityLookupResponse, SrcProfile } from './sdks/types'; import SrciError from './sdks/SrciError'; +import ShopperCard from '../models/ShopperCard'; + +test('should be able to tweak the configuration to store the cookie', () => { + const visa = mock(); + const schemesConfig = mock(); + const sdkLoader = mock(); + sdkLoader.load.mockResolvedValue([visa]); + + const service = new ClickToPayService(schemesConfig, sdkLoader, 'test'); + expect(service.storeCookies).toBe(false); + + service.updateStoreCookiesConsent(true); + expect(service.storeCookies).toBe(true); + + service.updateStoreCookiesConsent(false); + expect(service.storeCookies).toBe(false); +}); + +test('should pass the complianceSettings if the cookie is set to be stored', async () => { + const checkoutResponseMock = mock(); + checkoutResponseMock.dcfActionCode = 'COMPLETE'; + + const profileFromVisaSrcSystem: SrcProfile = { + srcCorrelationId: '123456', + profiles: [ + { + maskedCards: [ + { + srcDigitalCardId: 'xxxx', + panLastFour: '8902', + dateOfCardLastUsed: '2019-09-28T08:10:02.312Z', + paymentCardDescriptor: 'visa', + panExpirationMonth: '12', + panExpirationYear: '2020', + digitalCardData: { + descriptorName: 'Visa', + artUri: 'https://image.com/visa' + }, + tokenId: '9w8e8e' + } + ] + } + ] + }; + + const visa = mock(); + // @ts-ignore Mocking readonly property + visa.schemeName = 'visa'; + visa.checkout.mockResolvedValue(checkoutResponseMock); + visa.init.mockResolvedValue(); + visa.isRecognized.mockResolvedValue({ recognized: true, idTokens: ['id-token'] }); + visa.getSrcProfile.mockResolvedValue(profileFromVisaSrcSystem); + + const sdkLoader = mock(); + const schemesConfig = mock(); + + const shopperCard = mock(); + shopperCard.srcDigitalCardId = 'xxxx'; + shopperCard.srcCorrelationId = 'zzzz'; + shopperCard.scheme = 'visa'; + Object.defineProperty(shopperCard, 'isDcfPopupEmbedded', { + get: jest.fn(() => false) + }); + + sdkLoader.load.mockResolvedValue([visa]); + + const service = new ClickToPayService(schemesConfig, sdkLoader, 'test'); + service.updateStoreCookiesConsent(true); + + await service.initialize(); + await service.checkout(shopperCard); + + expect(visa.checkout).toHaveBeenCalledTimes(1); + expect(visa.checkout).toHaveBeenCalledWith({ + complianceSettings: { + complianceResources: [ + { + complianceType: 'REMEMBER_ME', + uri: '' + } + ] + }, + srcCorrelationId: 'zzzz', + srcDigitalCardId: 'xxxx' + }); +}); test('should pass the correct configuration to the respective scheme SDKs', async () => { const visa = mock(); diff --git a/packages/lib/src/components/internal/ClickToPay/services/ClickToPayService.ts b/packages/lib/src/components/internal/ClickToPay/services/ClickToPayService.ts index 3b2e47b109..a23c882620 100644 --- a/packages/lib/src/components/internal/ClickToPay/services/ClickToPayService.ts +++ b/packages/lib/src/components/internal/ClickToPay/services/ClickToPayService.ts @@ -55,6 +55,11 @@ class ClickToPayService implements IClickToPayService { public shopperCards: ShopperCard[] = null; public identityValidationData: IdentityValidationData = null; + /** + * Indicates if the shopper opted for saving cookies during the transaction + */ + public storeCookies = false; + constructor( schemesConfig: SchemesConfiguration, sdkLoader: ISrcSdkLoader, @@ -77,12 +82,17 @@ class ClickToPayService implements IClickToPayService { return this.sdkLoader.schemes; } + public updateStoreCookiesConsent(shouldStore: boolean) { + this.storeCookies = shouldStore; + } + public async initialize(): Promise { this.setState(CtpState.Loading); try { this.sdks = await this.sdkLoader.load(this.environment); await this.initiateSdks(); + const { recognized = false, idTokens = null } = await this.verifyIfShopperIsRecognized(); if (recognized) { @@ -104,10 +114,14 @@ class ClickToPayService implements IClickToPayService { this.setState(CtpState.NotAvailable); } catch (error) { - if (error instanceof SrciError) console.warn(`Error at ClickToPayService # init: ${error.toString()}`); - if (error instanceof TimeoutError) { + if (error instanceof SrciError && error?.reason === 'REQUEST_TIMEOUT') { + const timeoutError = new TimeoutError(`ClickToPayService - Timeout during ${error.source}() of the scheme '${error.scheme}'`); + this.onTimeout?.(timeoutError); + } else if (error instanceof TimeoutError) { console.warn(error.toString()); this.onTimeout?.(error); + } else if (error instanceof SrciError) { + console.warn(`Error at ClickToPayService # init: ${error.toString()}`); } else { console.warn(error); } @@ -168,7 +182,8 @@ class ClickToPayService implements IClickToPayService { const checkoutResponse = await checkoutSdk.checkout({ srcDigitalCardId: card.srcDigitalCardId, srcCorrelationId: card.srcCorrelationId, - ...(card.isDcfPopupEmbedded && { windowRef: window.frames[CTP_IFRAME_NAME] }) + ...(card.isDcfPopupEmbedded && { windowRef: window.frames[CTP_IFRAME_NAME] }), + ...(this.storeCookies && { complianceSettings: { complianceResources: [{ complianceType: 'REMEMBER_ME', uri: '' }] } }) }); if (checkoutResponse.dcfActionCode !== 'COMPLETE') { diff --git a/packages/lib/src/components/internal/ClickToPay/services/sdks/MastercardSdk.test.ts b/packages/lib/src/components/internal/ClickToPay/services/sdks/MastercardSdk.test.ts new file mode 100644 index 0000000000..ae794b7969 --- /dev/null +++ b/packages/lib/src/components/internal/ClickToPay/services/sdks/MastercardSdk.test.ts @@ -0,0 +1,398 @@ +import MastercardSdk from './MastercardSdk'; +import Script from '../../../../../utils/Script'; +import { MC_SDK_PROD, MC_SDK_TEST } from './config'; + +const mockScriptLoaded = jest.fn().mockImplementation(() => { + window.SRCSDK_MASTERCARD = { + init: jest.fn().mockResolvedValue(() => {}), + identityLookup: jest.fn().mockResolvedValue({ consumerPresent: true }), + completeIdentityValidation: jest.fn().mockResolvedValue({ idToken: 'id-token' }), + unbindAppInstance: jest.fn(), + initiateIdentityValidation: jest.fn().mockResolvedValue({ maskedValidationChannel: '+31*******55' }), + isRecognized: jest.fn().mockResolvedValue({ + recognized: 'true', + idTokens: ['id-token'] + }), + checkout: jest.fn().mockResolvedValue({ + dcfActionCode: 'COMPLETE', + checkoutResponse: 'checkout-response' + }), + getSrcProfile: jest.fn().mockResolvedValue({ + srcCorrelationId: '1a2b3c', + profiles: [ + { + maskedCards: [ + { + srcDigitalCardId: 'yyyy', + panLastFour: '4302', + dateOfCardLastUsed: '2019-12-25T20:20:02.942Z', + paymentCardDescriptor: 'mc', + panExpirationMonth: '12', + panExpirationYear: '2020', + digitalCardData: { + descriptorName: 'Mastercard', + artUri: 'https://image.com/mc' + }, + tokenId: '2a2a3b3b' + } + ] + } + ] + }) + }; +}); + +const mockScriptRemoved = jest.fn(); + +jest.mock('../../../../../utils/Script', () => { + return jest.fn().mockImplementation(() => { + return { load: mockScriptLoaded, remove: mockScriptRemoved }; + }); +}); + +beforeEach(() => { + // @ts-ignore 'mockClear' is provided by jest.mock + Script.mockClear(); + mockScriptLoaded.mockClear(); + jest.resetModules(); +}); + +afterEach(() => { + delete window.SRCSDK_MASTERCARD; +}); + +describe('SDK urls', () => { + test('should load sdk script with correct URL for live', async () => { + const sdk = new MastercardSdk('live', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + expect(sdk.schemeSdk).toBeNull; + expect(sdk.schemeName).toBe('mc'); + + await sdk.loadSdkScript(); + + expect(Script).toHaveBeenCalledWith(MC_SDK_PROD); + expect(mockScriptLoaded).toHaveBeenCalledTimes(1); + }); + + test('should load sdk script with correct URL for test', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + expect(Script).toHaveBeenCalledWith(MC_SDK_TEST); + expect(mockScriptLoaded).toHaveBeenCalledTimes(1); + }); +}); + +describe('init()', () => { + test('should init with the correct values', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const srcInitiatorId = 'xxxx-yyyy'; + const srciDpaId = '123456'; + const srciTransactionId = '99999999'; + + await sdk.init({ srciDpaId, srcInitiatorId }, srciTransactionId); + + expect(sdk.schemeSdk.init).toHaveBeenCalledWith({ + dpaData: { + dpaPresentationName: 'MyStore' + }, + dpaTransactionOptions: { + confirmPayment: false, + consumerNameRequested: true, + customInputData: { + 'com.mastercard.dcfExperience': 'PAYMENT_SETTINGS' + }, + dpaLocale: 'en-US', + paymentOptions: { + dynamicDataType: 'CARD_APPLICATION_CRYPTOGRAM_SHORT_FORM' + } + }, + srcInitiatorId: 'xxxx-yyyy', + srciDpaId: '123456', + srciTransactionId: '99999999' + }); + }); + + test('should trigger error if init fails', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const mcError = { + message: 'Something went wrong', + reason: 'FAILED' + }; + + sdk.schemeSdk.init = jest.fn().mockRejectedValue(mcError); + + expect.assertions(4); + + await sdk.init({ srciDpaId: 'dpa-id', srcInitiatorId: 'initiator-id' }, 'transaction-id').catch(error => { + expect(error.scheme).toBe('mc'); + expect(error.source).toBe('init'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('identityLookup()', () => { + test('should call identityLookup with the correct values', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const response = await sdk.identityLookup({ identityValue: 'john@example.com', type: 'email' }); + + expect(response.consumerPresent).toBeTruthy(); + expect(sdk.schemeSdk.identityLookup).toHaveBeenCalledWith({ + consumerIdentity: { + identityValue: 'john@example.com', + identityType: 'EMAIL_ADDRESS' + } + }); + }); + + test('should trigger error if identityLookup fails', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const mcError = { + message: 'Something went wrong', + reason: 'FAILED' + }; + + sdk.schemeSdk.identityLookup = jest.fn().mockRejectedValue(mcError); + + expect.assertions(4); + + await sdk.identityLookup({ identityValue: 'test@example.com', type: 'email' }).catch(error => { + expect(error.scheme).toBe('mc'); + expect(error.source).toBe('identityLookup'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('completeIdentityValidation()', () => { + test('should call completeIdentityValidation with the correct values', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const otp = '123456'; + + const response = await sdk.completeIdentityValidation(otp); + + expect(response.idToken).toBeDefined(); + expect(sdk.schemeSdk.completeIdentityValidation).toHaveBeenCalledWith({ + validationData: otp + }); + }); + + test('should trigger error if completeIdentityValidation fails', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const mcError = { + message: 'Something went wrong', + reason: 'FAILED' + }; + + sdk.schemeSdk.completeIdentityValidation = jest.fn().mockRejectedValue(mcError); + + expect.assertions(4); + + await sdk.completeIdentityValidation('123456').catch(error => { + expect(error.scheme).toBe('mc'); + expect(error.source).toBe('completeIdentityValidation'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('checkout()', () => { + test('should call checkout with the correct values', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const response = await sdk.checkout({ + srcDigitalCardId: 'digital-id', + srcCorrelationId: 'correlation-id', + complianceSettings: { complianceResources: [{ complianceType: 'REMEMBER_ME', uri: '' }] } + }); + + expect(response.dcfActionCode).toBe('COMPLETE'); + expect(response.checkoutResponse).toBe('checkout-response'); + expect(sdk.schemeSdk.checkout).toHaveBeenCalledWith({ + srcDigitalCardId: 'digital-id', + srcCorrelationId: 'correlation-id', + complianceSettings: { complianceResources: [{ complianceType: 'REMEMBER_ME', uri: '' }] } + }); + }); + + test('should trigger error if checkout fails', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const mcError = { + message: 'Something went wrong', + reason: 'FAILED' + }; + + sdk.schemeSdk.checkout = jest.fn().mockRejectedValue(mcError); + + expect.assertions(4); + + await sdk.checkout({ srcCorrelationId: 'xxx', srcDigitalCardId: 'yyyy' }).catch(error => { + expect(error.scheme).toBe('mc'); + expect(error.source).toBe('checkout'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('unbindAppInstance()', () => { + test('should call unbind', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + await sdk.unbindAppInstance(); + + expect(sdk.schemeSdk.unbindAppInstance).toHaveBeenCalled(); + }); + + test('should trigger error if unbindAppInstance fails', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const mcError = { + message: 'Something went wrong', + reason: 'FAILED' + }; + + sdk.schemeSdk.unbindAppInstance = jest.fn().mockRejectedValue(mcError); + + expect.assertions(4); + + await sdk.unbindAppInstance().catch(error => { + expect(error.scheme).toBe('mc'); + expect(error.source).toBe('unbindAppInstance'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('isRecognized()', () => { + test('should call isRecognized', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const response = await sdk.isRecognized(); + + expect(sdk.schemeSdk.isRecognized).toHaveBeenCalledTimes(1); + expect(response.recognized).toBeTruthy(); + expect(response.idTokens).toBeDefined(); + }); + + test('should trigger error if isRecognized fails', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const mcError = { + message: 'Something went wrong', + reason: 'FAILED' + }; + + sdk.schemeSdk.isRecognized = jest.fn().mockRejectedValue(mcError); + + expect.assertions(4); + + await sdk.isRecognized().catch(error => { + expect(error.scheme).toBe('mc'); + expect(error.source).toBe('isRecognized'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('initiateIdentityValidation()', () => { + test('should call initiateIdentityValidation', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const response = await sdk.initiateIdentityValidation(); + + expect(response.maskedValidationChannel).toBe('+31*******55'); + expect(sdk.schemeSdk.initiateIdentityValidation).toHaveBeenCalledTimes(1); + }); + + test('should trigger error if initiateIdentityValidation fails', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const mcError = { + message: 'Something went wrong', + reason: 'FAILED' + }; + + sdk.schemeSdk.initiateIdentityValidation = jest.fn().mockRejectedValue(mcError); + + expect.assertions(4); + + await sdk.initiateIdentityValidation().catch(error => { + expect(error.scheme).toBe('mc'); + expect(error.source).toBe('initiateIdentityValidation'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('getSrcProfile()', () => { + test('should call getSrcProfile', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const response = await sdk.getSrcProfile(['id-token']); + + expect(response.srcCorrelationId).toBe('1a2b3c'); + expect(response.profiles[0].maskedCards[0]).toBeDefined(); + expect(sdk.schemeSdk.getSrcProfile).toHaveBeenCalledWith({ + idTokens: ['id-token'] + }); + }); + + test('should trigger error if getSrcProfile fails', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const mcError = { + message: 'Something went wrong', + reason: 'FAILED' + }; + + sdk.schemeSdk.getSrcProfile = jest.fn().mockRejectedValue(mcError); + + expect.assertions(4); + + await sdk.getSrcProfile(['xxxx']).catch(error => { + expect(error.scheme).toBe('mc'); + expect(error.source).toBe('getSrcProfile'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('Removing script', () => { + test('should remove script', async () => { + const sdk = new MastercardSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + sdk.removeSdkScript(); + + expect(mockScriptRemoved).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/lib/src/components/internal/ClickToPay/services/sdks/MastercardSdk.ts b/packages/lib/src/components/internal/ClickToPay/services/sdks/MastercardSdk.ts index 901439a5ae..0df9d20d75 100644 --- a/packages/lib/src/components/internal/ClickToPay/services/sdks/MastercardSdk.ts +++ b/packages/lib/src/components/internal/ClickToPay/services/sdks/MastercardSdk.ts @@ -33,12 +33,17 @@ class MastercardSdk extends AbstractSrcInitiator { } public async init(params: SrcInitParams, srciTransactionId: string): Promise { - const sdkProps = { - ...params, - ...getMastercardSettings(this.customSdkConfiguration), - srciTransactionId - }; - await this.schemeSdk.init(sdkProps); + try { + const sdkProps = { + ...params, + ...getMastercardSettings(this.customSdkConfiguration), + srciTransactionId + }; + await this.schemeSdk.init(sdkProps); + } catch (err) { + const srciError = new SrciError(err, 'init', this.schemeName); + throw srciError; + } } public async identityLookup({ identityValue, type }: SrcIdentityLookupParams): Promise { diff --git a/packages/lib/src/components/internal/ClickToPay/services/sdks/SrcSdkLoader.test.ts b/packages/lib/src/components/internal/ClickToPay/services/sdks/SrcSdkLoader.test.ts new file mode 100644 index 0000000000..d0bb7fec7a --- /dev/null +++ b/packages/lib/src/components/internal/ClickToPay/services/sdks/SrcSdkLoader.test.ts @@ -0,0 +1,58 @@ +import SrcSdkLoader from './SrcSdkLoader'; + +import VisaSdk from './VisaSdk'; +import MastercardSdk from './MastercardSdk'; +import AdyenCheckoutError from '../../../../../core/Errors/AdyenCheckoutError'; + +jest.mock('./VisaSdk'); +jest.mock('./MastercardSdk'); + +describe('load()', () => { + test('should resolve Promise when all SDKs load sucessfully', async () => { + jest.spyOn(VisaSdk.prototype, 'loadSdkScript').mockResolvedValueOnce(); + jest.spyOn(MastercardSdk.prototype, 'loadSdkScript').mockResolvedValueOnce(); + + const loader = new SrcSdkLoader(['visa', 'mc'], { dpaLocale: 'pt_BR', dpaPresentationName: 'MyStore' }); + const sdks = await loader.load('test'); + + expect(VisaSdk).toHaveBeenCalledWith('test', { dpaLocale: 'pt_BR', dpaPresentationName: 'MyStore' }); + expect(MastercardSdk).toHaveBeenCalledWith('test', { dpaLocale: 'pt_BR', dpaPresentationName: 'MyStore' }); + expect(sdks.length).toBe(2); + }); + + test('should reject Promise when all SDKs fail to load', async () => { + jest.spyOn(VisaSdk.prototype, 'loadSdkScript').mockRejectedValueOnce({}); + jest.spyOn(MastercardSdk.prototype, 'loadSdkScript').mockRejectedValueOnce({}); + + const loader = new SrcSdkLoader(['visa', 'mc'], { dpaLocale: 'pt_BR', dpaPresentationName: 'MyStore' }); + + expect.assertions(2); + + await loader.load('test').catch(error => { + expect(error).toBeInstanceOf(AdyenCheckoutError); + expect(error.message).toContain('ClickToPay -> SrcSdkLoader # Unable to load network schemes'); + }); + }); + + test('should resolve when at least one SDK loaded sucessfully', async () => { + jest.spyOn(VisaSdk.prototype, 'loadSdkScript').mockRejectedValueOnce({}); + jest.spyOn(MastercardSdk.prototype, 'loadSdkScript').mockResolvedValue(); + + const loader = new SrcSdkLoader(['visa', 'mc'], { dpaLocale: 'pt_BR', dpaPresentationName: 'MyStore' }); + const sdks = await loader.load('live'); + + expect(sdks.length).toBe(1); + expect(sdks[0]).toBeInstanceOf(MastercardSdk); + }); + + test('should throw error if no schemes are passed', async () => { + const loader = new SrcSdkLoader([], { dpaLocale: 'pt_BR', dpaPresentationName: 'MyStore' }); + + expect.assertions(2); + + await loader.load('test').catch(error => { + expect(error).toBeInstanceOf(AdyenCheckoutError); + expect(error.message).toContain('ClickToPay -> SrcSdkLoader: There are no schemes set to be loaded'); + }); + }); +}); diff --git a/packages/lib/src/components/internal/ClickToPay/services/sdks/SrcSdkLoader.ts b/packages/lib/src/components/internal/ClickToPay/services/sdks/SrcSdkLoader.ts index af0b8c4cac..975abd7d97 100644 --- a/packages/lib/src/components/internal/ClickToPay/services/sdks/SrcSdkLoader.ts +++ b/packages/lib/src/components/internal/ClickToPay/services/sdks/SrcSdkLoader.ts @@ -31,7 +31,7 @@ class SrcSdkLoader implements ISrcSdkLoader { } public async load(environment: string): Promise { - if (!this.schemes) { + if (!this.schemes || this.schemes.length === 0) { throw new AdyenCheckoutError('ERROR', 'ClickToPay -> SrcSdkLoader: There are no schemes set to be loaded'); } diff --git a/packages/lib/src/components/internal/ClickToPay/services/sdks/SrciError.ts b/packages/lib/src/components/internal/ClickToPay/services/sdks/SrciError.ts index e09f4a3a0e..58d29fac7d 100644 --- a/packages/lib/src/components/internal/ClickToPay/services/sdks/SrciError.ts +++ b/packages/lib/src/components/internal/ClickToPay/services/sdks/SrciError.ts @@ -1,11 +1,11 @@ import { ClickToPayScheme } from '../../types'; -type MastercardError = { +export type MastercardError = { message: string; reason: string; }; -type VisaError = { +export type VisaError = { error: { message: string; reason: string; diff --git a/packages/lib/src/components/internal/ClickToPay/services/sdks/VisaSdk.test.ts b/packages/lib/src/components/internal/ClickToPay/services/sdks/VisaSdk.test.ts new file mode 100644 index 0000000000..21150d0d89 --- /dev/null +++ b/packages/lib/src/components/internal/ClickToPay/services/sdks/VisaSdk.test.ts @@ -0,0 +1,177 @@ +import VisaSdk from './VisaSdk'; +import Script from '../../../../../utils/Script'; +import { VISA_SDK_PROD, VISA_SDK_TEST } from './config'; +import { VisaError } from './SrciError'; + +const mockScriptLoaded = jest.fn().mockImplementation(() => { + window.vAdapters = { + VisaSRCI: jest.fn().mockImplementation(() => ({ + init: jest.fn().mockResolvedValue(() => {}), + identityLookup: jest.fn().mockResolvedValue({ consumerPresent: true }), + completeIdentityValidation: jest.fn().mockResolvedValue({ idToken: 'id-token' }) + })) + }; +}); + +const mockScriptRemoved = jest.fn(); + +jest.mock('../../../../../utils/Script', () => { + return jest.fn().mockImplementation(() => { + return { load: mockScriptLoaded, remove: mockScriptRemoved }; + }); +}); + +beforeEach(() => { + // @ts-ignore 'mockClear' is provided by jest.mock + Script.mockClear(); + mockScriptLoaded.mockClear(); + jest.resetModules(); +}); + +afterEach(() => { + delete window?.vAdapters?.VisaSRCI; +}); + +describe('SDK urls', () => { + test('should load sdk script with correct URL for live', async () => { + const sdk = new VisaSdk('live', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + expect(sdk.schemeSdk).toBeNull; + expect(sdk.schemeName).toBe('visa'); + + await sdk.loadSdkScript(); + + expect(Script).toHaveBeenCalledWith(VISA_SDK_PROD); + expect(mockScriptLoaded).toHaveBeenCalledTimes(1); + }); + + test('should load sdk script with correct URL for test', async () => { + const sdk = new VisaSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + expect(Script).toHaveBeenCalledWith(VISA_SDK_TEST); + expect(mockScriptLoaded).toHaveBeenCalledTimes(1); + }); +}); + +describe('init()', () => { + test('should init with the correct values', async () => { + const sdk = new VisaSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const srcInitiatorId = 'xxxx-yyyy'; + const srciDpaId = '123456'; + const srciTransactionId = '99999999'; + + await sdk.init({ srciDpaId, srcInitiatorId }, srciTransactionId); + + expect(sdk.schemeSdk.init).toHaveBeenCalledWith({ + dpaData: { dpaPresentationName: 'MyStore' }, + dpaTransactionOptions: { + customInputData: { checkoutOrchestrator: 'merchant' }, + dpaLocale: 'en-US', + payloadTypeIndicator: 'NON_PAYMENT' + }, + srcInitiatorId: 'xxxx-yyyy', + srciDpaId: '123456', + srciTransactionId: '99999999' + }); + }); + + test('should trigger error if init fails', async () => { + const sdk = new VisaSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const error: VisaError = { + error: { + message: 'Something went wrong', + reason: 'FAILED' + } + }; + + sdk.schemeSdk.init = jest.fn().mockRejectedValue(error); + + expect.assertions(4); + + await sdk.init({ srciDpaId: 'dpa-id', srcInitiatorId: 'initiator-id' }, 'transaction-id').catch(error => { + expect(error.scheme).toBe('visa'); + expect(error.source).toBe('init'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('identityLookup()', () => { + test('should call identityLookup with the correct values', async () => { + const sdk = new VisaSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const response = await sdk.identityLookup({ identityValue: 'john@example.com', type: 'email' }); + + expect(response.consumerPresent).toBeTruthy(); + expect(sdk.schemeSdk.identityLookup).toHaveBeenCalledWith({ + identityValue: 'john@example.com', + type: 'EMAIL' + }); + }); + + test('should trigger error if identityLookup fails', async () => { + const sdk = new VisaSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const error: VisaError = { + error: { + message: 'Something went wrong', + reason: 'FAILED' + } + }; + + sdk.schemeSdk.identityLookup = jest.fn().mockRejectedValue(error); + + expect.assertions(4); + + await sdk.identityLookup({ identityValue: 'test@example.com', type: 'email' }).catch(error => { + expect(error.scheme).toBe('visa'); + expect(error.source).toBe('identityLookup'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); + +describe('completeValidation()', () => { + test('should call completeIdentityValidation with the correct values', async () => { + const sdk = new VisaSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const otp = '123456'; + + const response = await sdk.completeIdentityValidation(otp); + + expect(response.idToken).toBeDefined(); + expect(sdk.schemeSdk.completeIdentityValidation).toHaveBeenCalledWith(otp); + }); + + test('should trigger error if completeIdentityValidation fails', async () => { + const sdk = new VisaSdk('test', { dpaLocale: 'en-US', dpaPresentationName: 'MyStore' }); + await sdk.loadSdkScript(); + + const error: VisaError = { + error: { + message: 'Something went wrong', + reason: 'FAILED' + } + }; + + sdk.schemeSdk.completeIdentityValidation = jest.fn().mockRejectedValue(error); + + expect.assertions(4); + + await sdk.completeIdentityValidation('123456').catch(error => { + expect(error.scheme).toBe('visa'); + expect(error.source).toBe('completeIdentityValidation'); + expect(error.reason).toBe('FAILED'); + expect(error.message).toBe('Something went wrong'); + }); + }); +}); diff --git a/packages/lib/src/components/internal/ClickToPay/services/sdks/VisaSdk.ts b/packages/lib/src/components/internal/ClickToPay/services/sdks/VisaSdk.ts index bbe3b3ae35..90eb4249b6 100644 --- a/packages/lib/src/components/internal/ClickToPay/services/sdks/VisaSdk.ts +++ b/packages/lib/src/components/internal/ClickToPay/services/sdks/VisaSdk.ts @@ -33,13 +33,18 @@ class VisaSdk extends AbstractSrcInitiator { } public async init(params: SrcInitParams, srciTransactionId: string): Promise { - const sdkProps = { - ...params, - ...getVisaSetttings(this.customSdkConfiguration), - srciTransactionId - }; + try { + const sdkProps = { + ...params, + ...getVisaSetttings(this.customSdkConfiguration), + srciTransactionId + }; - await this.schemeSdk.init(sdkProps); + await this.schemeSdk.init(sdkProps); + } catch (err) { + const srciError = new SrciError(err, 'init', this.schemeName); + throw srciError; + } } public async identityLookup({ identityValue, type }: SrcIdentityLookupParams): Promise { diff --git a/packages/lib/src/components/internal/ClickToPay/services/sdks/types.ts b/packages/lib/src/components/internal/ClickToPay/services/sdks/types.ts index b52152f2f6..9311f7f174 100644 --- a/packages/lib/src/components/internal/ClickToPay/services/sdks/types.ts +++ b/packages/lib/src/components/internal/ClickToPay/services/sdks/types.ts @@ -1,3 +1,12 @@ +declare global { + interface Window { + SRCSDK_MASTERCARD?: object; + vAdapters: { + VisaSRCI?: object; + }; + } +} + /** * Type that represent the object which contains the customizable properties of the SDK initialization */ @@ -59,6 +68,16 @@ export type SrcCard = { export type SrcCheckoutParams = { srcCorrelationId: string; srcDigitalCardId: string; + complianceSettings?: { + complianceResources: [ + { + complianceType: 'REMEMBER_ME'; + uri: string; + version?: string; + dataPublished?: string; + } + ]; + }; windowRef?: Window; }; diff --git a/packages/lib/src/components/internal/ClickToPay/services/types.ts b/packages/lib/src/components/internal/ClickToPay/services/types.ts index c6472d48b6..e42fe3fe0e 100644 --- a/packages/lib/src/components/internal/ClickToPay/services/types.ts +++ b/packages/lib/src/components/internal/ClickToPay/services/types.ts @@ -9,6 +9,8 @@ export interface IClickToPayService { identityValidationData: IdentityValidationData; schemes: string[]; shopperAccountFound: boolean; + storeCookies: boolean; + updateStoreCookiesConsent(shouldStore: boolean): void; initialize(): Promise; checkout(card: ShopperCard): Promise; logout(): Promise; diff --git a/packages/lib/src/components/internal/Countdown/CountdownA11yReporter.ts b/packages/lib/src/components/internal/Countdown/CountdownA11yReporter.ts index d5a1972a60..6733a1f914 100644 --- a/packages/lib/src/components/internal/Countdown/CountdownA11yReporter.ts +++ b/packages/lib/src/components/internal/Countdown/CountdownA11yReporter.ts @@ -22,6 +22,7 @@ export class CountdownA11yReporter { constructor(props: ICountdownA11yService) { const { srPanel, i18n } = props; + this.srPanel = srPanel; this.i18n = i18n; // Force the srPanel to update ariaRelevant diff --git a/packages/lib/src/components/internal/FormFields/InputEmail.tsx b/packages/lib/src/components/internal/FormFields/InputEmail.tsx index 888601615f..9da03e0d6c 100644 --- a/packages/lib/src/components/internal/FormFields/InputEmail.tsx +++ b/packages/lib/src/components/internal/FormFields/InputEmail.tsx @@ -1,6 +1,6 @@ import { h } from 'preact'; -import InputBase from './InputBase'; +import InputBase, { InputBaseProps } from './InputBase'; -export default function InputEmail(props) { +export default function InputEmail(props: InputBaseProps) { return ; } diff --git a/packages/lib/src/components/internal/FormFields/Select/Select.tsx b/packages/lib/src/components/internal/FormFields/Select/Select.tsx index d8505cdd1c..c1ba6e35f5 100644 --- a/packages/lib/src/components/internal/FormFields/Select/Select.tsx +++ b/packages/lib/src/components/internal/FormFields/Select/Select.tsx @@ -26,7 +26,8 @@ function Select({ uniqueId, disabled, disableTextFilter, - clearOnSelect + clearOnSelect, + blurOnClose }: SelectProps) { const filterInputRef = useRef(null); const selectContainerRef = useRef(null); @@ -77,6 +78,8 @@ function Select({ * Closes the selectList, empties the text filter and focuses the button element */ const closeList = () => { + //blurs the field when the list is closed, makes for a better UX for most users, needs more testing + blurOnClose && filterInputRef.current.blur(); setShowList(false); }; diff --git a/packages/lib/src/components/internal/FormFields/Select/types.ts b/packages/lib/src/components/internal/FormFields/Select/types.ts index bed81faa85..20580da838 100644 --- a/packages/lib/src/components/internal/FormFields/Select/types.ts +++ b/packages/lib/src/components/internal/FormFields/Select/types.ts @@ -32,6 +32,7 @@ export interface SelectProps { disabled?: boolean; disableTextFilter?: boolean; clearOnSelect?: boolean; + blurOnClose?: boolean; } export interface SelectButtonProps { diff --git a/packages/lib/src/components/internal/OpenInvoice/OpenInvoice.scss b/packages/lib/src/components/internal/OpenInvoice/OpenInvoice.scss index ed53d00120..1e14044603 100644 --- a/packages/lib/src/components/internal/OpenInvoice/OpenInvoice.scss +++ b/packages/lib/src/components/internal/OpenInvoice/OpenInvoice.scss @@ -20,7 +20,7 @@ } .adyen-checkout__open-invoice .adyen-checkout__field--consentCheckbox { - margin-top: 22px; + margin-top: 16px; } .adyen-checkout__input--separateDeliveryAddress + .adyen-checkout__checkbox__label { diff --git a/packages/lib/src/components/internal/PhoneInput/PhoneInput.test.tsx b/packages/lib/src/components/internal/PhoneInput/PhoneInput.test.tsx index 8890d2623a..0d489c37b4 100644 --- a/packages/lib/src/components/internal/PhoneInput/PhoneInput.test.tsx +++ b/packages/lib/src/components/internal/PhoneInput/PhoneInput.test.tsx @@ -2,33 +2,68 @@ import { h } from 'preact'; import { fireEvent, render, screen } from '@testing-library/preact'; import CoreProvider from '../../../core/Context/CoreProvider'; import { Resources } from '../../../core/Context/Resources'; +import userEvent from '@testing-library/user-event'; import PhoneInput from './PhoneInput'; +import { PhoneInputProps } from './types'; + +const items = [{ id: '+44', name: 'United Kingdom', code: 'GB', selectedOptionName: 'United Kingdom' }]; describe('PhoneInput', () => { - const items = [{ id: '+7', name: 'Russian Federation', code: 'RU' }]; - const customRender = ui => { + const defaultProps: PhoneInputProps = { + items, + data: { phonePrefix: items[0].id }, + onChange: jest.fn(), + phoneNumberErrorKey: 'mobileNumber.invalid', + placeholders: {} + }; + + const renderPhoneInput = (props: PhoneInputProps = defaultProps) => { return render( // @ts-ignore ignore - {ui} + ); }; - test('should render Prefix', async () => { - customRender( {}} />); - expect(await screen.findByText('Prefix')).toBeTruthy(); + test('should show phone prefix and phone number', async () => { + renderPhoneInput(); + expect(await screen.findByRole('combobox')).toBeTruthy(); + expect(await screen.findByRole('textbox')).toBeTruthy(); }); - test('should render Telephone number', async () => { - customRender( {}} />); - expect(await screen.findByText('Telephone number')).toBeTruthy(); + test('should show an error message for the invalid input', async () => { + const user = userEvent.setup({ delay: 100 }); + renderPhoneInput(); + const phoneNumberEle = await screen.findByRole('textbox'); + await user.type(phoneNumberEle, '1'); + fireEvent.blur(phoneNumberEle); + // @ts-ignore TODO: Fix jest types + expect(await screen.findByText(/invalid mobile number/i)).toBeInTheDocument(); }); - test('should show error message for an invalid telephone number', async () => { - customRender( {}} />); - const input = await screen.findByLabelText('Telephone number'); - fireEvent.blur(input, { target: { value: 1 } }); - expect(await screen.findByText('Invalid mobile number')).toBeTruthy(); + test('should show a success icon for the valid input', async () => { + const user = userEvent.setup({ delay: 100 }); + renderPhoneInput(); + const phoneNumberEle = await screen.findByRole('textbox'); + await user.type(phoneNumberEle, '123456'); + fireEvent.blur(phoneNumberEle); + const successIcons = await screen.findAllByRole('img'); + expect(successIcons.length).toBe(2); + }); + + test('should call onChange when the data has been changed', async () => { + const user = userEvent.setup({ delay: 100 }); + renderPhoneInput(); + const phoneNumberEle = await screen.findByRole('textbox'); + await user.type(phoneNumberEle, '123456'); + expect(defaultProps.onChange).toHaveBeenCalledWith( + expect.objectContaining({ + data: { phoneNumber: '123456', phonePrefix: '+44' }, + errors: expect.any(Object), + valid: expect.any(Object), + isValid: expect.any(Boolean) + }) + ); }); }); diff --git a/packages/lib/src/components/internal/PhoneInput/usePhonePrefixes.test.tsx b/packages/lib/src/components/internal/PhoneInput/usePhonePrefixes.test.tsx new file mode 100644 index 0000000000..d6fe785ce9 --- /dev/null +++ b/packages/lib/src/components/internal/PhoneInput/usePhonePrefixes.test.tsx @@ -0,0 +1,63 @@ +import usePhonePrefixes from './usePhonePrefixes'; +import getDataset from '../../../core/Services/get-dataset'; +import { renderHook } from '@testing-library/preact-hooks'; +import { waitFor } from '@testing-library/preact'; +import AdyenCheckoutError from '../../../core/Errors/AdyenCheckoutError'; + +jest.mock('../../../core/Services/get-dataset'); + +const getFlagEmoji = ({ id }) => { + const codePoints: number[] = id + .toUpperCase() + .split('') + .map(char => 127397 + char.charCodeAt(0)); + + return String.fromCodePoint ? String.fromCodePoint(...codePoints) + '\u00A0\u00A0' : ''; +}; + +describe('usePhonePrefixes', () => { + const props = { allowedCountries: [], loadingContext: 'test', handleError: jest.fn() }; + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('should return a list of phone prefixes', async () => { + const datasetMock = [{ id: 'AF', prefix: '+93' }]; + (getDataset as jest.Mock).mockResolvedValue(datasetMock); + + const { result } = renderHook(() => usePhonePrefixes(props)); + const expectedPrefixes = datasetMock.map(item => ({ + id: item.prefix, + name: `${getFlagEmoji(item)} ${item.prefix} (${item.id})`, + selectedOptionName: `${getFlagEmoji(item)} ${item.prefix}` + })); + await waitFor(() => expect(result.current).toEqual({ loadingStatus: 'ready', phonePrefixes: expectedPrefixes })); + }); + + test('should return a list of phone prefixes for allowedCountries', async () => { + const datasetMock = [ + { id: 'AF', prefix: '+93' }, + { id: 'TEST', prefix: '+00' } + ]; + (getDataset as jest.Mock).mockResolvedValue(datasetMock); + + const { result } = renderHook(() => usePhonePrefixes({ ...props, allowedCountries: ['AF'] })); + const expectedPrefixes = [ + { + id: datasetMock[0].prefix, + name: `${getFlagEmoji(datasetMock[0])} ${datasetMock[0].prefix} (${datasetMock[0].id})`, + selectedOptionName: `${getFlagEmoji(datasetMock[0])} ${datasetMock[0].prefix}` + } + ]; + await waitFor(() => expect(result.current).toEqual({ loadingStatus: 'ready', phonePrefixes: expectedPrefixes })); + }); + + test('should return an empty array when getDataset failed', async () => { + (getDataset as jest.Mock).mockRejectedValue([]); + + const { result } = renderHook(() => usePhonePrefixes(props)); + await waitFor(() => expect(result.current).toEqual({ loadingStatus: 'ready', phonePrefixes: [] })); + expect(props.handleError).toHaveBeenCalledWith(new AdyenCheckoutError('ERROR')); + }); +}); diff --git a/packages/lib/src/components/internal/QRLoader/QRLoader.test.tsx b/packages/lib/src/components/internal/QRLoader/QRLoader.test.tsx index d0c5309f5c..ff3caa814a 100644 --- a/packages/lib/src/components/internal/QRLoader/QRLoader.test.tsx +++ b/packages/lib/src/components/internal/QRLoader/QRLoader.test.tsx @@ -7,6 +7,7 @@ import { SRPanel } from '../../../core/Errors/SRPanel'; import SRPanelProvider from '../../../core/Errors/SRPanelProvider'; jest.mock('../../../core/Services/payment-status'); +jest.useFakeTimers(); const getWrapper = ui => { const srPanel = new SRPanel({ core: global.core }); @@ -24,6 +25,11 @@ describe('QRLoader', () => { }); }); + afterEach(() => { + jest.clearAllTimers(); + jest.restoreAllMocks(); + }); + describe('checkStatus', () => { // Pending status test('checkStatus processes a pending response', () => { @@ -97,5 +103,32 @@ describe('QRLoader', () => { expect(onErrorMock.mock.calls.length).toBe(1); }); }); + + describe('statusInterval', () => { + let qrLoader; + + beforeEach(() => { + const checkPaymentStatusValue = { payload: 'Ab02b4c0!', resultCode: 'pending', type: 'complete' }; + (checkPaymentStatus as jest.Mock).mockResolvedValue(checkPaymentStatusValue); + }); + + test('should set a timeout recursively', async () => { + jest.spyOn(global, 'setTimeout'); + qrLoader = new QRLoader({ delay: 1000 }); + qrLoader.statusInterval(); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); + await jest.runOnlyPendingTimersAsync(); + expect(setTimeout).toHaveBeenCalledTimes(2); + }); + + test('should change the delay to the throttledInterval if the timePassed exceeds the throttleTime', async () => { + jest.spyOn(global, 'setTimeout'); + qrLoader = new QRLoader({ throttleTime: 0, throttledInterval: 2000, delay: 1000 }); + qrLoader.statusInterval(); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 2000); + }); + }); }); }); diff --git a/packages/lib/src/components/internal/QRLoader/QRLoader.tsx b/packages/lib/src/components/internal/QRLoader/QRLoader.tsx index 41beadb92e..ad083eaa2b 100644 --- a/packages/lib/src/components/internal/QRLoader/QRLoader.tsx +++ b/packages/lib/src/components/internal/QRLoader/QRLoader.tsx @@ -19,7 +19,7 @@ import useAutoFocus from '../../../utils/useAutoFocus'; const QRCODE_URL = 'barcode.shtml?barcodeType=qrCode&fileType=png&data='; class QRLoader extends Component { - private interval; + private timeoutId; constructor(props) { super(props); @@ -43,38 +43,44 @@ class QRLoader extends Component { throttleTime: 60000, classNameModifiers: [], throttledInterval: 10000, - introduction: 'wechatpay.scanqrcode' - }; - - // Retry until getting a complete response from the server or it times out\ - // Changes interval time to 10 seconds after 1 minute (60 seconds) - public statusInterval = () => { - this.checkStatus(); - - this.setState({ timePassed: this.state.timePassed + this.props.delay }); - - if (this.state.timePassed >= this.props.throttleTime) { - this.setState({ delay: this.props.throttledInterval }); - } + introduction: 'wechatpay.scanqrcode', + timeToPay: 'wechatpay.timetopay', + buttonLabel: 'openApp' }; componentDidMount() { - this.interval = setInterval(this.statusInterval, this.state.delay); + this.statusInterval(); + } + + componentWillUnmount() { + clearTimeout(this.timeoutId); } - public redirectToApp = url => { + public redirectToApp = (url: string | URL) => { window.location.assign(url); }; - componentDidUpdate(prevProps, prevState) { - if (prevState.delay !== this.state.delay) { - clearInterval(this.interval); - this.interval = setInterval(this.statusInterval, this.state.delay); - } - } + // Retry until getting a complete response from the server, or it times out + public statusInterval = (responseTime = 0) => { + // If we are already in the final statuses, do not poll! + if (this.state.expired || this.state.completed) return; - componentWillUnmount() { - clearInterval(this.interval); + this.setState(previous => ({ timePassed: previous.timePassed + this.props.delay + responseTime })); + // Changes interval time to 10 seconds after 1 minute (60 seconds) + const newDelay = this.state.timePassed >= this.props.throttleTime ? this.props.throttledInterval : this.state.delay; + this.pollStatus(newDelay); + }; + + private pollStatus(delay: number) { + clearTimeout(this.timeoutId); + this.timeoutId = setTimeout(async () => { + // Wait for previous status call to finish. + // Also taking the server response time into the consideration to calculate timePassed. + const start = performance.now(); + await this.checkStatus(); + const end = performance.now(); + this.statusInterval(Math.round(end - start)); + }, delay); } private onTick = (time): void => { @@ -83,12 +89,12 @@ class QRLoader extends Component { private onTimeUp = (): void => { this.setState({ expired: true }); - clearInterval(this.interval); + clearTimeout(this.timeoutId); this.props.onError(new AdyenCheckoutError('ERROR', 'Payment Expired')); }; private onComplete = (status: StatusObject): void => { - clearInterval(this.interval); + clearTimeout(this.timeoutId); this.setState({ completed: true, loading: false }); const state = { @@ -102,7 +108,7 @@ class QRLoader extends Component { }; private onError = (status: StatusObject): void => { - clearInterval(this.interval); + clearTimeout(this.timeoutId); this.setState({ expired: true, loading: false }); if (status.props.payload) { @@ -120,9 +126,9 @@ class QRLoader extends Component { }; private checkStatus = () => { - const { paymentData, clientKey, loadingContext } = this.props; + const { paymentData, clientKey, loadingContext, throttledInterval } = this.props; - return checkPaymentStatus(paymentData, clientKey, loadingContext) + return checkPaymentStatus(paymentData, clientKey, loadingContext, throttledInterval) .then(processResponse) .catch(response => ({ type: 'network-error', props: response })) .then((status: StatusObject) => { @@ -144,7 +150,6 @@ class QRLoader extends Component { const { i18n, loadingContext } = useCoreContext(); const getImage = useImage(); const qrCodeImage = this.props.qrCodeData ? `${loadingContext}${QRCODE_URL}${this.props.qrCodeData}` : this.props.qrCodeImage; - const finalState = (image, message) => { const status = i18n.get(message); useA11yReporter(status); @@ -177,7 +182,7 @@ class QRLoader extends Component { ); } - const timeToPayString = i18n.get('wechatpay.timetopay').split('%@'); + const timeToPayString = i18n.get(this.props.timeToPay).split('%@'); const qrSubtitleRef = useAutoFocus(); @@ -197,7 +202,10 @@ class QRLoader extends Component { {url && (
-
)} @@ -211,7 +219,7 @@ class QRLoader extends Component { src={qrCodeImage} alt={i18n.get('wechatpay.scanqrcode')} onLoad={() => { - onActionHandled({ componentType: this.props.type, actionDescription: 'qr-code-loaded' }); + onActionHandled?.({ componentType: this.props.type, actionDescription: 'qr-code-loaded' }); }} /> @@ -225,7 +233,11 @@ class QRLoader extends Component {  {timeToPayString[1]} - {this.props.instructions &&
{i18n.get(this.props.instructions)}
} + {typeof this.props.instructions === 'string' ? ( +
{i18n.get(this.props.instructions)}
+ ) : ( + this.props.instructions?.() + )} {this.props.copyBtn && (
diff --git a/packages/lib/src/components/internal/QRLoader/types.ts b/packages/lib/src/components/internal/QRLoader/types.ts index abc481b44d..6fe938d0b0 100644 --- a/packages/lib/src/components/internal/QRLoader/types.ts +++ b/packages/lib/src/components/internal/QRLoader/types.ts @@ -1,5 +1,6 @@ import { ActionHandledReturnObject, PaymentAmount } from '../../../types/global-types'; import Language from '../../../language/Language'; +import { h } from 'preact'; export interface QRLoaderProps { delay?: number; @@ -20,8 +21,11 @@ export interface QRLoaderProps { classNameModifiers?: string[]; brandLogo?: string; brandName?: string; + buttonLabel?: string; introduction?: string; - instructions?: string; + redirectIntroduction?: string; + timeToPay?: string; + instructions?: string | (() => h.JSX.Element); copyBtn?: boolean; onActionHandled?: (rtnObj: ActionHandledReturnObject) => void; } diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/AbstractCSF.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/AbstractCSF.ts index 17920a0d81..79a38b241f 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/AbstractCSF.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/AbstractCSF.ts @@ -1,5 +1,5 @@ import { CSFSetupObject, CSFConfigObject, CSFCallbacksConfig, CSFStateObject } from './types'; -import { SFFeedbackObj, SendBrandObject, SendExpiryDateObject, CVCPolicyType, DatePolicyType } from '../types'; +import { SFFeedbackObj, SendBrandObject, SendExpiryDateObject, CVCPolicyType, DatePolicyType, SFFieldType } from '../types'; import { createSecuredFields } from './extensions/createSecuredFields'; import processBrand from './partials/processBrand'; import handleBrandFromBinLookup from './extensions/handleBrandFromBinLookup'; @@ -34,7 +34,7 @@ abstract class AbstractCSF { protected processBrand: typeof processBrand; protected sendBrandToCardSF: (brandObj: SendBrandObject) => void; protected sendExpiryDatePolicyToSF: (dateObj: SendExpiryDateObject) => void; - protected setFocusOnFrame: (pFieldType: string, doLog?: boolean) => void; + protected setFocusOnFrame: (pFieldType: SFFieldType, doLog?: boolean) => void; protected setupSecuredField: (pItem: HTMLElement) => void; protected touchendListener: (e: Event) => void; protected touchstartListener: () => void; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/CSF.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/CSF.ts index be6b46cd3b..fefa6bff90 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/CSF.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/CSF.ts @@ -1,6 +1,6 @@ import AbstractCSF from './AbstractCSF'; -import { CSFReturnObject, CSFSetupObject, CSFStateObject } from './types'; -import { StylesObject, CbObjOnAdditionalSF } from '../types'; +import { CSFReturnObject, CSFSetupObject, CSFStateObject, CSFThisObject } from './types'; +import { StylesObject, CbObjOnAdditionalSF, SFFieldType } from '../types'; import { BinLookupResponse } from '../../../../Card/types'; import { handleConfig } from './extensions/handleConfig'; import { configureCallbacks } from './extensions/configureCallbacks'; @@ -73,7 +73,7 @@ class CSF extends AbstractCSF { } as CSFStateObject; // Create object of references to properties on 'this', that can be used to set up the partials - const thisObj = { csfState: this.state, csfConfig: this.config, csfProps: this.props, csfCallbacks: this.callbacks }; + const thisObj: CSFThisObject = { csfState: this.state, csfConfig: this.config, csfProps: this.props, csfCallbacks: this.callbacks }; // Setup 'this' references this.configHandler = handleConfig; @@ -97,15 +97,15 @@ class CSF extends AbstractCSF { this.postMessageToAllIframes = partial(postMessageToAllIframes, thisObj); - this.setFocusOnFrame = partial(setFocusOnFrame, thisObj); - this.handleFocus = partial(handleFocus, thisObj); - this.handleIOSTouchEvents = handleIOSTouchEvents.handleTouchend; this.touchendListener = handleIOSTouchEvents.touchendListener.bind(this); this.destroyTouchendListener = handleIOSTouchEvents.destroyTouchendListener; this.touchstartListener = handleIOSTouchEvents.touchstartListener.bind(this); this.destroyTouchstartListener = handleIOSTouchEvents.destroyTouchstartListener; + this.setFocusOnFrame = partial(setFocusOnFrame, thisObj); + this.handleFocus = partial(handleFocus, thisObj, this.handleIOSTouchEvents); + this.handleSFShiftTab = handleTab.handleSFShiftTab; this.handleShiftTab = handleTab.handleShiftTab; @@ -161,7 +161,7 @@ class CSF extends AbstractCSF { ); } }, - setFocusOnFrame: (pFieldType: string): void => { + setFocusOnFrame: (pFieldType: SFFieldType): void => { if (this.state.isConfigured) { this.setFocusOnFrame(pFieldType); // Comment in a quick way to test destroying secured fields (also see comment in destroySecuredFields) @@ -172,7 +172,7 @@ class CSF extends AbstractCSF { }, // For component based implementation - if showValidation function is called on the component use this // function as a way to notify the CSF that a field is in error - isValidated: (pFieldType: string, code: string): void => { + isValidated: (pFieldType: SFFieldType, code: string): void => { if (this.state.isConfigured) { if (hasOwnProperty(this.state.securedFields, pFieldType)) { this.state.securedFields[pFieldType].hasError = true; @@ -198,7 +198,7 @@ class CSF extends AbstractCSF { notConfiguredWarning('You cannot set validated on any secured field'); } }, - hasUnsupportedCard: (pFieldType: string, code: string): void => { + hasUnsupportedCard: (pFieldType: SFFieldType, code: string): void => { if (this.state.isConfigured) { if (hasOwnProperty(this.state.securedFields, pFieldType)) { // @@ -235,14 +235,14 @@ class CSF extends AbstractCSF { notConfiguredWarning('You cannot set pass brands to secured fields'); } }, - addSecuredField: (pFieldType: string): void => { + addSecuredField: (pFieldType: SFFieldType): void => { const securedField: HTMLElement = selectOne(this.props.rootNode, `[data-cse="${pFieldType}"]`); if (securedField) { this.state.numIframes += 1; this.setupSecuredField(securedField); } }, - removeSecuredField: (pFieldType: string): void => { + removeSecuredField: (pFieldType: SFFieldType): void => { if (this.state.securedFields[pFieldType]) { this.state.securedFields[pFieldType].destroy(); delete this.state.securedFields[pFieldType]; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createCardSecuredFields.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createCardSecuredFields.test.ts new file mode 100644 index 0000000000..434f794bef --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createCardSecuredFields.test.ts @@ -0,0 +1,72 @@ +import { createCardSecuredFields, createNonCardSecuredFields } from './createSecuredFields'; +import { DATA_ENCRYPTED_FIELD_ATTR, ENCRYPTED_CARD_NUMBER } from '../../configuration/constants'; +import { SecuredFields } from '../../types'; +import SecuredField from '../../securedField/SecuredField'; + +jest.useFakeTimers(); +jest.spyOn(global, 'setTimeout'); + +jest.mock('../../securedField/SecuredField'); + +const mockedSecuredField = SecuredField as jest.Mock; + +window._b$dl = true; // to cover some missing lines + +let MySecuredField; + +const makeDiv = encName => { + const myDiv = document.createElement('div'); + myDiv.setAttribute(DATA_ENCRYPTED_FIELD_ATTR, encName); + return myDiv; +}; + +const myCSF = { + state: { type: 'card', securedFields: {} as SecuredFields }, + config: { cardGroupTypes: ['mc'] }, + props: {}, + callbacks: { + onBrand: jest.fn(() => {}) + }, + setupSecuredField: () => Promise.resolve(), + createCardSecuredFields, + createNonCardSecuredFields, + isSingleBrandedCard: null +}; + +describe("Testing setupSecuredField's createCardSecuredFields functionality", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + MySecuredField = { + fieldType: ENCRYPTED_CARD_NUMBER + }; + const SecuredFieldMock = jest.fn(() => MySecuredField); + + mockedSecuredField.mockReset(); + mockedSecuredField.mockImplementation(() => SecuredFieldMock()); + SecuredFieldMock.mockClear(); + }); + + test("setupSecuredField's createCardSecuredFields function, as a single-branded card, should call the onBrand callback", async () => { + myCSF.state.type = 'mc'; + myCSF.isSingleBrandedCard = true; + + myCSF.setupSecuredField = () => Promise.resolve(); + + const myDiv = makeDiv(ENCRYPTED_CARD_NUMBER); + await myCSF.createCardSecuredFields([myDiv], 'required', 'required'); + + // Fast-forward until related timer has been executed + jest.runAllTimers(); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + + expect(myCSF.callbacks.onBrand).toHaveBeenCalled(); + }); + + test("setupSecuredField's createNonCardSecuredFields function should resolve", async () => { + const myDiv = makeDiv(ENCRYPTED_CARD_NUMBER); + await expect(myCSF.createNonCardSecuredFields([myDiv])).resolves.toBe(undefined); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.test.ts new file mode 100644 index 0000000000..451c641a4d --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.test.ts @@ -0,0 +1,309 @@ +import { createSecuredFields, setupSecuredField } from './createSecuredFields'; +import { DATA_ENCRYPTED_FIELD_ATTR, ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE, SF_CONFIG_TIMEOUT } from '../../configuration/constants'; +import { SecuredFields } from '../../types'; +import Language from '../../../../../../language'; +import SecuredField from '../../securedField/SecuredField'; + +jest.useFakeTimers(); +jest.spyOn(global, 'setTimeout'); + +jest.mock('../../securedField/SecuredField'); + +const mockedSecuredField = SecuredField as jest.Mock; + +window._b$dl = true; // to cover some missing lines + +let MySecuredField; + +const myCSF = { + state: { type: 'card', hasSeparateDateFields: null, securedFields: {} as SecuredFields, iframeCount: 0, originalNumIframes: 2, numIframes: 2 }, + config: {}, + props: { rootNode: null, i18n: new Language('en-US', {}), shouldDisableIOSArrowKeys: null }, + callbacks: { + onLoad: jest.fn(() => {}), + onTouchstartIOS: jest.fn(() => {}) + }, + createSecuredFields, + setupSecuredField, + createNonCardSecuredFields: jest.fn(() => {}), + encryptedAttrName: DATA_ENCRYPTED_FIELD_ATTR, + destroySecuredFields: jest.fn(() => { + console.log('### createSecuredFields.test::calling destroySecuredFields:: '); + }), + handleIframeConfigFeedback: jest.fn(obj => { + console.log('### createSecuredFields.test::calling handleIframeConfigFeedback:: with', obj); + }), + handleFocus: jest.fn(() => {}), + handleBinValue: jest.fn(() => {}), + handleSFShiftTab: jest.fn(() => {}), + handleEncryption: jest.fn(() => {}), + handleValidation: jest.fn(() => {}), + processAutoComplete: jest.fn(() => {}), + hasGenuineTouchEvents: null, + postMessageToAllIframes: jest.fn(() => {}) +}; + +const makeDiv = encName => { + const myDiv = document.createElement('div'); + myDiv.setAttribute(DATA_ENCRYPTED_FIELD_ATTR, encName); + return myDiv; +}; + +const dummyObj = { foo: 'bar' }; + +describe('Testing CSFs setupSecuredField functionality', () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + console.warn = jest.fn(() => {}); + + MySecuredField = { + fieldType: ENCRYPTED_CARD_NUMBER, + onIframeLoadedCallback: null, + onConfigCallback: null, + onFocusCallback: null, + onBinValueCallback: null, + onTouchstartCallback: null, + onShiftTabCallback: null, + onEncryptionCallback: null, + onValidationCallback: null, + onAutoCompleteCallback: null, + onIframeLoaded: cbFn => { + MySecuredField.onIframeLoadedCallback = cbFn; + return MySecuredField; + }, + onConfig: cbFn => { + MySecuredField.onConfigCallback = cbFn; + return MySecuredField; + }, + onFocus: cbFn => { + MySecuredField.onFocusCallback = cbFn; + return MySecuredField; + }, + onBinValue: cbFn => { + MySecuredField.onBinValueCallback = cbFn; + return MySecuredField; + }, + onTouchstart: cbFn => { + MySecuredField.onTouchstartCallback = cbFn; + return MySecuredField; + }, + onShiftTab: cbFn => { + MySecuredField.onShiftTabCallback = cbFn; + return MySecuredField; + }, + onEncryption: cbFn => { + MySecuredField.onEncryptionCallback = cbFn; + return MySecuredField; + }, + onValidation: cbFn => { + MySecuredField.onValidationCallback = cbFn; + return MySecuredField; + }, + onAutoComplete: cbFn => { + MySecuredField.onAutoCompleteCallback = cbFn; + return MySecuredField; + } + }; + const SecuredFieldMock = jest.fn(() => MySecuredField); + + mockedSecuredField.mockReset(); + mockedSecuredField.mockImplementation(() => SecuredFieldMock()); + SecuredFieldMock.mockClear(); + }); + + test('Calling setupSecuredField with an unsupported value for the data-cse attribute should see that a SF is not created and that a warning is given', () => { + const rootNode = document.createElement('div'); + const unsupportedEl = makeDiv('encryptedCustomField'); + rootNode.appendChild(unsupportedEl); + myCSF.props.rootNode = rootNode; + + const numIframes: number = myCSF.createSecuredFields(); + + expect(numIframes).toEqual(0); + + expect(console.warn).toBeCalledWith( + `WARNING: 'encryptedCustomField' is not a valid type for the '${DATA_ENCRYPTED_FIELD_ATTR}' attribute. A SecuredField will not be created for this element.` + ); + + expect(myCSF.createNonCardSecuredFields).toBeCalledWith([]); + }); + + test('Calling setupSecuredField to see that an "encryptedCardNumber" SF is created and stored in state', () => { + myCSF.setupSecuredField(makeDiv(ENCRYPTED_CARD_NUMBER)); + + expect(myCSF.state.securedFields.encryptedCardNumber).not.toEqual(null); + }); + + test('Calling to see that an "encryptedExpiryYear" SF is created and stored in state and that we register the fact that we have separate date fields', () => { + myCSF.setupSecuredField(makeDiv('encryptedExpiryYear')); + + expect(myCSF.state.hasSeparateDateFields).toEqual(true); + expect(myCSF.state.securedFields.encryptedExpiryYear).not.toEqual(null); + }); + + test( + 'Calling setupSecuredField to see that the expected onIframeLoadedCallback is set.' + + 'Running the onIframeLoadedCallback callback sees the iframeCount increases, but because all expected iframes have not been loaded, ' + + 'the onLoad callback is not called; and we force the config timeout to see that the promise rejects', + () => { + const prom = myCSF.setupSecuredField(makeDiv(ENCRYPTED_CARD_NUMBER)); + + expect(MySecuredField.onIframeLoadedCallback).not.toEqual(null); + MySecuredField.onIframeLoadedCallback(); + + expect(myCSF.state.iframeCount).toEqual(1); + expect(myCSF.callbacks.onLoad).not.toHaveBeenCalled(); + + // Fast-forward until config related timer has been executed + jest.runAllTimers(); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), SF_CONFIG_TIMEOUT); + + return expect(prom).rejects.toEqual({ type: ENCRYPTED_CARD_NUMBER, failReason: 'sf took too long to config' }); + } + ); + + test( + 'Calling setupSecuredField to see that the expected onIframeLoadedCallback is set.' + + 'Running the onIframeLoadedCallback callback sees the iframeCount increases, and because all expected iframes have been loaded, the onLoad callback is called', + () => { + myCSF.setupSecuredField(makeDiv(ENCRYPTED_CARD_NUMBER)); + + expect(MySecuredField.onIframeLoadedCallback).not.toEqual(null); + MySecuredField.onIframeLoadedCallback(); + + expect(myCSF.state.iframeCount).toEqual(2); + expect(myCSF.callbacks.onLoad).toHaveBeenCalledWith({ iframesLoaded: true }); + } + ); + + test( + 'Calling setupSecuredField to see that the expected onIframeLoadedCallback is set. ' + + 'Running the onIframeLoadedCallback callback again sees the iframeCount increases, and because we now exceed the expected number of iframes have been loaded, ' + + 'the destroySecuredFields function is called and we throw an error', + () => { + myCSF.setupSecuredField(makeDiv(ENCRYPTED_CARD_NUMBER)); + + expect(MySecuredField.onIframeLoadedCallback).not.toEqual(null); + + expect(() => MySecuredField.onIframeLoadedCallback()).toThrow( + 'One or more securedFields has just loaded new content. This should never happen. securedFields have been removed.\n iframe load count=3. Expected count:2' + ); + + expect(myCSF.state.iframeCount).toEqual(3); + expect(myCSF.destroySecuredFields).toHaveBeenCalled(); + } + ); + + test( + 'Calling setupSecuredField to see that the expected onConfigCallback callback is set. Running it sees that an object is passed through to the relevant callback function, ' + + 'and that the promise is resolved', + () => { + const prom = myCSF.setupSecuredField(makeDiv(ENCRYPTED_CARD_NUMBER)); + + // onConfigCallback + expect(MySecuredField.onConfigCallback).not.toEqual(null); + + MySecuredField.onConfigCallback(dummyObj); + expect(myCSF.handleIframeConfigFeedback).toHaveBeenCalledWith(dummyObj); + + return expect(prom).resolves.toBe(dummyObj); + } + ); + + test( + 'Calling setupSecuredField to see that expected onTouchstartCallback is set. Running it sees that because myCSF is not configured to allow it ' + + '- the callback function and postMessageToAllIframes are not called', + () => { + myCSF.props.shouldDisableIOSArrowKeys = true; + myCSF.hasGenuineTouchEvents = false; + + myCSF.setupSecuredField(makeDiv(ENCRYPTED_CARD_NUMBER)); + + expect(MySecuredField.onTouchstartCallback).not.toEqual(null); + + MySecuredField.onTouchstartCallback({ fieldType: ENCRYPTED_CARD_NUMBER }); + + expect(myCSF.callbacks.onTouchstartIOS).not.toHaveBeenCalled(); + + expect(myCSF.postMessageToAllIframes).not.toHaveBeenCalled(); + } + ); + + test( + 'Calling setupSecuredField to see that expected onTouchstartCallback is set. Running it sees that because myCSF is configured to allow it ' + + '- an object is passed through to the relevant callback function, and that postMessageToAllIframes is called ', + () => { + myCSF.hasGenuineTouchEvents = true; + + myCSF.setupSecuredField(makeDiv(ENCRYPTED_CARD_NUMBER)); + + expect(MySecuredField.onTouchstartCallback).not.toEqual(null); + + MySecuredField.onTouchstartCallback({ fieldType: ENCRYPTED_CARD_NUMBER }); + + expect(myCSF.callbacks.onTouchstartIOS).toHaveBeenCalledWith({ fieldType: ENCRYPTED_CARD_NUMBER }); + + expect(myCSF.postMessageToAllIframes).toHaveBeenCalledWith({ fieldType: ENCRYPTED_CARD_NUMBER, fieldClick: true }); + } + ); + + test( + 'Calling setupSecuredField to see that expected onTouchstartCallback is set. Running it sees that, because, between then, myCSF & the feedback object are configured to allow it ' + + '- an object is passed through to the relevant callback function, and that postMessageToAllIframes is called ', + () => { + myCSF.hasGenuineTouchEvents = false; + + myCSF.setupSecuredField(makeDiv(ENCRYPTED_EXPIRY_DATE)); + + expect(MySecuredField.onTouchstartCallback).not.toEqual(null); + + MySecuredField.onTouchstartCallback({ fieldType: ENCRYPTED_EXPIRY_DATE, hasGenuineTouchEvents: true }); + + expect(myCSF.callbacks.onTouchstartIOS).toHaveBeenCalledWith({ fieldType: ENCRYPTED_EXPIRY_DATE }); + + expect(myCSF.postMessageToAllIframes).toHaveBeenCalledWith({ fieldType: ENCRYPTED_EXPIRY_DATE, fieldClick: true }); + } + ); + + test('Calling setupSecuredField to see that the remaining, expected callbacks are set. Running them sees that an object is passed through to the relevant callback function', () => { + myCSF.setupSecuredField(makeDiv(ENCRYPTED_CARD_NUMBER)); + + // onFocusCallback; + expect(MySecuredField.onFocusCallback).not.toEqual(null); + + MySecuredField.onFocusCallback(dummyObj); + expect(myCSF.handleFocus).toHaveBeenCalledWith(dummyObj); + + // onBinValueCallback + expect(MySecuredField.onBinValueCallback).not.toEqual(null); + + MySecuredField.onBinValueCallback(dummyObj); + expect(myCSF.handleBinValue).toHaveBeenCalledWith(dummyObj); + + // onShiftTabCallback + expect(MySecuredField.onShiftTabCallback).not.toEqual(null); + + MySecuredField.onShiftTabCallback({ fieldType: ENCRYPTED_CARD_NUMBER }); + expect(myCSF.handleSFShiftTab).toHaveBeenCalledWith(ENCRYPTED_CARD_NUMBER); + + // onEncryptionCallback + expect(MySecuredField.onEncryptionCallback).not.toEqual(null); + + MySecuredField.onEncryptionCallback(dummyObj); + expect(myCSF.handleEncryption).toHaveBeenCalledWith(dummyObj); + + // onValidationCallback + expect(MySecuredField.onValidationCallback).not.toEqual(null); + + MySecuredField.onValidationCallback(dummyObj); + expect(myCSF.handleValidation).toHaveBeenCalledWith(dummyObj); + + // onAutoCompleteCallback + expect(MySecuredField.onAutoCompleteCallback).not.toEqual(null); + + MySecuredField.onAutoCompleteCallback(dummyObj); + expect(myCSF.processAutoComplete).toHaveBeenCalledWith(dummyObj); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.ts index fd15d2dd79..74d7617272 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.ts @@ -5,7 +5,9 @@ import { CVC_POLICY_REQUIRED, DATA_ENCRYPTED_FIELD_ATTR, DATA_INFO, - DATA_UID + DATA_UID, + SF_CONFIG_TIMEOUT, + ALL_SECURED_FIELDS } from '../../configuration/constants'; import { existy } from '../../utilities/commonUtils'; import cardType from '../utils/cardType'; @@ -21,8 +23,17 @@ import AdyenCheckoutError from '../../../../../../core/Errors/AdyenCheckoutError export function createSecuredFields(): number { this.encryptedAttrName = DATA_ENCRYPTED_FIELD_ATTR; - // Detect DOM elements that qualify as securedField holders - const securedFields: HTMLElement[] = select(this.props.rootNode, `[${this.encryptedAttrName}]`); + // Detect DOM elements that qualify as securedField holders & filter them for valid types + const securedFields: HTMLElement[] = select(this.props.rootNode, `[${this.encryptedAttrName}]`).filter(field => { + const fieldType: string = getAttribute(field, this.encryptedAttrName); + const isValidType = ALL_SECURED_FIELDS.includes(fieldType); + if (!isValidType) { + console.warn( + `WARNING: '${fieldType}' is not a valid type for the '${this.encryptedAttrName}' attribute. A SecuredField will not be created for this element.` + ); + } + return isValidType; + }); /** * cvcPolicy - 'required' | 'optional' | 'hidden' @@ -224,7 +235,7 @@ export function setupSecuredField(pItem: HTMLElement, cvcPolicy?: CVCPolicyType, // @ts-ignore - timeout 'type' *is* a number sf.loadToConfigTimeout = setTimeout(() => { reject({ type: sfInitObj.fieldType, failReason: 'sf took too long to config' }); - }, 6000); + }, SF_CONFIG_TIMEOUT); // If all iframes are loaded - call onLoad callback if (this.state.iframeCount === this.state.originalNumIframes) { diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleBrandFromBinLookup.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleBrandFromBinLookup.test.ts new file mode 100644 index 0000000000..223ae8e172 --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleBrandFromBinLookup.test.ts @@ -0,0 +1,241 @@ +import postMessageToIframe from '../utils/iframes/postMessageToIframe'; +import handleBrandFromBinLookup, { sendBrandToCardSF, sendExpiryDatePolicyToSF } from './handleBrandFromBinLookup'; + +jest.mock('../utils/iframes/postMessageToIframe'); + +const mockedPostMessageToIframe = postMessageToIframe as jest.Mock; + +const securedFieldsObj: any = { + encryptedCardNumber: { numKey: 654321 }, + encryptedExpiryDate: { expiryDatePolicy: null, numKey: 654321 }, + encryptedSecurityCode: { cvcPolicy: null, numKey: 654321 } +}; + +const myCSF = { + state: { type: 'card', securedFields: securedFieldsObj }, + config: {}, + handleBrandFromBinLookup, + sendBrandToCardSF, + sendExpiryDatePolicyToSF, + validateForm: null, + processBrand: null +}; + +const binLookupResponseObj: any = { + issuingCountryCode: 'US', + supportedBrands: [ + { + brand: 'mc', + cvcPolicy: 'required', + enableLuhnCheck: true, + expiryDatePolicy: 'required', + localeBrand: 'MasterCard', + paymentMethodVariant: 'mcdebit', + showSocialSecurityNumber: false, + supported: true + } + ] +}; + +const nonGenericCard_resetObj = { + brand: 'bcmc', + cvcPolicy: 'hidden' +}; + +// ////////// + +const expectedProcessBrandObj = { + brand: 'mc', + cvcPolicy: 'required', + expiryDatePolicy: 'required', + cvcText: 'Security code', + showSocialSecurityNumber: false, + fieldType: 'encryptedCardNumber' +}; + +const expectedProcessBrand_resetObj = { + brand: 'bcmc', + cvcPolicy: 'hidden', + fieldType: 'encryptedCardNumber' +}; + +const expectedSendBrandToCardSFObj = { + txVariant: 'card', + brand: 'mc', + enableLuhnCheck: true, + fieldType: 'encryptedCardNumber', + numKey: 654321 +}; + +const expectedSendExpiryDatePolicyToSF_expiryDateObj: any = { + txVariant: 'card', + expiryDatePolicy: 'required', + fieldType: 'encryptedExpiryDate', + numKey: 654321 +}; + +const expectedSendExpiryDatePolicyToSF_expiryMonthObj: any = { + txVariant: 'card', + expiryDatePolicy: 'required', + fieldType: 'encryptedExpiryMonth', + numKey: 654321 +}; + +const expectedSendExpiryDatePolicyToSF_expiryYearObj: any = { + txVariant: 'card', + expiryDatePolicy: 'required', + fieldType: 'encryptedExpiryYear', + numKey: 654321 +}; + +const expectedSendBrandToCardSF_resetObj = { + txVariant: 'card', + brand: 'reset', + fieldType: 'encryptedCardNumber', + numKey: 654321 +}; + +describe('Testing CSFs handleBrandFromBinLookup functionality', () => { + const postMessageToIframeMock = jest.fn(obj => console.log('### handleEncryption.test::Mock FN call to postMessageToIframe:: ', obj)); + + beforeEach(() => { + console.log = jest.fn(() => {}); + + mockedPostMessageToIframe.mockReset(); + mockedPostMessageToIframe.mockImplementation(obj => postMessageToIframeMock(obj)); + postMessageToIframeMock.mockClear(); + + myCSF.processBrand = jest.fn(obj => { + console.log('### handleBrandFromBinLookup.test::myCSF.processBrand:: obj', obj); + }); + + myCSF.validateForm = jest.fn(() => { + console.log('### handleBrandFromBinLookup.test::myCSF.validateForm:: '); + }); + }); + + test( + 'Calling handleBrandFromBinLookup with a binLookupResponse object inside a regular card component should make the expected calls to ' + + 'processBrand & validateForm, send postMsgs to set brand & expiryDatePolicy on the card and expiryDate SFs respectively, and set cvcPolicy & expiryDatePolicy in state', + () => { + myCSF.handleBrandFromBinLookup(binLookupResponseObj, null); + + expect(myCSF.processBrand).toHaveBeenCalledWith(expectedProcessBrandObj); + + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedSendBrandToCardSFObj); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedSendExpiryDatePolicyToSF_expiryDateObj); + + expect(myCSF.state.securedFields.encryptedSecurityCode.cvcPolicy).toEqual('required'); + expect(myCSF.state.securedFields.encryptedExpiryDate.expiryDatePolicy).toEqual('required'); + + expect(myCSF.validateForm).toHaveBeenCalled(); + } + ); + + test( + 'Calling handleBrandFromBinLookup with a binLookupResponse object inside a custom card component should make the expected calls to ' + + 'processBrand & validateForm, send postMsgs to set brand on the card SF & expiryDatePolicy on the expiryMonth & expiryYear SFs, and set cvcPolicy & expiryDatePolicy in state', + () => { + // reconfigure state.securedFields to reflect a custom card setup + delete myCSF.state.securedFields.encryptedExpiryDate; + + myCSF.state.securedFields.encryptedExpiryMonth = { expiryDatePolicy: null, numKey: 654321 }; + myCSF.state.securedFields.encryptedExpiryYear = { expiryDatePolicy: null, numKey: 654321 }; + + myCSF.handleBrandFromBinLookup(binLookupResponseObj, null); + + expect(myCSF.processBrand).toHaveBeenCalled(); + expect(postMessageToIframeMock).toHaveBeenCalled(); + + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedSendExpiryDatePolicyToSF_expiryMonthObj); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedSendExpiryDatePolicyToSF_expiryYearObj); + + expect(myCSF.state.securedFields.encryptedSecurityCode.cvcPolicy).toEqual('required'); + expect(myCSF.state.securedFields.encryptedExpiryMonth.expiryDatePolicy).toEqual('required'); + expect(myCSF.state.securedFields.encryptedExpiryYear.expiryDatePolicy).toEqual('required'); + + expect(myCSF.validateForm).toHaveBeenCalled(); + } + ); + + test( + 'Calling handleBrandFromBinLookup without a binLookupResponse object or a reset object, on a custom card, ' + + 'should see that postMsgs are set to reset brand on the card SF and expiryDatePolicy on the expiryMonth & expiryYear SFs, ' + + 'and processBrand & validateForm are not called', + () => { + // @ts-ignore it's a mock scenario! + myCSF.handleBrandFromBinLookup({}, null); + + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedSendBrandToCardSF_resetObj); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedSendExpiryDatePolicyToSF_expiryMonthObj); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedSendExpiryDatePolicyToSF_expiryYearObj); + + expect(myCSF.processBrand).not.toHaveBeenCalled(); + expect(myCSF.validateForm).not.toHaveBeenCalled(); + } + ); + + test( + 'Calling handleBrandFromBinLookup without a binLookupResponse object or a reset object, on a regular card, ' + + 'should see that postMsgs are set to reset brand and expiryDatePolicy on the SFs, ' + + 'expiryDatePolicy is reset in local state, and processBrand & validateForm are not called', + () => { + // reconfigure state.securedFields to revert it back to a regular card setup + delete myCSF.state.securedFields.encryptedExpiryMonth; + delete myCSF.state.securedFields.encryptedExpiryYear; + + myCSF.state.securedFields.encryptedExpiryDate = { expiryDatePolicy: null, numKey: 654321 }; + + // @ts-ignore it's a mock scenario! + myCSF.handleBrandFromBinLookup({}, null); + + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedSendBrandToCardSF_resetObj); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedSendExpiryDatePolicyToSF_expiryDateObj); + + expect(myCSF.state.securedFields.encryptedExpiryDate.expiryDatePolicy).toEqual('required'); + + expect(myCSF.processBrand).not.toHaveBeenCalled(); + expect(myCSF.validateForm).not.toHaveBeenCalled(); + } + ); + + test( + 'Calling handleBrandFromBinLookup without a binLookupResponse object or a reset object, when state.type is not the generic value "card", ' + + 'should see that postMsgs are not sent, expiryDatePolicy is not reset in local state, and processBrand & validateForm are not called', + () => { + myCSF.state.securedFields.encryptedExpiryDate.expiryDatePolicy = null; + myCSF.state.type = 'mc'; + + myCSF.handleBrandFromBinLookup(null, null); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(myCSF.state.securedFields.encryptedExpiryDate.expiryDatePolicy).toEqual(null); // not been set to 'required' + + expect(myCSF.processBrand).not.toHaveBeenCalled(); + expect(myCSF.validateForm).not.toHaveBeenCalled(); + } + ); + + test( + 'Calling handleBrandFromBinLookup without a binLookupResponse object but with a reset object, when state.type is not the generic value "card", ' + + 'should see processBrand called with the expect "reset" object, postMsgs are not sent, expiryDatePolicy is not reset in local state, ' + + 'and validateForm is not called', + () => { + myCSF.state.type = 'bcmc'; + + // @ts-ignore it's a mock scenario! + myCSF.handleBrandFromBinLookup(null, nonGenericCard_resetObj); + + expect(myCSF.processBrand).toHaveBeenCalledWith(expectedProcessBrand_resetObj); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(myCSF.state.securedFields.encryptedExpiryDate.expiryDatePolicy).toEqual(null); + + expect(myCSF.validateForm).not.toHaveBeenCalled(); + } + ); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleBrandFromBinLookup.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleBrandFromBinLookup.ts index 746129c9e2..df763ecbe2 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleBrandFromBinLookup.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleBrandFromBinLookup.ts @@ -52,8 +52,10 @@ export default function handleBrandFromBinLookup(binLookupResponse: BinLookupRes */ if (!binLookupResponse || !Object.keys(binLookupResponse).length) { if (isGenericCard) { - // This will be sent to CardNumber SF which will trigger the brand to be re-evaluated and broadcast (which will reset cvcPolicy) + // This will be sent to CardNumber SF which will trigger the brand to be re-evaluated and broadcast + // (which will reset cvcPolicy & expiryDatePolicy in state, here in Checkout) this.sendBrandToCardSF({ brand: 'reset' }); + // Also pass the reset expiryDatePolicy to the date related SFs so they can reset visibility & aria-required attrs this.sendExpiryDatePolicyToSF({ expiryDatePolicy: DATE_POLICY_REQUIRED }); } else { /** @@ -67,7 +69,7 @@ export default function handleBrandFromBinLookup(binLookupResponse: BinLookupRes } } - // Reset expiryDatePolicy - which never comes from SF + // Reset expiryDatePolicy - which never comes from SF // TODO find out under which circumstances this clause is still required if (this.state.type === 'card' && hasOwnProperty(this.state.securedFields, ENCRYPTED_EXPIRY_DATE)) { this.state.securedFields[ENCRYPTED_EXPIRY_DATE].expiryDatePolicy = DATE_POLICY_REQUIRED; } diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleEncryption.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleEncryption.test.ts new file mode 100644 index 0000000000..3375176c89 --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleEncryption.test.ts @@ -0,0 +1,267 @@ +import postMessageToIframe from '../utils/iframes/postMessageToIframe'; +import { handleEncryption } from './handleEncryption'; + +jest.mock('../utils/iframes/postMessageToIframe'); + +const mockedPostMessageToIframe = postMessageToIframe as jest.Mock; + +let fieldToFocus = null; + +const securedFieldsObj = { + encryptedCardNumber: { hasError: true, isEncrypted: false }, + encryptedExpiryDate: { hasError: false, isEncrypted: false }, + encryptedExpiryMonth: { hasError: false, isEncrypted: false }, + encryptedExpiryYear: { numKey: 654321 }, + encryptedSecurityCode: { hasError: false, cvcPolicy: 'required', isEncrypted: false } +}; + +const myCSF = { + state: { type: 'card', securedFields: securedFieldsObj }, + props: { rootNode: 'div' }, + config: { allowedDOMAccess: true, autoFocus: false }, + callbacks: { + onFieldValid: jest.fn(obj => { + console.log('### handleEncryption.test::callbacks.onFieldValid:: obj', obj); + }), + onError: null + }, + handleEncryption, + validateForm: jest.fn(() => { + console.log('### handleEncryption.test::myCSF.validateForm:: '); + }), + setFocusOnFrame: null +}; + +const feedbackObj_encryptedCard: any = { + type: 'encryptedCardNumber', + action: 'encryption', + encryptedCardNumber: [ + { + type: 'encryptedCardNumber', + encryptedFieldName: 'encryptedCardNumber', + blob: 'eyJhbGc' + } + ], + endDigits: '1111', + issuerBin: '41111111', + fieldType: 'encryptedCardNumber', + numKey: 2577403429 +}; + +const feedbackObj_encryptedCVC: any = { + type: 'encryptedSecurityCode', + action: 'encryption', + encryptedSecurityCode: [ + { + type: 'encryptedSecurityCode', + encryptedFieldName: 'encryptedSecurityCode', + blob: 'eyJhbGc' + } + ], + fieldType: 'encryptedSecurityCode', + numKey: 3824721353 +}; + +const feedbackObj_encryptedDate: any = { + type: 'year', + action: 'encryption', + encryptedExpiryDate: [ + { + type: 'month', + encryptedFieldName: 'encryptedExpiryMonth', + blob: 'eyJhbGc' + }, + { + type: 'year', + encryptedFieldName: 'encryptedExpiryYear', + blob: 'eyJhbGc_' + } + ], + fieldType: 'encryptedExpiryDate', + numKey: 2083655694 +}; + +const feedbackObj_encryptedMonth: any = { + type: 'encryptedExpiryMonth', + action: 'encryption', + encryptedExpiryMonth: [ + { + type: 'encryptedExpiryMonth', + encryptedFieldName: 'encryptedExpiryMonth', + blob: 'eyJhbGc' + } + ], + code: '440_472', + fieldType: 'encryptedExpiryMonth', + numKey: 1069876890 +}; + +// //////// + +const expected_callbackObj_errorCleared = { + rootNode: 'div', + fieldType: 'encryptedCardNumber', + error: '', + type: 'card' +}; + +const expected_callbackObj_onFieldValid_CVC = { + fieldType: 'encryptedSecurityCode', + encryptedFieldName: 'encryptedSecurityCode', + uid: 'card-encrypted-encryptedSecurityCode', + valid: true, + type: 'card', + rootNode: 'div', + blob: 'eyJhbGc' +}; + +const expected_callbackObj_onFieldValid_Month = { + fieldType: 'encryptedExpiryDate', + encryptedFieldName: 'encryptedExpiryMonth', + uid: 'card-encrypted-encryptedExpiryMonth', + valid: true, + type: 'card', + rootNode: 'div', + blob: 'eyJhbGc' +}; + +const expected_callbackObj_onFieldValid_SeparateMonth = { + blob: 'eyJhbGc', + encryptedFieldName: 'encryptedExpiryMonth', + fieldType: 'encryptedExpiryMonth', + rootNode: 'div', + type: 'card', + uid: 'card-encrypted-encryptedExpiryMonth', + valid: true +}; + +const expected_callbackObj_onFieldValid_Year = { + fieldType: 'encryptedExpiryDate', + encryptedFieldName: 'encryptedExpiryYear', + uid: 'card-encrypted-encryptedExpiryYear', + valid: true, + type: 'card', + rootNode: 'div', + blob: 'eyJhbGc_' +}; + +const expected_callbackObj_onFieldValid_PAN = { + fieldType: 'encryptedCardNumber', + encryptedFieldName: 'encryptedCardNumber', + uid: 'card-encrypted-encryptedCardNumber', + valid: true, + type: 'card', + rootNode: 'div', + blob: 'eyJhbGc', + endDigits: '1111', + issuerBin: 41111111 +}; + +describe('Testing CSFs handleEncryption functionality', () => { + const postMessageToIframeMock = jest.fn(obj => console.log('### handleEncryption.test::Mock FN call to postMessageToIframe:: ', obj)); + + beforeEach(() => { + console.log = jest.fn(() => {}); + + mockedPostMessageToIframe.mockReset(); + mockedPostMessageToIframe.mockImplementation(obj => postMessageToIframeMock(obj)); + postMessageToIframeMock.mockClear(); + + myCSF.callbacks.onError = jest.fn(obj => { + console.log('### handleEncryption.test::callbacks.onError:: obj', obj); + // callbackObj_error = obj; + }); + + myCSF.setFocusOnFrame = jest.fn(fieldType => { + fieldToFocus = fieldType; + }); + }); + + test( + 'handleEncryption should handle an object detailing an encrypted cvc field to set the isEncrypted prop on state.securedFields.encryptedSecurityCode, ' + + 'call the onFieldValid callback with the expected object; and call validateForm', + () => { + myCSF.handleEncryption(feedbackObj_encryptedCVC); + + expect(myCSF.state.securedFields.encryptedSecurityCode.isEncrypted).toEqual(true); + + expect(myCSF.callbacks.onError).not.toHaveBeenCalled(); + + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalled(); + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalledWith(expected_callbackObj_onFieldValid_CVC); + + expect(myCSF.validateForm).toHaveBeenCalledTimes(1); + } + ); + + test( + 'handleEncryption should handle an object detailing an encrypted date field to set the isEncrypted prop on state.securedFields.encryptedExpiryDate, ' + + 'call the onFieldValid callback twice, with the expected objects, call validateForm, and call setFocusOnFrame to focus the cvc field', + () => { + myCSF.config.allowedDOMAccess = false; + myCSF.config.autoFocus = true; + + myCSF.handleEncryption(feedbackObj_encryptedDate); + + expect(myCSF.state.securedFields.encryptedExpiryDate.isEncrypted).toEqual(true); + + expect(myCSF.callbacks.onError).not.toHaveBeenCalled(); + + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalled(); + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalledWith(expected_callbackObj_onFieldValid_Month); + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalledWith(expected_callbackObj_onFieldValid_Year); + + expect(myCSF.validateForm).toHaveBeenCalledTimes(2); + + expect(myCSF.setFocusOnFrame).toHaveBeenCalled(); + expect(fieldToFocus).toEqual('encryptedSecurityCode'); + } + ); + + test( + 'handleEncryption should handle an object detailing an encrypted month field to set the isEncrypted prop on state.securedFields.encryptedExpiryMonth, ' + + 'call the onFieldValid callback with the expected object, call validateForm, and call setFocusOnFrame to focus the year field. It should also call postMessageToIframe', + () => { + myCSF.handleEncryption(feedbackObj_encryptedMonth); + + expect(myCSF.state.securedFields.encryptedExpiryMonth.isEncrypted).toEqual(true); + + expect(myCSF.callbacks.onError).not.toHaveBeenCalled(); + + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalled(); + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalledWith(expected_callbackObj_onFieldValid_SeparateMonth); + + expect(myCSF.validateForm).toHaveBeenCalledTimes(3); + + expect(myCSF.setFocusOnFrame).toHaveBeenCalled(); + expect(fieldToFocus).toEqual('encryptedExpiryYear'); + + expect(postMessageToIframeMock).toHaveBeenCalledWith({ + txVariant: 'card', + code: '440_472', + blob: 'eyJhbGc', + fieldType: 'encryptedExpiryYear', + numKey: 654321 + }); + } + ); + + test( + 'handleEncryption should handle an object detailing an encrypted PAN field (that had been in error), to set the isEncrypted prop on state.securedFields.encryptedCardNumber, ' + + 'call the processErrors & onFieldValid callbacks with the expected objects, call validateForm, and not call setFocusOnFrame', + () => { + myCSF.handleEncryption(feedbackObj_encryptedCard); + + expect(myCSF.state.securedFields.encryptedCardNumber.isEncrypted).toEqual(true); + + expect(myCSF.callbacks.onError).toHaveBeenCalledWith(expected_callbackObj_errorCleared); + + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalled(); + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalledWith(expected_callbackObj_onFieldValid_PAN); + + expect(myCSF.validateForm).toHaveBeenCalledTimes(4); + + expect(myCSF.setFocusOnFrame).not.toHaveBeenCalled(); + } + ); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleEncryption.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleEncryption.ts index 9fe195f85f..39a5c85086 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleEncryption.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleEncryption.ts @@ -13,6 +13,7 @@ export function handleEncryption(pFeedbackObj: SFFeedbackObj): void { // SET FOCUS ON OTHER INPUT - If user has just typed a correct expiryDate - set focus on the cvc field OR typed a correct expiryMonth - focus on year field if (this.config.autoFocus) { + // pFeedbackObj.type === 'year' when the encryption is happening on an expiryDate field c.f. a separate year field (when it equals ENCRYPTED_EXPIRY_YEAR) if (pFeedbackObj.type === 'year' || fieldType === ENCRYPTED_EXPIRY_YEAR) { this.setFocusOnFrame(ENCRYPTED_SECURITY_CODE); } diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleIOSTouchEvents.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleIOSTouchEvents.test.ts new file mode 100644 index 0000000000..b061abd1ff --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleIOSTouchEvents.test.ts @@ -0,0 +1,211 @@ +import handleIOSTouchEvents from './handleIOSTouchEvents'; + +import ua from '../utils/userAgent'; + +const myCSF = { + state: { type: 'card', registerFieldForIos: null }, + props: { rootNode: { appendChild: jest.fn(() => {}), removeChild: jest.fn(() => {}) } }, + callbacks: { + onTouchstartIOS: null + }, + config: { keypadFix: false }, + touchstartListener: handleIOSTouchEvents.touchstartListener, + touchendListener: handleIOSTouchEvents.touchendListener, + handleTouchend: handleIOSTouchEvents.handleTouchend, + hasGenuineTouchEvents: null, + postMessageToAllIframes: null, + destroyTouchendListener: null, + destroyTouchstartListener: handleIOSTouchEvents.destroyTouchstartListener +}; + +const makeElementWithAttribute = (elementType, attrName, attrValue) => { + const myElement = document.createElement(elementType); + myElement.setAttribute(attrName, attrValue); + return myElement; +}; + +describe("Testing CSF's handleIOSTouchEvents' touchstartListener functionality", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + myCSF.hasGenuineTouchEvents = false; + + myCSF.postMessageToAllIframes = jest.fn(() => {}); + + myCSF.callbacks.onTouchstartIOS = jest.fn(() => {}); + }); + + test( + 'Calling touchstartListener and passing it an input element will see that hasGenuineTouchEvents is set to true, and ' + + 'that postMessageToAllIframes & callbacks.onTouchstartIOS are both called with the expected objects', + () => { + const myElement = makeElementWithAttribute('input', 'name', 'myInput'); + + // @ts-ignore - it's just a test! + myCSF.touchstartListener({ target: myElement }); + + expect(myCSF.hasGenuineTouchEvents).toEqual(true); + + expect(myCSF.postMessageToAllIframes).toHaveBeenCalledWith({ fieldType: 'webInternalElement', checkoutTouchEvent: true }); + expect(myCSF.callbacks.onTouchstartIOS).toHaveBeenCalledWith({ fieldType: 'webInternalElement', name: 'myInput' }); + } + ); + + test( + 'Calling touchstartListener and passing it a span element will see that hasGenuineTouchEvents is set to true, and ' + + 'that postMessageToAllIframes & callbacks.onTouchstartIOS are both called with the expected objects', + () => { + const myElement = makeElementWithAttribute('span', 'data-id', 'mySpan'); + + // @ts-ignore - it's just a test! + myCSF.touchstartListener({ target: myElement }); + + expect(myCSF.hasGenuineTouchEvents).toEqual(true); + + expect(myCSF.postMessageToAllIframes).toHaveBeenCalledWith({ fieldType: 'webInternalElement', checkoutTouchEvent: true }); + expect(myCSF.callbacks.onTouchstartIOS).toHaveBeenCalledWith({ fieldType: 'webInternalElement', name: 'mySpan' }); + } + ); + + test( + 'Calling touchstartListener and passing it an element that is not an input or a span will see that hasGenuineTouchEvents is set to true, but ' + + 'postMessageToAllIframes & callbacks.onTouchstartIOS will not be called', + () => { + const myElement = makeElementWithAttribute('div', 'data-id', 'myDiv'); + + // @ts-ignore - it's just a test! + myCSF.touchstartListener({ target: myElement }); + + expect(myCSF.hasGenuineTouchEvents).toEqual(true); + + expect(myCSF.postMessageToAllIframes).not.toHaveBeenCalled(); + expect(myCSF.callbacks.onTouchstartIOS).not.toHaveBeenCalled(); + } + ); +}); + +describe("Testing CSF's handleIOSTouchEvents' handleTouchend functionality", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + myCSF.state.registerFieldForIos = false; + }); + + test('Calling handleTouchend will set registerFieldForIos = true', () => { + myCSF.handleTouchend(); + + expect(myCSF.state.registerFieldForIos).toEqual(true); + }); +}); + +describe("Testing CSF's handleIOSTouchEvents' touchendListener functionality", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + myCSF.state.registerFieldForIos = null; + + myCSF.postMessageToAllIframes = jest.fn(() => {}); + + myCSF.destroyTouchendListener = jest.fn(() => {}); + }); + + test( + 'Calling handleTouchend and passing it an input element will see that destroyTouchendListener & postMessageToAllIframes are called, and ' + + 'registerFieldForIos will be set to false', + () => { + const myElement = makeElementWithAttribute('input', 'name', 'myInput'); + + // @ts-ignore - it's just a test! + myCSF.touchendListener({ target: myElement }); + + expect(myCSF.destroyTouchendListener).toHaveBeenCalled(); + + expect(myCSF.state.registerFieldForIos).toEqual(false); + + expect(myCSF.postMessageToAllIframes).toHaveBeenCalledWith({ fieldType: 'webInternalElement', fieldClick: true }); + } + ); + + test( + 'Calling handleTouchend and passing it an element that is not an input, when config.keypadFix = false, will see that destroyTouchendListener & postMessageToAllIframes are called, ' + + 'registerFieldForIos is set to false, and rootNode.appendChild & .removeChild are not called', + () => { + const myElement = makeElementWithAttribute('div', 'data-id', 'myInput'); + + // @ts-ignore - it's just a test! + myCSF.touchendListener({ target: myElement }); + + expect(myCSF.props.rootNode.appendChild).not.toHaveBeenCalled(); + expect(myCSF.props.rootNode.removeChild).not.toHaveBeenCalled(); + + expect(myCSF.destroyTouchendListener).toHaveBeenCalled(); + + expect(myCSF.state.registerFieldForIos).toEqual(false); + + expect(myCSF.postMessageToAllIframes).toHaveBeenCalledWith({ fieldType: 'webInternalElement', fieldClick: true }); + } + ); + + test( + 'Calling handleTouchend and passing it an element that is not an input, when config.keypadFix = true, will see that destroyTouchendListener & postMessageToAllIframes are called, ' + + 'registerFieldForIos is set to false, and rootNode.appendChild & .removeChild are called', + () => { + myCSF.config.keypadFix = true; + + const myElement = makeElementWithAttribute('div', 'data-id', 'myInput'); + + // @ts-ignore - it's just a test! + myCSF.touchendListener({ target: myElement }); + + expect(myCSF.props.rootNode.appendChild).toHaveBeenCalled(); + expect(myCSF.props.rootNode.removeChild).toHaveBeenCalled(); + + expect(myCSF.destroyTouchendListener).toHaveBeenCalled(); + + expect(myCSF.state.registerFieldForIos).toEqual(false); + + expect(myCSF.postMessageToAllIframes).toHaveBeenCalledWith({ fieldType: 'webInternalElement', fieldClick: true }); + } + ); +}); + +describe("Testing CSF's handleIOSTouchEvents' destroyTouchendListener functionality", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + ua.__IS_IOS = false; + + myCSF.destroyTouchendListener = handleIOSTouchEvents.destroyTouchendListener; + }); + + test('Calling destroyTouchendListener will return false since this is not iOS', () => { + const res = myCSF.destroyTouchendListener(); + + expect(res).toEqual(false); + }); + + test('Calling destroyTouchendListener will return true since this is iOS', () => { + ua.__IS_IOS = true; + const res = myCSF.destroyTouchendListener(); + + expect(res).toEqual(true); + }); +}); + +describe("Testing CSF's handleIOSTouchEvents' destroyTouchstartListener functionality", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + ua.__IS_IOS = false; + }); + + test('Calling destroyTouchstartListener will return false since this is not iOS', () => { + const res = myCSF.destroyTouchstartListener(); + + expect(res).toEqual(false); + }); + + test('Calling destroyTouchstartListener will return true since this is iOS', () => { + ua.__IS_IOS = true; + const res = myCSF.destroyTouchstartListener(); + + expect(res).toEqual(true); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleIOSTouchEvents.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleIOSTouchEvents.ts index 82d4889b83..9976eee914 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleIOSTouchEvents.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleIOSTouchEvents.ts @@ -128,18 +128,20 @@ function handleTouchend(): void { this.state.registerFieldForIos = true; } -function destroyTouchendListener(): void { - if (!ua.__IS_IOS) return; // For when fn is called as result of destroy being called on main csf instance +function destroyTouchendListener(): boolean { + if (!ua.__IS_IOS) return false; // For when fn is called as result of destroy being called on main csf instance const bodyEl: HTMLBodyElement = selectOne(document, 'body'); bodyEl.style.cursor = 'auto'; off(bodyEl, 'touchend', this.touchendListener); + return true; } -function destroyTouchstartListener(): void { - if (!ua.__IS_IOS) return; // For when fn is called as result of destroy being called on main csf instance +function destroyTouchstartListener(): boolean { + if (!ua.__IS_IOS) return false; // For when fn is called as result of destroy being called on main csf instance off(document, 'touchstart', this.touchstartListener); + return true; } export default { diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleTab.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleTab.test.ts new file mode 100644 index 0000000000..0552880fe2 --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleTab.test.ts @@ -0,0 +1,215 @@ +import { SecuredFields } from '../../types'; +import handleTab from './handleTab'; +import { + ENCRYPTED_BANK_ACCNT_NUMBER_FIELD, + ENCRYPTED_BANK_LOCATION_FIELD, + ENCRYPTED_CARD_NUMBER, + ENCRYPTED_EXPIRY_DATE, + ENCRYPTED_EXPIRY_MONTH, + ENCRYPTED_EXPIRY_YEAR, + ENCRYPTED_PWD_FIELD, + ENCRYPTED_SECURITY_CODE +} from '../../configuration/constants'; +import { getPreviousTabbableNonSFElement, focusExternalField } from '../utils/tabbing/utils'; +import ua from '../utils/userAgent'; + +ua.__IS_FIREFOX = true; + +jest.mock('../utils/tabbing/utils'); +// +const mockedGetPreviousTabbableNonSFElement = getPreviousTabbableNonSFElement as jest.Mock; +const mockedFocusExternalField = focusExternalField as jest.Mock; + +const myCSF = { + state: { type: 'card', securedFields: {} as SecuredFields, hasSeparateDateFields: false, numIframes: 3, isKCP: false }, + props: { rootNode: 'div' }, + handleShiftTab: handleTab.handleShiftTab, + handleSFShiftTab: handleTab.handleSFShiftTab, + setFocusOnFrame: jest.fn(obj => { + console.log('### handleTab.test:::: setFocusOnFrame called with', obj); + }) +}; + +const instantiateMocks = () => { + const getPreviousTabbableNonSFElementMock = jest.fn((who, rootNode) => { + console.log('### handleTab.test::getPreviousTabbableNonSFElementMock:: called with', who, rootNode); + return 'some-other-div'; + }); + + const focusExternalFieldMock = jest.fn(what => { + console.log('### handleTab.test::focusExternalFieldMock:: called with', what); + }); + + mockedGetPreviousTabbableNonSFElement.mockReset(); + mockedGetPreviousTabbableNonSFElement.mockImplementation((who, rootNode) => getPreviousTabbableNonSFElementMock(who, rootNode)); + getPreviousTabbableNonSFElementMock.mockClear(); + + mockedFocusExternalField.mockReset(); + mockedFocusExternalField.mockImplementation(what => focusExternalFieldMock(what)); + focusExternalFieldMock.mockClear(); +}; + +describe("Testing CSF's handleTab functionality in a regular card scenario", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + // reset + myCSF.state.numIframes = 3; + myCSF.state.isKCP = false; + myCSF.state.hasSeparateDateFields = false; + + instantiateMocks(); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedExpiryDate", should see setFocusOnFrame called with "encryptedCardNumber"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_EXPIRY_DATE); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_CARD_NUMBER, false); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedExpiryMonth", should see setFocusOnFrame called with "encryptedCardNumber"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_EXPIRY_MONTH); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_CARD_NUMBER, false); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedExpiryYear", should see setFocusOnFrame called with "encryptedExpiryMonth"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_EXPIRY_YEAR); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_EXPIRY_MONTH, false); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedSecurityCode", when hasSeparateDateFields = false, should see setFocusOnFrame called with "encryptedExpiryDate"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_SECURITY_CODE); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_EXPIRY_DATE, false); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedSecurityCode", when hasSeparateDateFields = true, should see setFocusOnFrame called with "encryptedExpiryYear"', async () => { + myCSF.state.hasSeparateDateFields = true; + + myCSF.handleSFShiftTab(ENCRYPTED_SECURITY_CODE); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_EXPIRY_YEAR, false); + }); + + test( + 'Calling handleSFShiftTab with a fieldType = "encryptedSecurityCode", when numIframes = 1 (i.e. a storedCard scenario), ' + + 'should see getPreviousTabbableNonSFElement called with "encryptedSecurityCode", and ' + + 'focusExternalField called with the object returned from getPreviousTabbableNonSFElement', + async () => { + myCSF.state.numIframes = 1; + + myCSF.handleSFShiftTab(ENCRYPTED_SECURITY_CODE); + expect(getPreviousTabbableNonSFElement).toBeCalledWith(ENCRYPTED_SECURITY_CODE, 'div'); + expect(focusExternalField).toBeCalledWith('some-other-div'); + } + ); + + test( + 'Calling handleSFShiftTab with a fieldType = "encryptedCardNumber", should see getPreviousTabbableNonSFElement called with "encryptedCardNumber", and ' + + 'focusExternalField called with the object returned from getPreviousTabbableNonSFElement', + async () => { + myCSF.handleSFShiftTab(ENCRYPTED_CARD_NUMBER); + expect(getPreviousTabbableNonSFElement).toBeCalledWith(ENCRYPTED_CARD_NUMBER, 'div'); + expect(focusExternalField).toBeCalledWith('some-other-div'); + } + ); +}); + +describe("Testing CSF's handleTab functionality in a KCP card scenario", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + // reset + myCSF.state.numIframes = 3; + myCSF.state.isKCP = true; + myCSF.state.hasSeparateDateFields = false; + + instantiateMocks(); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedExpiryDate", should see setFocusOnFrame called with "encryptedCardNumber"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_EXPIRY_DATE); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_CARD_NUMBER, false); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedExpiryMonth", should see setFocusOnFrame called with "encryptedCardNumber"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_EXPIRY_MONTH); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_CARD_NUMBER, false); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedExpiryYear", should see setFocusOnFrame called with "encryptedExpiryMonth"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_EXPIRY_YEAR); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_EXPIRY_MONTH, false); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedSecurityCode", when hasSeparateDateFields = false, should see setFocusOnFrame called with "encryptedExpiryDate"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_SECURITY_CODE); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_EXPIRY_DATE, false); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedSecurityCode", when hasSeparateDateFields = true, should see setFocusOnFrame called with "encryptedExpiryYear"', async () => { + myCSF.state.hasSeparateDateFields = true; + + myCSF.handleSFShiftTab(ENCRYPTED_SECURITY_CODE); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_EXPIRY_YEAR, false); + }); + + test( + 'Calling handleSFShiftTab with a fieldType = "encryptedCardNumber", should see getPreviousTabbableNonSFElement called with "encryptedCardNumber", and ' + + 'focusExternalField called with the object returned from getPreviousTabbableNonSFElement', + async () => { + myCSF.handleSFShiftTab(ENCRYPTED_CARD_NUMBER); + expect(getPreviousTabbableNonSFElement).toBeCalledWith(ENCRYPTED_CARD_NUMBER, 'div'); + expect(focusExternalField).toBeCalledWith('some-other-div'); + } + ); + + test( + 'Calling handleSFShiftTab with a fieldType = "encryptedPassword", should see getPreviousTabbableNonSFElement called with "encryptedCardNumber", and ' + + 'focusExternalField called with the object returned from getPreviousTabbableNonSFElement', + async () => { + myCSF.handleSFShiftTab(ENCRYPTED_PWD_FIELD); + expect(getPreviousTabbableNonSFElement).toBeCalledWith(ENCRYPTED_PWD_FIELD, 'div'); + expect(focusExternalField).toBeCalledWith('some-other-div'); + } + ); +}); + +describe("Testing CSF's handleTab functionality in a ACH scenario", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + // reset + myCSF.state.numIframes = 2; + myCSF.state.type = 'ach'; + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedBankLocationId", should see setFocusOnFrame called with "encryptedBankAccountNumber"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_BANK_LOCATION_FIELD); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_BANK_ACCNT_NUMBER_FIELD, false); + }); +}); + +describe("Testing CSF's handleTab functionality in a Giftcard scenario", () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + // reset + myCSF.state.numIframes = 2; + myCSF.state.type = 'giftcard'; + + instantiateMocks(); + }); + + test('Calling handleSFShiftTab with a fieldType = "encryptedSecurityCode", should see setFocusOnFrame called with "encryptedCardNumber"', async () => { + myCSF.handleSFShiftTab(ENCRYPTED_SECURITY_CODE); + expect(myCSF.setFocusOnFrame).toBeCalledWith(ENCRYPTED_CARD_NUMBER, false); + }); + + test( + 'Calling handleSFShiftTab with a fieldType = "encryptedCardNumber", should see getPreviousTabbableNonSFElement called with "encryptedCardNumber", and ' + + 'focusExternalField called with the object returned from getPreviousTabbableNonSFElement', + async () => { + myCSF.handleSFShiftTab(ENCRYPTED_CARD_NUMBER); + expect(getPreviousTabbableNonSFElement).toBeCalledWith(ENCRYPTED_CARD_NUMBER, 'div'); + expect(focusExternalField).toBeCalledWith('some-other-div'); + } + ); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleTab.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleTab.ts index 653824ee42..315eaf3404 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleTab.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleTab.ts @@ -4,21 +4,12 @@ import { shiftTabCreditCard } from '../utils/tabbing/tabScenarioCreditCard'; import { shiftTabACH } from '../utils/tabbing/tabScenarioACH'; import { shiftTabGiftCard } from '../utils/tabbing/tabScenarioGiftCard'; import { shiftTabKCP } from '../utils/tabbing/tabScenarioKCP'; -import { ShiftTabObject } from '../../types'; +import { SFFieldType, ShiftTabObject } from '../../types'; +import { focusExternalField } from '../utils/tabbing/utils'; const logTab = false; -const focusExternalField = (pAdditionalField: HTMLElement): void => { - if (pAdditionalField) { - pAdditionalField.focus(); - - // Quirky! - Needed to work in the Components scenario - pAdditionalField.blur(); - pAdditionalField.focus(); - } -}; - -function handleShiftTab(fieldType: string): void { +function handleShiftTab(fieldType: SFFieldType): void { if (logTab) logger.log('### handleTab::handleShiftTab:: fieldType', fieldType); let shiftTabObj: ShiftTabObject; @@ -59,7 +50,7 @@ function handleShiftTab(fieldType: string): void { const eligibleForTabFix = (): boolean => ua.__IS_FIREFOX || (ua.__IS_IE && ua.__IS_IE <= 11); -function handleSFShiftTab(fieldType: string): void { +function handleSFShiftTab(fieldType: SFFieldType): void { if (eligibleForTabFix()) { this.handleShiftTab(fieldType); } diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleValidation.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleValidation.test.ts new file mode 100644 index 0000000000..1a934c4b24 --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/handleValidation.test.ts @@ -0,0 +1,182 @@ +import { handleValidation } from './handleValidation'; +import { SFFeedbackObj } from '../../types'; + +let callbackObj_error = null; +let callbackObj_fieldValid = null; + +const securedFieldsObj = { + encryptedCardNumber: { hasError: false, isEncrypted: false }, + encryptedExpiryDate: { hasError: false }, + encryptedSecurityCode: { hasError: false, cvcPolicy: 'required', isEncrypted: false } +}; + +const myCSF = { + state: { type: 'card', securedFields: securedFieldsObj }, + props: { rootNode: 'div' }, + config: { allowedDOMAccess: false }, + callbacks: { + onFieldValid: jest.fn(obj => { + console.log('### handleValidation.test::callbacks.onFieldValid:: obj', obj); + callbackObj_fieldValid = obj; + }), + onError: null + }, + handleValidation, + validateForm: null, + processBrand: jest.fn(() => {}) +}; + +const feedbackObj_error: SFFeedbackObj = { + error: 'error.va.sf-cc-cvc.02', + action: 'incorrectly filled field', + fieldType: 'encryptedSecurityCode', + numKey: 3480222232 +}; + +const feedbackObj_clearedError: SFFeedbackObj = { + error: '', + action: 'delete', + fieldType: 'encryptedSecurityCode', + numKey: 3480222232 +}; + +const feedbackObj_clearedError_PAN: SFFeedbackObj = { + error: '', + action: 'delete', + fieldType: 'encryptedCardNumber', + numKey: 3480222232 +}; + +const feedbackObj_brand_maestro: SFFeedbackObj = { + brand: 'maestro', + cvcPolicy: 'optional', + cvcText: 'Security_code', + maxLength: 3, + action: 'brand', + fieldType: 'encryptedCardNumber', + numKey: 1292815120 +}; + +// //////// + +const expected_callbackObj_errorSet = { + rootNode: 'div', + fieldType: 'encryptedSecurityCode', + error: 'error.va.sf-cc-cvc.02', + type: 'card' +}; + +const expected_callbackObj_errorCleared = { + rootNode: 'div', + fieldType: 'encryptedSecurityCode', + error: '', + type: 'card' +}; + +const expected_callbackObj_onFieldValid = { + fieldType: 'encryptedSecurityCode', + encryptedFieldName: 'encryptedSecurityCode', + uid: 'card-encrypted-encryptedSecurityCode', + valid: false, + type: 'card', + rootNode: 'div' +}; + +const expected_callbackObj_onFieldValid_PAN = { + fieldType: 'encryptedCardNumber', + encryptedFieldName: 'encryptedCardNumber', + uid: 'card-encrypted-encryptedCardNumber', + valid: false, + type: 'card', + rootNode: 'div', + endDigits: '' +}; + +describe('Testing CSFs handleValidation functionality', () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + myCSF.validateForm = jest.fn(() => {}); + + myCSF.callbacks.onError = jest.fn(obj => { + console.log('### handleValidation.test::callbacks.onError:: obj', obj); + callbackObj_error = obj; + }); + }); + + test('handleValidation, when passed an error feedback object, should call processErrors, leading to a call to the onError callback; and then proceed to call validateForm', () => { + myCSF.handleValidation(feedbackObj_error); + + expect(myCSF.callbacks.onError).toHaveBeenCalledTimes(1); + expect(callbackObj_error).toEqual(expected_callbackObj_errorSet); + + expect(myCSF.validateForm).toHaveBeenCalledTimes(1); + }); + + test('handleValidation, when passed an error-clearing feedback object, should call processErrors, leading to a call to the onError callback; and then proceed to call validateForm', () => { + myCSF.handleValidation(feedbackObj_clearedError); + + expect(myCSF.callbacks.onError).toHaveBeenCalledTimes(1); + expect(callbackObj_error).toEqual(expected_callbackObj_errorCleared); + + expect(myCSF.validateForm).toHaveBeenCalledTimes(1); + }); + + test( + 'handleValidation, when passed a brand feedback object, should call processErrors which will immediately return and so not call to the onError callback; ' + + 'then proceed to call validateForm and processBrands; and because the brand is maestro it should also set cvcPolicy on the encryptedSecurityCode object', + () => { + myCSF.handleValidation(feedbackObj_brand_maestro); + + expect(myCSF.callbacks.onError).not.toHaveBeenCalled(); + + expect(myCSF.validateForm).toHaveBeenCalledTimes(1); + + expect(myCSF.state.securedFields.encryptedSecurityCode.cvcPolicy).toEqual('optional'); + + expect(myCSF.processBrand).toHaveBeenCalled(); + } + ); + + test( + 'handleValidation, when dealing with a securityCode that was previously encrypted, ' + + 'should call processErrors which will return and so not call to the onError callback; ' + + 'then proceed to call validateForm, callbacks.onFieldValid & set isEncrypted on the encryptedSecurityCode object', + () => { + myCSF.state.securedFields.encryptedSecurityCode.isEncrypted = true; + myCSF.config.allowedDOMAccess = true; + + myCSF.handleValidation(feedbackObj_clearedError); + + expect(myCSF.callbacks.onError).not.toHaveBeenCalled(); + + expect(myCSF.validateForm).toHaveBeenCalledTimes(1); + + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalled(); + expect(callbackObj_fieldValid).toEqual(expected_callbackObj_onFieldValid); + + expect(myCSF.state.securedFields.encryptedSecurityCode.isEncrypted).toEqual(false); + } + ); + + test( + 'handleValidation, when dealing with a cardNumber that was previously encrypted, ' + + 'should call processErrors which will return and so not call to the onError callback; ' + + 'then proceed to call validateForm, callbacks.onFieldValid (sending an endDigits property) & set isEncrypted on the encryptedCardNumber object', + () => { + myCSF.state.securedFields.encryptedCardNumber.isEncrypted = true; + myCSF.config.allowedDOMAccess = false; + + myCSF.handleValidation(feedbackObj_clearedError_PAN); + + expect(myCSF.callbacks.onError).not.toHaveBeenCalled(); + + expect(myCSF.validateForm).toHaveBeenCalledTimes(1); + + expect(myCSF.callbacks.onFieldValid).toHaveBeenCalled(); + expect(callbackObj_fieldValid).toEqual(expected_callbackObj_onFieldValid_PAN); + + expect(myCSF.state.securedFields.encryptedCardNumber.isEncrypted).toEqual(false); + } + ); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/initCSF.test.tsx b/packages/lib/src/components/internal/SecuredFields/lib/CSF/initCSF.test.tsx index 1272d7bea0..deb829cd97 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/initCSF.test.tsx +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/initCSF.test.tsx @@ -62,7 +62,7 @@ describe('Calling initCSF', () => { expect(() => initCSF({ rootNode: {}, clientKey: 'fsdg', type: 'card' })).toThrow(/WARNING Config :: no loadingContext has been specified!/); }); - test('initializing correctly should lead to the return of a CSF object exposing key functions', () => { + test('initializing correctly (for a "card") should lead to the return of a CSF object exposing key functions', () => { /* @ts-ignore deliberately-not-implementing-all-members */ const csf = initCSF({ rootNode: {}, clientKey: 'fsdg', type: 'card', loadingContext: 'http' }); @@ -77,4 +77,52 @@ describe('Calling initCSF', () => { expect(csf).toHaveProperty('setKCPStatus'); expect(csf).toHaveProperty('sfIsOptionalOrHidden'); }); + + test('initializing correctly (for a non-card) should lead to the return of a CSF object exposing key functions', () => { + /* @ts-ignore deliberately-not-implementing-all-members */ + const csf = initCSF({ rootNode: {}, clientKey: 'fsdg', type: 'ach', loadingContext: 'http' }); + + expect(csf).toHaveProperty('updateStyles'); + expect(csf).toHaveProperty('setFocusOnFrame'); + expect(csf).toHaveProperty('isValidated'); + expect(csf).toHaveProperty('hasUnsupportedCard'); + expect(csf).toHaveProperty('destroy'); + expect(csf).toHaveProperty('brandsFromBinLookup'); + expect(csf).toHaveProperty('addSecuredField'); + expect(csf).toHaveProperty('removeSecuredField'); + expect(csf).toHaveProperty('setKCPStatus'); + expect(csf).toHaveProperty('sfIsOptionalOrHidden'); + }); + + test('initializing for a "card" with one brand should lead to the return of a CSF object exposing key functions', () => { + /* @ts-ignore deliberately-not-implementing-all-members */ + const csf = initCSF({ rootNode: {}, clientKey: 'fsdg', type: 'card', loadingContext: 'http', cardGroupTypes: ['mc'] }); + + expect(csf).toHaveProperty('updateStyles'); + expect(csf).toHaveProperty('setFocusOnFrame'); + expect(csf).toHaveProperty('isValidated'); + expect(csf).toHaveProperty('hasUnsupportedCard'); + expect(csf).toHaveProperty('destroy'); + expect(csf).toHaveProperty('brandsFromBinLookup'); + expect(csf).toHaveProperty('addSecuredField'); + expect(csf).toHaveProperty('removeSecuredField'); + expect(csf).toHaveProperty('setKCPStatus'); + expect(csf).toHaveProperty('sfIsOptionalOrHidden'); + }); + + test('initializing for a "card" with one unrecognised brand should lead to the return of a CSF object exposing key functions', () => { + /* @ts-ignore deliberately-not-implementing-all-members */ + const csf = initCSF({ rootNode: {}, clientKey: 'fsdg', type: 'card', loadingContext: 'http', cardGroupTypes: ['madeupcard'] }); + + expect(csf).toHaveProperty('updateStyles'); + expect(csf).toHaveProperty('setFocusOnFrame'); + expect(csf).toHaveProperty('isValidated'); + expect(csf).toHaveProperty('hasUnsupportedCard'); + expect(csf).toHaveProperty('destroy'); + expect(csf).toHaveProperty('brandsFromBinLookup'); + expect(csf).toHaveProperty('addSecuredField'); + expect(csf).toHaveProperty('removeSecuredField'); + expect(csf).toHaveProperty('setKCPStatus'); + expect(csf).toHaveProperty('sfIsOptionalOrHidden'); + }); }); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleBinValue.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleBinValue.test.ts new file mode 100644 index 0000000000..fa4e02fb7c --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleBinValue.test.ts @@ -0,0 +1,75 @@ +import { handleBinValue } from './handleBinValue'; + +const csfState = { type: 'card' }; +const csfProps = {}; +const csfConfig = {}; +const csfCallbacks = { onBinValue: null }; + +const CSFObj = { + csfState, + csfProps, + csfConfig, + csfCallbacks +}; + +const feedbackObj_min = { + action: 'binValue', + binValue: '5', + fieldType: 'encryptedCardNumber', + numKey: 419658698 +}; + +const feedbackObj_max = { + action: 'binValue', + binValue: '550000', + encryptedBin: 'eyJhbGcx', + uuid: '1d89f5b8', + fieldType: 'encryptedCardNumber', + numKey: 419658698 +}; + +let onBinValueCallbackObj = null; + +csfCallbacks.onBinValue = jest.fn(callbackObj => { + onBinValueCallbackObj = callbackObj; + console.log('### handleFocus.test::onBinValue callback called:: callbackObj', callbackObj); +}); + +const expectedCallbackObj_min = { + binValue: '5', + type: 'card' +}; + +const expectedCallbackObj_max = { + binValue: '550000', + type: 'card', + encryptedBin: 'eyJhbGcx', + uuid: '1d89f5b8' +}; + +const callHandleBinValue = feedbackObj => { + // @ts-ignore - test is faking setup object + handleBinValue(CSFObj, feedbackObj); +}; + +describe('Testing CSFs handleBinValue functionality', () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + onBinValueCallbackObj = null; + }); + + test('handleBinValue should call callback with a minimal callback object since no encryptedBin is present', () => { + callHandleBinValue(feedbackObj_min); + + expect(csfCallbacks.onBinValue).toHaveBeenCalledTimes(1); + expect(onBinValueCallbackObj).toEqual(expectedCallbackObj_min); + }); + + test('handleBinValue should call callback with a larger callback object since encryptedBin is present', () => { + callHandleBinValue(feedbackObj_max); + + expect(csfCallbacks.onBinValue).toHaveBeenCalledTimes(2); + expect(onBinValueCallbackObj).toEqual(expectedCallbackObj_max); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleBinValue.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleBinValue.ts index dd4a04da12..88f8466c23 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleBinValue.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleBinValue.ts @@ -1,4 +1,5 @@ import { SFFeedbackObj, CbObjOnBinValue } from '../../types'; +import { CSFThisObject } from '../types'; interface DestructuredFeedbackObj { binValue?: string; @@ -12,7 +13,7 @@ interface DestructuredFeedbackObj { * * @param pFeedbackObj - */ -export function handleBinValue({ csfState, csfCallbacks }, pFeedbackObj: SFFeedbackObj): void { +export function handleBinValue({ csfState, csfCallbacks }: CSFThisObject, pFeedbackObj: SFFeedbackObj): void { const { binValue, encryptedBin, uuid }: DestructuredFeedbackObj = pFeedbackObj; const callbacksObj: CbObjOnBinValue = { binValue, type: csfState.type }; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleFocus.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleFocus.test.ts new file mode 100644 index 0000000000..991f842117 --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleFocus.test.ts @@ -0,0 +1,100 @@ +import { handleFocus } from './handleFocus'; +import ua from '../utils/userAgent'; +import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_SECURITY_CODE } from '../../configuration/constants'; + +ua.__IS_IOS = false; + +const csfState = { type: 'card', currentFocusObject: null, registerFieldForIos: false }; +const csfProps = { rootNode: 'div' }; +const csfCallbacks = { onFocus: null }; + +const CSFObj = { + csfState, + csfProps, + csfCallbacks +}; + +const feedbackObj = { + action: 'focus', + focus: true, + numChars: 0, + fieldType: ENCRYPTED_CARD_NUMBER, + numKey: 896044601 +}; + +const expectedCallbackObj = { + action: 'focus', + focus: true, + numChars: 0, + fieldType: ENCRYPTED_CARD_NUMBER, + rootNode: 'div', + type: 'card', + currentFocusObject: ENCRYPTED_CARD_NUMBER +}; + +const handleIOSTouchEvents = jest.fn(() => {}); + +const callOnFocus = () => { + // @ts-ignore - test is faking setup object + handleFocus(CSFObj, handleIOSTouchEvents, feedbackObj); +}; + +describe('Testing CSFs handleFocus functionality', () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + csfCallbacks.onFocus = jest.fn(() => {}); + }); + + test('handleFocus should simulate focus event on PAN, when not in iOS scenario', () => { + callOnFocus(); + + expect(csfState.currentFocusObject).toEqual(ENCRYPTED_CARD_NUMBER); + + expect(handleIOSTouchEvents).not.toHaveBeenCalled(); // not iOS + + expect(csfCallbacks.onFocus).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onFocus).toHaveBeenCalledWith(expectedCallbackObj); + }); + + test('handleFocus should see that repeating focus event on PAN, in iOS scenario, does not see handleIOSTouchEvents, since currentFocusObject has not changed', () => { + ua.__IS_IOS = true; + callOnFocus(); + + expect(csfState.currentFocusObject).toEqual(ENCRYPTED_CARD_NUMBER); + + expect(handleIOSTouchEvents).not.toHaveBeenCalled(); + + expect(csfCallbacks.onFocus).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onFocus).toHaveBeenCalledWith(expectedCallbackObj); + }); + + test('handleFocus should simulate focus moving to CVC, in iOS scenario', () => { + ua.__IS_IOS = true; + feedbackObj.fieldType = ENCRYPTED_SECURITY_CODE; + expectedCallbackObj.fieldType = ENCRYPTED_SECURITY_CODE; + expectedCallbackObj.currentFocusObject = ENCRYPTED_SECURITY_CODE; + + callOnFocus(); + + expect(csfState.currentFocusObject).toEqual(ENCRYPTED_SECURITY_CODE); + + expect(handleIOSTouchEvents).toHaveBeenCalled(); // iOS scenario + + expect(csfCallbacks.onFocus).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onFocus).toHaveBeenCalledWith(expectedCallbackObj); + }); + + test('handleFocus should simulate blur event (on CVC field)', () => { + feedbackObj.focus = false; + expectedCallbackObj.currentFocusObject = null; + expectedCallbackObj.focus = false; + + callOnFocus(); + + expect(csfState.currentFocusObject).toEqual(null); + + expect(csfCallbacks.onFocus).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onFocus).toHaveBeenCalledWith(expectedCallbackObj); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleFocus.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleFocus.ts index f6a45dd07c..8bebd2f990 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleFocus.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleFocus.ts @@ -1,5 +1,6 @@ import { SFFeedbackObj, CbObjOnFocus } from '../../types'; import ua from '../utils/userAgent'; +import { CSFThisObject } from '../types'; /** * Call focus callback and store which field currently has focus @@ -10,12 +11,12 @@ import ua from '../utils/userAgent'; * * @param pFeedbackObj - */ -export function handleFocus({ csfState, csfProps, csfCallbacks }, pFeedbackObj: SFFeedbackObj): void { +export function handleFocus({ csfState, csfProps, csfCallbacks }: CSFThisObject, handleIOSTouchEvents, pFeedbackObj: SFFeedbackObj): void { const feedbackObj: SFFeedbackObj = { ...pFeedbackObj }; delete feedbackObj.numKey; - feedbackObj.rootNode = csfProps.rootNode; + feedbackObj.rootNode = csfProps.rootNode as HTMLElement; feedbackObj.type = csfState.type; // Store which field has focus @@ -28,7 +29,7 @@ export function handleFocus({ csfState, csfProps, csfCallbacks }, pFeedbackObj: // If iOS detected AND we don't have a (touchend) listener if (ua.__IS_IOS && !csfState.registerFieldForIos) { - this.handleIOSTouchEvents(); + handleIOSTouchEvents(); } } } else { diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleIframeConfigFeedback.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleIframeConfigFeedback.test.ts new file mode 100644 index 0000000000..da2ed1e77b --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleIframeConfigFeedback.test.ts @@ -0,0 +1,70 @@ +import { handleIframeConfigFeedback } from './handleIframeConfigFeedback'; +import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_PWD_FIELD } from '../../configuration/constants'; + +const csfState = { type: 'card', isConfigured: false, originalNumIframes: 3, iframeConfigCount: 0 }; +const csfCallbacks = { onAdditionalSFConfig: null }; + +const CSFObj = { + csfState, + csfCallbacks +}; + +const feedbackObj = { + action: 'config', + fieldType: ENCRYPTED_CARD_NUMBER, + numKey: 1244301008 +}; + +let isConfigured; + +const callHandleIframeConfigFeedback = () => { + // @ts-ignore - test is faking setup object + return handleIframeConfigFeedback(CSFObj, isConfigured, feedbackObj); +}; + +describe('Testing CSFs handleIframeConfigFeedback functionality', () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + isConfigured = jest.fn(() => {}); + + csfCallbacks.onAdditionalSFConfig = jest.fn(() => {}); + }); + + test('isConfigured should not be called since the iframe count is still below the total', () => { + const res = callHandleIframeConfigFeedback(); + + expect(isConfigured).not.toHaveBeenCalled(); + + expect(res).toEqual(false); + }); + + test('isConfigured should be called since the iframe count is the same as the total', () => { + csfState.iframeConfigCount = 2; + + const res = callHandleIframeConfigFeedback(); + + expect(isConfigured).toHaveBeenCalledTimes(1); + + expect(res).toEqual(true); + }); + + test('isConfigured should be not called again since we are now mocking an additional securedField being added i.e. KCP scenario', () => { + csfState.isConfigured = true; + feedbackObj.fieldType = ENCRYPTED_PWD_FIELD; + + const res = callHandleIframeConfigFeedback(); + + // no new call to isConfigured + expect(isConfigured).not.toHaveBeenCalled(); + + expect(csfCallbacks.onAdditionalSFConfig).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onAdditionalSFConfig).toHaveBeenCalledWith({ + additionalIframeConfigured: true, + fieldType: ENCRYPTED_PWD_FIELD, + type: 'card' + }); + + expect(res).toEqual(false); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleIframeConfigFeedback.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleIframeConfigFeedback.ts index 3929bfe686..e443bbbc26 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleIframeConfigFeedback.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/handleIframeConfigFeedback.ts @@ -1,6 +1,7 @@ // Count how many iframes have successfully been configured and, if its all of them, call callback function import { CbObjOnAdditionalSF } from '../../types'; +import { CSFThisObject } from '../types'; // First, object, param comes from partial implementation /** @@ -10,7 +11,7 @@ import { CbObjOnAdditionalSF } from '../../types'; * * @param pFeedbackObj - */ -export function handleIframeConfigFeedback({ csfState, csfCallbacks }, isConfigured, pFeedbackObj): boolean { +export function handleIframeConfigFeedback({ csfState, csfCallbacks }: CSFThisObject, isConfigured, pFeedbackObj): boolean { csfState.iframeConfigCount += 1; if (window._b$dl) diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/isConfigured.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/isConfigured.test.ts new file mode 100644 index 0000000000..afd6f51089 --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/isConfigured.test.ts @@ -0,0 +1,99 @@ +import { isConfigured } from './isConfigured'; +import * as logger from '../../utilities/logger'; + +const csfState = { type: 'card', isConfigured: false, numIframes: 3 }; +const csfProps = { rootNode: 'div' }; +const csfConfig = { isCreditCardType: true }; +const csfCallbacks = { onConfigSuccess: null }; + +const CSFObj = { + csfState, + csfProps, + csfConfig, + csfCallbacks +}; + +let consoleError = null; + +let validateForm; + +const callIsConfigured = () => { + // @ts-ignore - test is faking setup object + return isConfigured(CSFObj, validateForm); +}; + +describe('Testing CSFs isConfigured functionality', () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + + // @ts-ignore allow jset override of error loggers + console.error = logger.error = jest.fn(error => { + consoleError = error; + }); + + validateForm = jest.fn(() => {}); + + csfCallbacks.onConfigSuccess = jest.fn(() => {}); + }); + + test('onConfigSuccess callback should be called since this is the purpose of this function, but validateForm is not called since we are dealing with 3 securedFields & not a recurringCard', () => { + const res = callIsConfigured(); + + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledWith({ iframesConfigured: true, type: 'card', rootNode: 'div' }); + // expect(onConfigSuccessCallbackObj).toEqual({ iframesConfigured: true, type: 'card', rootNode: 'div' }); + + expect(validateForm).not.toHaveBeenCalled(); + + expect(res).toEqual(true); + }); + + test('Console should show an error since we are saying we have a recurringCard - but the type is "card"', () => { + csfState.numIframes = 1; + const res = callIsConfigured(); + + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledWith({ iframesConfigured: true, type: 'card', rootNode: 'div' }); + + expect(validateForm).not.toHaveBeenCalled(); + + expect(res).toEqual(false); + expect(consoleError).toEqual("ERROR: Payment method with a single secured field - but 'type' has not been set to a specific card brand"); + }); + + test('validateForm should not be called since we are dealing with a recurring card that has a cvcPolicy that equals "reguired"', () => { + csfState.type = 'visa'; + const res = callIsConfigured(); + + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledWith({ iframesConfigured: true, type: 'visa', rootNode: 'div' }); + + expect(validateForm).not.toHaveBeenCalled(); + + expect(res).toEqual(true); + }); + + test("validateForm should not be called since we are dealing with a recurring card of an unknown txvariant so we don't know what it's cvcPolicy is", () => { + csfState.type = 'visa_tokenised_subvariant'; + const res = callIsConfigured(); + + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledWith({ iframesConfigured: true, type: 'visa_tokenised_subvariant', rootNode: 'div' }); + + expect(validateForm).not.toHaveBeenCalled(); + + expect(res).toEqual(true); + }); + + test('validateForm should be called since we are dealing with a known recurring card that has a cvcPolicy that equals "optional"', () => { + csfState.type = 'laser'; + const res = callIsConfigured(); + + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onConfigSuccess).toHaveBeenCalledWith({ iframesConfigured: true, type: 'laser', rootNode: 'div' }); + + expect(validateForm).toHaveBeenCalled(); + + expect(res).toEqual(true); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/isConfigured.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/isConfigured.ts index fa4b06e35b..860a767c2e 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/isConfigured.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/isConfigured.ts @@ -2,6 +2,7 @@ import cardType from '../utils/cardType'; import { CardObject, CbObjOnConfigSuccess } from '../../types'; import * as logger from '../../utilities/logger'; import { CVC_POLICY_REQUIRED } from '../../configuration/constants'; +import { CSFThisObject } from '../types'; /** * @param csfState - comes from initial, partial, implementation @@ -10,10 +11,10 @@ import { CVC_POLICY_REQUIRED } from '../../configuration/constants'; * @param csfCallbacks - comes from initial, partial, implementation * @param validateForm - comes from initial, partial, implementation */ -export function isConfigured({ csfState, csfConfig, csfProps, csfCallbacks }, validateForm): void { +export function isConfigured({ csfState, csfConfig, csfProps, csfCallbacks }: CSFThisObject, validateForm): boolean { csfState.isConfigured = true; - const callbackObj: CbObjOnConfigSuccess = { iframesConfigured: true, type: csfState.type, rootNode: csfProps.rootNode }; + const callbackObj: CbObjOnConfigSuccess = { iframesConfigured: true, type: csfState.type, rootNode: csfProps.rootNode as HTMLElement }; csfCallbacks.onConfigSuccess(callbackObj); @@ -21,7 +22,7 @@ export function isConfigured({ csfState, csfConfig, csfProps, csfCallbacks }, va if (csfState.numIframes === 1 && csfConfig.isCreditCardType) { if (csfState.type === 'card') { logger.error("ERROR: Payment method with a single secured field - but 'type' has not been set to a specific card brand"); - return; + return false; } // Get card object from txVariant @@ -35,9 +36,9 @@ export function isConfigured({ csfState, csfConfig, csfProps, csfCallbacks }, va // If cvc is optional - the form can be considered valid if (cvcPolicy !== CVC_POLICY_REQUIRED) { - // this.validateForm(); validateForm(); } } } + return true; } diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/postMessageToAllIframes.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/postMessageToAllIframes.test.ts new file mode 100644 index 0000000000..8f0856d91a --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/postMessageToAllIframes.test.ts @@ -0,0 +1,69 @@ +import postMessageToIframe from '../utils/iframes/postMessageToIframe'; +import { postMessageToAllIframes } from './postMessageToAllIframes'; +import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE, ENCRYPTED_SECURITY_CODE } from '../../configuration/constants'; + +jest.mock('../utils/iframes/postMessageToIframe'); + +const mockedPostMessageToIframe = postMessageToIframe as jest.Mock; + +const csfState = { + type: 'card', + securedFields: { encryptedCardNumber: { numKey: 654321 }, encryptedExpiryDate: { numKey: 654321 }, encryptedSecurityCode: { numKey: 654321 } } +}; +const csfConfig = { loadingContext: '*' }; + +const CSFObj = { + csfState, + csfConfig +}; + +const dataObj = { + destroy: true +}; + +const expectedPostMsgDataObj = { + txVariant: 'card', + fieldType: ENCRYPTED_CARD_NUMBER, + numKey: 654321, + destroy: true +}; + +const callPostMessageToAllIframes = sfFeedbackObj => { + // @ts-ignore - test is faking setup object + return postMessageToAllIframes(CSFObj, sfFeedbackObj); +}; + +describe('Testing postMessageToAllIframes fny', () => { + const postMessageToIframeMock = jest.fn(obj => console.log('### postMessageToAllIframes.test::FN call:: ', obj)); + + beforeEach(() => { + console.log = jest.fn(() => {}); + + mockedPostMessageToIframe.mockReset(); + mockedPostMessageToIframe.mockImplementation(obj => postMessageToIframeMock(obj)); + postMessageToIframeMock.mockClear(); + }); + + test('Calling postMessageToAllIframes with no data object will not lead to any calls to postMessageToIframe', () => { + const res = callPostMessageToAllIframes(null); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(res).toEqual(false); + }); + + test('Calling postMessageToAllIframes with a data object will lead to calls to postMessageToIframe for all existing securedFields', () => { + const res = callPostMessageToAllIframes(dataObj); + + expect(postMessageToIframeMock).toHaveBeenCalledTimes(3); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedPostMsgDataObj); // PAN SF + + expectedPostMsgDataObj.fieldType = ENCRYPTED_EXPIRY_DATE; + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedPostMsgDataObj); // Date SF + + expectedPostMsgDataObj.fieldType = ENCRYPTED_SECURITY_CODE; + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedPostMsgDataObj); // CVC SF + + expect(res).toEqual(true); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/postMessageToAllIframes.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/postMessageToAllIframes.ts index a246a8cadc..600eefe3b1 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/postMessageToAllIframes.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/postMessageToAllIframes.ts @@ -1,5 +1,7 @@ import postMessageToIframe from '../utils/iframes/postMessageToIframe'; import getIframeContentWin from '../utils/iframes/getIframeContentWin'; +import { CSFThisObject } from '../types'; +import { SFFieldType } from '../../types'; /** * UTIL TO BROADCAST TO ALL IFRAMES AT ONCE @@ -10,16 +12,16 @@ import getIframeContentWin from '../utils/iframes/getIframeContentWin'; * * @param pDataObj - */ -export function postMessageToAllIframes({ csfState, csfConfig }, pDataObj: object): void { +export function postMessageToAllIframes({ csfState, csfConfig }: CSFThisObject, pDataObj: object): boolean { const objKeys: string[] = Object.keys(pDataObj || {}); if (!objKeys.length) { // pDataObj is an object with the 'special' key(s) that represent the reason for making this postMessage // without it/them there is no reason to postMessage - return; + return false; } const securedFieldKeys: string[] = Object.keys(csfState.securedFields); - securedFieldKeys.forEach(pFieldType => { + securedFieldKeys.forEach((pFieldType: SFFieldType) => { const dataObj: object = { txVariant: csfState.type, fieldType: pFieldType, @@ -33,4 +35,5 @@ export function postMessageToAllIframes({ csfState, csfConfig }, pDataObj: objec postMessageToIframe(dataObj, getIframeContentWin(csfState, pFieldType), csfConfig.loadingContext); }); + return true; } diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processAutoComplete.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processAutoComplete.test.ts new file mode 100644 index 0000000000..f25b752ffb --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processAutoComplete.test.ts @@ -0,0 +1,191 @@ +import postMessageToIframe from '../utils/iframes/postMessageToIframe'; +import { SFFeedbackObj } from '../../types'; +import { processAutoComplete } from './processAutoComplete'; +import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE } from '../../configuration/constants'; + +jest.mock('../utils/iframes/postMessageToIframe'); + +const mockedPostMessageToIframe = postMessageToIframe as jest.Mock; + +jest.useFakeTimers(); +jest.spyOn(global, 'setTimeout'); + +const csfState = { + brand: { brand: null, cvcPolicy: 'required' }, + type: 'card', + securedFields: { encryptedExpiryDate: { numKey: 654321 }, encryptedExpiryMonth: { numKey: 654321 }, encryptedExpiryYear: { numKey: 654321 } } +}; +const csfConfig = {}; +const csfCallbacks = { onAutoComplete: null }; + +const CSFObj = { + csfState, + // csfProps, + csfConfig, + csfCallbacks +}; + +const nameCallbackObj = { + action: 'autoComplete', + name: 'cc-name', + value: 'nn', + fieldType: ENCRYPTED_CARD_NUMBER +}; + +const sfFeedbackObj_name: SFFeedbackObj = { + action: 'autoComplete', + name: 'cc-name', + value: 'nn', + fieldType: ENCRYPTED_CARD_NUMBER, + numKey: 654321 +}; + +const sfFeedbackObj_date: SFFeedbackObj = { + action: 'autoComplete', + name: 'cc-exp', + value: '03/2030', + fieldType: ENCRYPTED_CARD_NUMBER, + numKey: 654321 +}; + +const expectedPostMsgDataObj = { + txVariant: 'card', + fieldType: ENCRYPTED_EXPIRY_DATE, + autoComplete: '03/30', + numKey: 654321 +}; + +const callProcessAutoComplete = sfFeedbackObj => { + // @ts-ignore - test is faking setup object + return processAutoComplete(CSFObj, sfFeedbackObj); +}; + +describe('Testing processAutoComplete fny', () => { + const postMessageToIframeMock = jest.fn(obj => console.log('### processAutoComplete.test::FN call:: ', obj)); + + beforeEach(() => { + console.log = jest.fn(() => {}); + + csfCallbacks.onAutoComplete = jest.fn(() => {}); + + mockedPostMessageToIframe.mockReset(); + mockedPostMessageToIframe.mockImplementation(obj => postMessageToIframeMock(obj)); + postMessageToIframeMock.mockClear(); + }); + + test('Calling processAutoComplete will not lead to a call to postMessageToIframe since we are auto-filling the name so only the callback fn should be called', () => { + const res = callProcessAutoComplete(sfFeedbackObj_name); + + expect(csfCallbacks.onAutoComplete).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onAutoComplete).toHaveBeenCalledWith(nameCallbackObj); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(res).toEqual(true); + }); + + test('Calling processAutoComplete will not call anything & return false since the date is not long enough', () => { + sfFeedbackObj_date.value = '03'; + + const res = callProcessAutoComplete(sfFeedbackObj_date); + + // callback not called a second time + expect(csfCallbacks.onAutoComplete).not.toHaveBeenCalled(); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(res).toEqual(false); + }); + + test('Calling processAutoComplete will not call anything & return false since the year is not set', () => { + sfFeedbackObj_date.value = '03/'; + + const res = callProcessAutoComplete(sfFeedbackObj_date); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(res).toEqual(false); + }); + + test('Calling processAutoComplete will not call anything & return false since the year is not set correctly (too short)', () => { + sfFeedbackObj_date.value = '03/1'; + + const res = callProcessAutoComplete(sfFeedbackObj_date); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(res).toEqual(false); + }); + + test('Calling processAutoComplete will not call anything & return false since the year is not set correctly (3 digits)', () => { + sfFeedbackObj_date.value = '03/999'; + + const res = callProcessAutoComplete(sfFeedbackObj_date); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(res).toEqual(false); + }); + + test('Calling processAutoComplete will not call anything & return false since the year is not set correctly (not digits)', () => { + sfFeedbackObj_date.value = '03/yyyy'; + + const res = callProcessAutoComplete(sfFeedbackObj_date); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(res).toEqual(false); + }); + + test('Calling processAutoComplete will call postMessageToIframe sending it the expected expiryDate related data object, and the onAutoComplete callback will not be called', () => { + sfFeedbackObj_date.value = '03/2030'; + + const res = callProcessAutoComplete(sfFeedbackObj_date); + + expect(csfCallbacks.onAutoComplete).not.toHaveBeenCalled(); + + expect(postMessageToIframeMock).toHaveBeenCalled(); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedPostMsgDataObj); + + expect(res).toEqual(true); + }); + + test('Calling processAutoComplete will call postMessageToIframe twice sending it the expected expiryMonth and then expiryYear data objects, and the onAutoComplete callback will not be called', () => { + sfFeedbackObj_date.value = '3/2030'; // also test the month padding fny + + delete csfState.securedFields.encryptedExpiryDate; + + expectedPostMsgDataObj.fieldType = 'encryptedExpiryMonth'; + expectedPostMsgDataObj.autoComplete = '03'; + + const res = callProcessAutoComplete(sfFeedbackObj_date); + + expect(csfCallbacks.onAutoComplete).not.toHaveBeenCalled(); + + expect(postMessageToIframeMock).toHaveBeenCalled(); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedPostMsgDataObj); + + expect(res).toEqual(true); + + expectedPostMsgDataObj.fieldType = 'encryptedExpiryYear'; + expectedPostMsgDataObj.autoComplete = '30'; + + // Fast-forward until expiryYear related timer has been executed + jest.runAllTimers(); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedPostMsgDataObj); + }); + + test('Calling processAutoComplete will not call anything & return false since the autoComplete event is not for a name or date field', () => { + sfFeedbackObj_date.name = 'cc-csc'; + + const res = callProcessAutoComplete(sfFeedbackObj_date); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(res).toEqual(false); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processAutoComplete.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processAutoComplete.ts index 3b47313a43..46b47edc41 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processAutoComplete.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processAutoComplete.ts @@ -3,6 +3,7 @@ import { ENCRYPTED_EXPIRY_DATE, ENCRYPTED_EXPIRY_MONTH, ENCRYPTED_EXPIRY_YEAR } import { SFFeedbackObj, CbObjOnAutoComplete } from '../../types'; import { hasOwnProperty } from '../../../../../../utils/hasOwnProperty'; import getIframeContentWin from '../utils/iframes/getIframeContentWin'; +import { CSFThisObject } from '../types'; /** * @@ -12,7 +13,7 @@ import getIframeContentWin from '../utils/iframes/getIframeContentWin'; * * @param pFeedbackObj - */ -export function processAutoComplete({ csfState, csfConfig, csfCallbacks }, pFeedbackObj: SFFeedbackObj): boolean { +export function processAutoComplete({ csfState, csfConfig, csfCallbacks }: CSFThisObject, pFeedbackObj: SFFeedbackObj): boolean { /** * NOTE: It seems Chrome has started autofilling across cross-origin iframes. Have tested as far back as v104 but have no resources to test further back * So, in theory for Chrome \>= v104 we don't need to do any of this, including having special listeners in the securedFields @@ -24,6 +25,7 @@ export function processAutoComplete({ csfState, csfConfig, csfCallbacks }, pFeed delete feedbackObj.numKey; const ACFeedbackObj: CbObjOnAutoComplete = feedbackObj as CbObjOnAutoComplete; csfCallbacks.onAutoComplete(ACFeedbackObj); + return true; } // Send date info to relevant secured fields (needed for Safari whose Security model won't allow direct population of fields in 3rd party iframes) @@ -32,7 +34,10 @@ export function processAutoComplete({ csfState, csfConfig, csfCallbacks }, pFeed const dateValArr: string[] = splittableDateVal.split('/'); - if (dateValArr.length !== 2) return false; // To avoid bug in some versions of Safari where date doesn't come through as expected + if (dateValArr.length !== 2) { + // To avoid bug in some versions of Safari where date doesn't come through as expected + return false; + } if (dateValArr[0].length === 1) dateValArr[0] = `0${dateValArr[0]}`; // pad, if required @@ -41,7 +46,9 @@ export function processAutoComplete({ csfState, csfConfig, csfCallbacks }, pFeed // Extra checks that passed year is a valid value const year = dateValArr[1]; const isValidYear = (year?.length === 4 || year?.length === 2) && !isNaN(parseInt(year)); - if (!isValidYear) return false; + if (!isValidYear) { + return false; + } const acYearVal: string = year.slice(-2); // take last 2 digits of year const acDateVal = `${acMonthVal}/${acYearVal}`; @@ -81,4 +88,6 @@ export function processAutoComplete({ csfState, csfConfig, csfCallbacks }, pFeed } return true; } + + return false; } diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processBrand.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processBrand.test.ts new file mode 100644 index 0000000000..d692fdaace --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processBrand.test.ts @@ -0,0 +1,120 @@ +import postMessageToIframe from '../utils/iframes/postMessageToIframe'; +import processBrand from './processBrand'; +import { SFFeedbackObj } from '../../types'; +import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_SECURITY_CODE } from '../../configuration/constants'; + +jest.mock('../utils/iframes/postMessageToIframe'); + +const mockedPostMessageToIframe = postMessageToIframe as jest.Mock; + +const csfState = { brand: { brand: null, cvcPolicy: 'required' }, type: null, securedFields: { encryptedSecurityCode: { numKey: 654321 } } }; +const csfProps = { rootNode: 'div' }; +const csfConfig = {}; +const csfCallbacks = { onBrand: null }; + +const CSFObj = { + csfState, + csfProps, + csfConfig, + csfCallbacks +}; + +const sfFeedbackObj: SFFeedbackObj = { + action: 'foo', + brand: 'amex', + numKey: 654321, + fieldType: ENCRYPTED_SECURITY_CODE, + cvcPolicy: 'required', + expiryDatePolicy: 'required', + showSocialSecurityNumber: false, + type: null +}; + +const expectedPostMsgDataObj = { + txVariant: 'card', + brand: 'amex', + fieldType: ENCRYPTED_SECURITY_CODE, + cvcPolicy: 'required', + numKey: 654321 +}; + +const expectedCallbackObj = { + brand: 'amex', + cvcPolicy: 'required', + expiryDatePolicy: 'required', + showSocialSecurityNumber: false, + type: 'card', + rootNode: 'div' +}; + +const callProcessBrand = () => { + // @ts-ignore - test is faking setup object + processBrand(CSFObj, sfFeedbackObj); +}; + +describe('Testing processBrand fny', () => { + const postMessageToIframeMock = jest.fn(obj => console.log('### processBrand.test::FN call:: ', obj)); + + beforeEach(() => { + console.log = jest.fn(() => {}); + + csfCallbacks.onBrand = jest.fn(() => {}); + + mockedPostMessageToIframe.mockReset(); + mockedPostMessageToIframe.mockImplementation(obj => postMessageToIframeMock(obj)); + postMessageToIframeMock.mockClear(); + }); + + test('Calling processBrand will not lead to a call to postMessageToIframe since feedback object is not for an "encryptedCardNumber" field', () => { + callProcessBrand(); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + }); + + test('Calling processBrand will not lead to a call to postMessageToIframe since state object does not have type "card"; nor will the onBrand callback get called', () => { + sfFeedbackObj.fieldType = ENCRYPTED_CARD_NUMBER; + callProcessBrand(); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(csfCallbacks.onBrand).not.toHaveBeenCalled(); + }); + + test('Calling processBrand will lead to a call to postMessageToIframe since state object has type "card"; and the onBrand callback will be called', () => { + csfState.type = 'card'; + callProcessBrand(); + + expect(postMessageToIframeMock).toHaveBeenCalled(); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedPostMsgDataObj); + + expect(csfCallbacks.onBrand).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onBrand).toHaveBeenCalledWith(expectedCallbackObj); + }); + + test('Calling processBrand will lead to a call to postMessageToIframe since state object has type "bcmc" (& the brand has changed); and the onBrand callback will be called', () => { + csfState.type = 'bcmc'; + sfFeedbackObj.brand = 'maestro'; + + expectedPostMsgDataObj.txVariant = 'bcmc'; + expectedPostMsgDataObj.brand = 'maestro'; + + expectedCallbackObj.type = 'bcmc'; + expectedCallbackObj.brand = 'maestro'; + + callProcessBrand(); + + expect(postMessageToIframeMock).toHaveBeenCalled(); + expect(postMessageToIframeMock).toHaveBeenCalledWith(expectedPostMsgDataObj); + + expect(csfCallbacks.onBrand).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onBrand).toHaveBeenCalledWith(expectedCallbackObj); + }); + + test('Calling processBrand will not lead to another call to postMessageToIframe or onBrand since the brand has not changed', () => { + callProcessBrand(); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + + expect(csfCallbacks.onBrand).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processBrand.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processBrand.ts index 80be2bd4e0..b4fa3e6da9 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processBrand.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/processBrand.ts @@ -1,10 +1,11 @@ import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_SECURITY_CODE } from '../../configuration/constants'; import postMessageToIframe from '../utils/iframes/postMessageToIframe'; import { objectsDeepEqual } from '../../utilities/commonUtils'; -import { BrandStorageObject, CbObjOnBrand, SFFeedbackObj } from '../../types'; +import { BrandStorageObject, CbObjOnBrand, SFFeedbackObj, SFFieldType } from '../../types'; import { pick } from '../../../utils'; import { hasOwnProperty } from '../../../../../../utils/hasOwnProperty'; import getIframeContentWin from '../utils/iframes/getIframeContentWin'; +import { CSFThisObject } from '../types'; const checkForBrandChange = (pBrand: BrandStorageObject, storedBrand: BrandStorageObject): boolean => { // if the objects aren't the same - then return true = brandChange has happened @@ -24,8 +25,8 @@ const checkForBrandChange = (pBrand: BrandStorageObject, storedBrand: BrandStora * * @param pFeedbackObj - */ -export default function processBrand({ csfState, csfConfig, csfProps, csfCallbacks }, pFeedbackObj: SFFeedbackObj): boolean { - const fieldType: string = pFeedbackObj.fieldType; +export default function processBrand({ csfState, csfConfig, csfProps, csfCallbacks }: CSFThisObject, pFeedbackObj: SFFeedbackObj): boolean { + const fieldType: SFFieldType = pFeedbackObj.fieldType; if (fieldType === ENCRYPTED_CARD_NUMBER) { // Check for new brand... @@ -71,9 +72,9 @@ export default function processBrand({ csfState, csfConfig, csfProps, csfCallbac if (brandInfoObj && brandInfoObj.brand) { const callbackObj: CbObjOnBrand = brandInfoObj as CbObjOnBrand; callbackObj.type = csfState.type; - callbackObj.rootNode = csfProps.rootNode; + callbackObj.rootNode = csfProps.rootNode as HTMLElement; - /// ...and call SFPHandlers.handleOnBrand + // ...and call SFPHandlers.handleOnBrand csfCallbacks.onBrand(callbackObj); } diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/setFocusOnFrame.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/setFocusOnFrame.test.ts new file mode 100644 index 0000000000..7998c6d85f --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/setFocusOnFrame.test.ts @@ -0,0 +1,45 @@ +import postMessageToIframe from '../utils/iframes/postMessageToIframe'; +import { setFocusOnFrame } from './setFocusOnFrame'; + +jest.mock('../utils/iframes/postMessageToIframe'); + +const mockedPostMessageToIframe = postMessageToIframe as jest.Mock; + +const csfState = { type: 'card', securedFields: { encryptedSecurityCode: { numKey: 654321 } } }; +const csfConfig = {}; + +const CSFObj = { + csfState, + csfConfig +}; + +describe('Testing setFocusOnFrame fny', () => { + const postMessageToIframeMock = jest.fn(obj => console.log('### setFocusOnFrame.test::FN call:: ', obj)); + + beforeEach(() => { + console.log = jest.fn(() => {}); + + mockedPostMessageToIframe.mockReset(); + mockedPostMessageToIframe.mockImplementation(obj => postMessageToIframeMock(obj)); + postMessageToIframeMock.mockClear(); + }); + + test('Calling setFocusOnFrame will lead to a call to postMessageToIframe', () => { + // @ts-ignore - test is faking setup object + setFocusOnFrame(CSFObj, 'encryptedSecurityCode'); + + expect(postMessageToIframeMock).toHaveBeenCalledWith({ + txVariant: 'card', + fieldType: 'encryptedSecurityCode', + focus: true, + numKey: 654321 + }); + }); + + test('Calling setFocusOnFrame will not lead to a call to postMessageToIframe since the securedFields prop has no "encryptedCardNumber" key', () => { + // @ts-ignore - test is faking setup object + setFocusOnFrame(CSFObj, 'encryptedCardNumber'); + + expect(postMessageToIframeMock).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/setFocusOnFrame.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/setFocusOnFrame.ts index 17c1f55357..a3eede88bd 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/setFocusOnFrame.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/setFocusOnFrame.ts @@ -1,6 +1,8 @@ import postMessageToIframe from '../utils/iframes/postMessageToIframe'; import { hasOwnProperty } from '../../../../../../utils/hasOwnProperty'; import getIframeContentWin from '../utils/iframes/getIframeContentWin'; +import { SFFieldType } from '../../types'; +import { CSFThisObject } from '../types'; /** * @param csfState - comes from initial, partial, implementation @@ -9,7 +11,7 @@ import getIframeContentWin from '../utils/iframes/getIframeContentWin'; * @param pFieldType - * @param doLog - */ -export function setFocusOnFrame({ csfState, csfConfig }, pFieldType: string, doLog?: boolean): void { +export function setFocusOnFrame({ csfState, csfConfig }: CSFThisObject, pFieldType: SFFieldType, doLog?: boolean): void { // Check destroySecuredFields hasn't been called (thus clearing the state's securedFields object) if (!hasOwnProperty(csfState.securedFields, pFieldType)) return; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/validateForm.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/validateForm.test.ts new file mode 100644 index 0000000000..872500f632 --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/validateForm.test.ts @@ -0,0 +1,68 @@ +import validateForm from './validateForm'; + +const securedFieldsObj = { + encryptedCardNumber: { isValid: false }, + encryptedExpiryDate: { isValid: true }, + encryptedSecurityCode: { isValid: true } +}; + +const csfState = { allValid: true, securedFields: securedFieldsObj, type: 'card' }; +const csfProps = { rootNode: 'div' }; +const csfCallbacks = { onAllValid: null }; + +const CSFObj = { + csfState, + csfProps, + csfCallbacks +}; + +const callValidateForm = () => { + // @ts-ignore - test is faking setup object + validateForm(CSFObj); +}; + +beforeEach(() => { + console.log = jest.fn(() => {}); + + csfCallbacks.onAllValid = jest.fn(() => {}); +}); + +describe('Testing CSFs validateForm functionality', () => { + test('Callback should be called since we are seeing a change from a valid to an invalid state', () => { + callValidateForm(); + + expect(csfCallbacks.onAllValid).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onAllValid).toHaveBeenCalledWith({ allValid: false, rootNode: 'div', type: 'card' }); + }); + + test('Callback should not be called since there is no overall state change', () => { + callValidateForm(); + + // still only been called once + expect(csfCallbacks.onAllValid).not.toHaveBeenCalled(); + }); + + test('Callback should be called since everything is now valid', () => { + securedFieldsObj.encryptedCardNumber.isValid = true; + + callValidateForm(); + + expect(csfCallbacks.onAllValid).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onAllValid).toHaveBeenCalledWith({ allValid: true, rootNode: 'div', type: 'card' }); + }); + + test('Callback called again since since it is always called when everything is valid', () => { + callValidateForm(); + + expect(csfCallbacks.onAllValid).toHaveBeenCalledTimes(1); + }); + + test('Callback called again since since we are changing from a valid to an invalid state', () => { + securedFieldsObj.encryptedCardNumber.isValid = false; + + callValidateForm(); + + expect(csfCallbacks.onAllValid).toHaveBeenCalledTimes(1); + expect(csfCallbacks.onAllValid).toHaveBeenCalledWith({ allValid: false, rootNode: 'div', type: 'card' }); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/validateForm.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/validateForm.ts index 69e8df0a0b..e56ae8e75a 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/validateForm.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/partials/validateForm.ts @@ -1,6 +1,7 @@ -import { CbObjOnAllValid } from '../../types'; +import { CbObjOnAllValid, SecuredFields } from '../../types'; +import { CSFThisObject } from '../types'; -const checkFormIsValid = (pSecuredFields: object): boolean => { +const checkFormIsValid = (pSecuredFields: SecuredFields): boolean => { const securedFieldKeys: string[] = Object.keys(pSecuredFields); for (let i = 0, len = securedFieldKeys.length; i < len; i += 1) { @@ -18,7 +19,7 @@ const checkFormIsValid = (pSecuredFields: object): boolean => { * @param csfProps - comes from initial, partial, implementation * @param csfCallbacks - comes from initial, partial, implementation */ -export default function validateForm({ csfState, csfProps, csfCallbacks }): void { +export default function validateForm({ csfState, csfProps, csfCallbacks }: CSFThisObject): void { const isValid: boolean = checkFormIsValid(csfState.securedFields); const validityHasChanged: boolean = isValid !== csfState.allValid; @@ -28,7 +29,7 @@ export default function validateForm({ csfState, csfProps, csfCallbacks }): void // Only call onAllValid callback if value has changed OR is true if (!isValid && !validityHasChanged) return; - const callbackObj: CbObjOnAllValid = { allValid: isValid, type: csfState.type, rootNode: csfProps.rootNode }; + const callbackObj: CbObjOnAllValid = { allValid: isValid, type: csfState.type, rootNode: csfProps.rootNode as HTMLElement }; // BROADCAST VALID STATE OF THE FORM AS A WHOLE csfCallbacks.onAllValid(callbackObj); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/types.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/types.ts index f40c715a7e..d631ac2b84 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/types.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/types.ts @@ -1,5 +1,5 @@ import Language from '../../../../../language/Language'; -import { BrandStorageObject } from '../types'; +import { BrandStorageObject, SecuredFields } from '../types'; import { Placeholders } from '../../SFP/types'; /** @@ -61,7 +61,7 @@ export interface CSFConfigObject extends CSFCommonProps { export interface CSFSetupObject extends CSFCommonProps { type: string; clientKey: string; - rootNode: string | HTMLElement; + rootNode: string | HTMLElement; // TODO - when is this a string? callbacks?: object; isKCP?: boolean; i18n?: Language; @@ -100,6 +100,13 @@ export interface CSFStateObject { hasSeparateDateFields: boolean; currentFocusObject: string; registerFieldForIos: boolean; - securedFields: object; + securedFields: SecuredFields; isKCP: boolean; } + +export interface CSFThisObject { + csfState?: CSFStateObject; + csfConfig?: CSFConfigObject; + csfProps?: CSFSetupObject; + csfCallbacks?: CSFCallbacksConfig; +} diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/iframes/postMessageToIframe.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/iframes/postMessageToIframe.test.ts new file mode 100644 index 0000000000..1bf13bf832 --- /dev/null +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/iframes/postMessageToIframe.test.ts @@ -0,0 +1,30 @@ +import postMessageToIframe from './postMessageToIframe'; + +window.postMessage = jest.fn(); + +describe('Testing postMessageToIframe fny', () => { + beforeEach(() => { + console.log = jest.fn(() => {}); + }); + + test('postMessage not called since there is no window object', () => { + postMessageToIframe({}, null, '*'); + + expect(window.postMessage).not.toHaveBeenCalled(); + }); + + test('postMessage called with expected data object', () => { + const dataObj = { + txVariant: 'card', + fieldType: 'encryptedSecurityCode', + focus: true, + numKey: 654321 + }; + + postMessageToIframe(dataObj, window, '*'); + + expect(window.postMessage).toHaveBeenCalledTimes(1); + + expect(window.postMessage).toHaveBeenCalledWith(JSON.stringify(dataObj), '*'); + }); +}); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/processErrors.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/processErrors.test.ts index e14b05329a..8438889a09 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/processErrors.test.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/processErrors.test.ts @@ -1,6 +1,7 @@ /* global expect, describe, jest, beforeEach */ import { processErrors } from './processErrors'; import SecuredField from '../../securedField/SecuredField'; +import { SFFeedbackObj } from '../../types'; const ERROR_MSG_CARD_TOO_FAR_IN_FUTURE = 'ERROR_MSG_CARD_TOO_FAR_IN_FUTURE'; const ERROR_MSG_CARD_TOO_OLD = 'ERROR_MSG_CARD_TOO_OLD'; const ERROR_MSG_INCOMPLETE_FIELD = 'ERROR_MSG_INCOMPLETE_FIELD'; @@ -26,21 +27,21 @@ const errorObj_dateTooOld = { action: 'dateKeyPressed', fieldType: 'encryptedExpiryDate', numKey: 3522473789 -}; +} as SFFeedbackObj; const erroObj_dateTooFar = { error: ERROR_MSG_CARD_TOO_FAR_IN_FUTURE, action: 'dateKeyPressed', fieldType: 'encryptedExpiryDate', numKey: 3522473789 -}; +} as SFFeedbackObj; const errorObj_incompleteField = { error: ERROR_MSG_INCOMPLETE_FIELD, action: 'blur', fieldType: 'encryptedCardNumber', numKey: 3522473789 -}; +} as SFFeedbackObj; // const errorObj_luhnCheck = { // "action": "luhnCheck", @@ -61,14 +62,14 @@ const noErrorObj = { action: 'dateKeyPressed', fieldType: 'encryptedCardNumber', numKey: 3522473789 -}; +} as SFFeedbackObj; const noErrorObj_date = { error: '', action: 'dateKeyPressed', fieldType: 'encryptedExpiryDate', numKey: 3522473789 -}; +} as SFFeedbackObj; beforeEach(() => { console.error = jest.fn(error => { diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/processErrors.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/processErrors.ts index 51bf946e44..06010dc245 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/processErrors.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/processErrors.ts @@ -24,7 +24,7 @@ export const processErrors = ( const isError: boolean = pFeedbackObj.error !== ''; // Error is empty string && field is not already in error: do nothing - don't need to propagate this non-error if the field wasn't already in error - // This situation arises when we encrypt a field and trigger an "error clearing" event + // This situation arises when we encrypt a field and trigger an "error clearing" event i.e. deleting a character // It also arises when an unsupportedCard (re. binLookup) is entered and the shopper continues to interact with the field (adding or deleting digits) if (!isError && !field.hasError) { return null; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioACH.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioACH.ts index 61b9f264d1..d7c8929870 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioACH.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioACH.ts @@ -1,10 +1,10 @@ import { ENCRYPTED_BANK_ACCNT_NUMBER_FIELD, ENCRYPTED_BANK_LOCATION_FIELD } from '../../../configuration/constants'; -import { ShiftTabObject } from '../../../types'; +import { SFFieldType, ShiftTabObject } from '../../../types'; // ACH scenario: bankAccountNumber SF followed by a bankLocationId SF -export function shiftTabACH(fieldType): ShiftTabObject { +export function shiftTabACH(fieldType: SFFieldType): ShiftTabObject { let additionalField: HTMLElement; - let fieldToFocus: string; + let fieldToFocus: SFFieldType; if (fieldType === ENCRYPTED_BANK_LOCATION_FIELD) { fieldToFocus = ENCRYPTED_BANK_ACCNT_NUMBER_FIELD; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioCreditCard.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioCreditCard.ts index 705fcec0c6..bc538e7aee 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioCreditCard.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioCreditCard.ts @@ -6,12 +6,17 @@ import { ENCRYPTED_EXPIRY_YEAR } from '../../../configuration/constants'; import { getPreviousTabbableNonSFElement } from './utils'; -import { ShiftTabObject } from '../../../types'; +import { SFFieldType, ShiftTabObject } from '../../../types'; // Regular Credit Card scenario -export function shiftTabCreditCard(fieldType: string, rootNode: HTMLElement, hasSeparateDateFields: boolean, numIframes: number): ShiftTabObject { +export function shiftTabCreditCard( + fieldType: SFFieldType, + rootNode: HTMLElement, + hasSeparateDateFields: boolean, + numIframes: number +): ShiftTabObject { let additionalField: HTMLElement; - let fieldToFocus: string; + let fieldToFocus: SFFieldType; switch (fieldType) { case ENCRYPTED_CARD_NUMBER: diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioGiftCard.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioGiftCard.ts index 1186f31ca8..cd63cbbb4d 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioGiftCard.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioGiftCard.ts @@ -1,11 +1,11 @@ import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_SECURITY_CODE } from '../../../configuration/constants'; import { getPreviousTabbableNonSFElement } from './utils'; -import { ShiftTabObject } from '../../../types'; +import { SFFieldType, ShiftTabObject } from '../../../types'; // GIFT CARD scenario: SecurityCode preceded by CardNumber -export function shiftTabGiftCard(fieldType: string, rootNode: HTMLElement): ShiftTabObject { +export function shiftTabGiftCard(fieldType: SFFieldType, rootNode: HTMLElement): ShiftTabObject { let additionalField: HTMLElement; - let fieldToFocus: string; + let fieldToFocus: SFFieldType; switch (fieldType) { case ENCRYPTED_CARD_NUMBER: diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioKCP.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioKCP.ts index c1ad48eaaf..3b02f12709 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioKCP.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/tabScenarioKCP.ts @@ -4,17 +4,16 @@ import { ENCRYPTED_EXPIRY_DATE, ENCRYPTED_EXPIRY_MONTH, ENCRYPTED_EXPIRY_YEAR, - ENCRYPTED_PWD_FIELD, - ENCRYPTED_PIN_FIELD + ENCRYPTED_PWD_FIELD } from '../../../configuration/constants'; import { getPreviousTabbableNonSFElement } from './utils'; -import { ShiftTabObject } from '../../../types'; +import { SFFieldType, ShiftTabObject } from '../../../types'; // KCP scenario: Regular credit card but with additional fields - // an encrypted pin/password field preceded by a form field of a non-SF type (d.o.b/taxRefNum) -export function shiftTabKCP(fieldType: string, rootNode: HTMLElement, hasSeparateDateFields: boolean): ShiftTabObject { +export function shiftTabKCP(fieldType: SFFieldType, rootNode: HTMLElement, hasSeparateDateFields: boolean): ShiftTabObject { let additionalField: HTMLElement; - let fieldToFocus: string; + let fieldToFocus: SFFieldType; switch (fieldType) { case ENCRYPTED_CARD_NUMBER: @@ -38,7 +37,6 @@ export function shiftTabKCP(fieldType: string, rootNode: HTMLElement, hasSeparat break; case ENCRYPTED_PWD_FIELD: - case ENCRYPTED_PIN_FIELD: additionalField = getPreviousTabbableNonSFElement(fieldType, rootNode); break; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/utils.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/utils.ts index 6242e26055..f5d3d8abb9 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/utils.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/tabbing/utils.ts @@ -45,3 +45,13 @@ export const getPreviousTabbableNonSFElement = (passedFieldType, rootNode, getPr const sfEl = selectOne(rootNode, `[data-cse=${passedFieldType}]`); return getPreviousTabbableEl(sfEl, getPrevious); }; + +export const focusExternalField = (pAdditionalField: HTMLElement): void => { + if (pAdditionalField) { + pAdditionalField.focus(); + + // Quirky! - Needed to work in the Components scenario + pAdditionalField.blur(); + pAdditionalField.focus(); + } +}; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/userAgent.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/userAgent.ts index ffaaccfc0f..3c5419d9f5 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/userAgent.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/utils/userAgent.ts @@ -24,11 +24,14 @@ function isIE() { return false; } -const __IS_ANDROID = typeof navigator !== 'undefined' && /(android)/i.test(navigator.userAgent); -const __IS_IE = typeof navigator !== 'undefined' && isIE(); -const __IS_IOS = typeof navigator !== 'undefined' && /iphone|ipod|ipad/i.test(navigator.userAgent); -const __IS_FIREFOX = typeof navigator !== 'undefined' && /(firefox)/i.test(navigator.userAgent); -const __IS_SAFARI = typeof navigator !== 'undefined' && /(safari)/i.test(navigator.userAgent) && !/(chrome)/i.test(navigator.userAgent); +// NOTE: change from const to let for the purposes of testing (the values get set in some tests) +/* eslint-disable */ +let __IS_ANDROID = typeof navigator !== 'undefined' && /(android)/i.test(navigator.userAgent); +let __IS_IE = typeof navigator !== 'undefined' && isIE(); +let __IS_IOS = typeof navigator !== 'undefined' && /iphone|ipod|ipad/i.test(navigator.userAgent); +let __IS_FIREFOX = typeof navigator !== 'undefined' && /(firefox)/i.test(navigator.userAgent); +let __IS_SAFARI = typeof navigator !== 'undefined' && /(safari)/i.test(navigator.userAgent) && !/(chrome)/i.test(navigator.userAgent); +/* eslint-enable */ export default { __IS_ANDROID, diff --git a/packages/lib/src/components/internal/SecuredFields/lib/configuration/constants.ts b/packages/lib/src/components/internal/SecuredFields/lib/configuration/constants.ts index 9f8ba92f08..f83edb6ec9 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/configuration/constants.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/configuration/constants.ts @@ -15,14 +15,13 @@ export const ENCRYPTED_SECURITY_CODE_4_DIGITS = 'encryptedSecurityCode4digits'; export const GIFT_CARD = 'giftcard'; -export const SF_VERSION = '4.7.0'; +export const SF_VERSION = '4.8.0'; export const DEFAULT_CARD_GROUP_TYPES = ['amex', 'mc', 'visa']; -// export const NON_CREDIT_CARD_TYPE_SECURED_FIELDS = ['ach', GIFT_CARD, 'sepa', 'sepadirectdebit']; //Maybe, sometime in the future -export const NON_CREDIT_CARD_TYPE_SECURED_FIELDS = ['ach', GIFT_CARD]; +export const NON_CREDIT_CARD_TYPE_SECURED_FIELDS = ['ach', GIFT_CARD]; // Maybe, sometime in the future will include 'sepa' & 'sepadirectdebit' -// Credit card (CardInput) related securedFields +// Credit card (CardInput) related securedFields (based on the data-cse attribute) export const CREDIT_CARD_SF_FIELDS = [ ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE, @@ -35,10 +34,9 @@ export const CREDIT_CARD_SF_FIELDS = [ export const OTHER_SF_FIELDS = [ENCRYPTED_BANK_ACCNT_NUMBER_FIELD, ENCRYPTED_BANK_LOCATION_FIELD]; // ACH fields +/** A list of all the data-cse attributes that relate to securedFields (as found in card, giftcard or ach) */ export const ALL_SECURED_FIELDS = CREDIT_CARD_SF_FIELDS.concat(OTHER_SF_FIELDS); -// export const ALL_RELATED_SECURED_FIELDS = ALL_SECURED_FIELDS.concat(NON_CREDIT_CARD_TYPE_SECURED_FIELDS); - // Card components created as: checkout.create({BRAND}) e.g. checkout.create('bcmc') // - which are dedicated to a single, core, brand e.g. 'bcmc' BUT which can in effect handle multiple brands e.g. "bcmc", "maestro", "visa" export const DEDICATED_CARD_COMPONENTS = ['bcmc']; @@ -88,3 +86,6 @@ export const SF_FIELDS_MAP = { [ENCRYPTED_SECURITY_CODE_3_DIGITS]: 'securityCodeThreeDigits', // ph [ENCRYPTED_SECURITY_CODE_4_DIGITS]: 'securityCodeFourDigits' // ph }; + +/** time within which we expect to receive a "configured" message from a securedField iframe, once it has registered as having loaded */ +export const SF_CONFIG_TIMEOUT = 6000; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/types.ts b/packages/lib/src/components/internal/SecuredFields/lib/types.ts index c470ff4a22..75050999b8 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/types.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/types.ts @@ -1,4 +1,5 @@ import { BrandObject } from '../../../Card/types'; +import SecuredField from './securedField/SecuredField'; // eslint-disable-next-line @typescript-eslint/no-unused-vars declare global { @@ -8,6 +9,27 @@ declare global { } } +export type SFFieldType = + | 'encryptedCardNumber' + | 'encryptedExpiryDate' + | 'encryptedExpiryMonth' + | 'encryptedExpiryYear' + | 'encryptedSecurityCode' + | 'encryptedPassword' + | 'encryptedBankAccountNumber' + | 'encryptedBankLocationId'; + +export interface SecuredFields { + encryptedCardNumber?: SecuredField; + encryptedExpiryDate?: SecuredField; + encryptedExpiryMonth?: SecuredField; + encryptedExpiryYear?: SecuredField; + encryptedSecurityCode?: SecuredField; + encryptedPassword?: SecuredField; + encryptedBankAccountNumber?: SecuredField; + encryptedBankLocationId?: SecuredField; +} + export interface BrandStorageObject { brand: string; cvcPolicy: CVCPolicyType; @@ -162,7 +184,7 @@ export interface CbObjOnAdditionalSF { export interface SFFeedbackObj { action: string; - fieldType: string; + fieldType: SFFieldType; numKey: number; brand?: string; code?: string; diff --git a/packages/lib/src/components/internal/UIElement/UIElement.test.ts b/packages/lib/src/components/internal/UIElement/UIElement.test.ts index 5ec1c42f7d..9bf3da1546 100644 --- a/packages/lib/src/components/internal/UIElement/UIElement.test.ts +++ b/packages/lib/src/components/internal/UIElement/UIElement.test.ts @@ -1,8 +1,9 @@ import UIElement from './UIElement'; import { ICore } from '../../../core/types'; -import { mockDeep, mockReset } from 'jest-mock-extended'; +import { mock, mockDeep, mockReset } from 'jest-mock-extended'; import { AdyenCheckout, ThreeDS2Challenge, ThreeDS2DeviceFingerprint } from '../../../index'; import { UIElementProps } from './types'; +import { Resources } from '../../../core/Context/Resources'; interface MyElementProps extends UIElementProps { challengeWindowSize?: string; @@ -33,6 +34,23 @@ beforeEach(() => { }); describe('UIElement', () => { + describe('icon()', () => { + test('should generate the icon URL by getting the tx variant from type() getter', () => { + const resources = mock(); + resources.getImage.mockReturnValue((icon: string) => `https://checkout-adyen.com/${icon}`); + + const txVariant = 'klarna_b2b'; + + const element = new MyElement({ core: core, type: txVariant, modules: { resources } }); + + const typeSpy = jest.spyOn(element, 'type', 'get'); + const iconUrl = element.icon; + + expect(typeSpy).toHaveBeenCalledTimes(1); + expect(iconUrl).toBe(`https://checkout-adyen.com/${txVariant}`); + }); + }); + describe('onComplete()', () => { test('should call "onComplete" prop if available', () => { const onCompleteCb = jest.fn(); diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx index 165af616c5..52908ea493 100644 --- a/packages/lib/src/components/internal/UIElement/UIElement.tsx +++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx @@ -291,8 +291,10 @@ export abstract class UIElement

exten /** * Get the element icon URL for the current environment */ + public get icon(): string { - return this.props.icon ?? this.resources.getImage()(this.constructor['type']); + const type = this.props.paymentMethodType || this.type; + return this.props.icon ?? this.resources.getImage()(type); } /** diff --git a/packages/lib/src/components/internal/UIElement/types.ts b/packages/lib/src/components/internal/UIElement/types.ts index daaf916816..ceeadf5657 100644 --- a/packages/lib/src/components/internal/UIElement/types.ts +++ b/packages/lib/src/components/internal/UIElement/types.ts @@ -83,6 +83,13 @@ export interface UIElementProps extends BaseElementProps { /** @internal */ elementRef?: any; + /** + * Returned after the payments call, when an action is returned. It represents the payment method tx variant + * that was used for the payment + * @internal + */ + paymentMethodType?: string; + /** @internal */ i18n?: Language; } diff --git a/packages/lib/src/core/CheckoutSession/CheckoutSession.ts b/packages/lib/src/core/CheckoutSession/CheckoutSession.ts index 6cc17a4b0c..4d694ac11b 100644 --- a/packages/lib/src/core/CheckoutSession/CheckoutSession.ts +++ b/packages/lib/src/core/CheckoutSession/CheckoutSession.ts @@ -71,6 +71,10 @@ class Session { this.configuration = { ...response.configuration }; } + if (response.sessionData) { + this.updateSessionData(response.sessionData); + } + return response; }); } diff --git a/packages/lib/src/core/Services/http.ts b/packages/lib/src/core/Services/http.ts index cc20861bab..fde4a325e3 100644 --- a/packages/lib/src/core/Services/http.ts +++ b/packages/lib/src/core/Services/http.ts @@ -1,4 +1,4 @@ -import { FALLBACK_CONTEXT } from '../config'; +import { DEFAULT_HTTP_TIMEOUT, FALLBACK_CONTEXT } from '../config'; import AdyenCheckoutError from '../Errors/AdyenCheckoutError'; interface HttpOptions { @@ -10,6 +10,7 @@ interface HttpOptions { method?: string; path: string; errorLevel?: ErrorLevel; + timeout?: number; } type ErrorLevel = 'silent' | 'info' | 'warn' | 'error' | 'fatal'; @@ -26,7 +27,7 @@ function isAdyenErrorResponse(data: any): data is AdyenErrorResponse { } export function http(options: HttpOptions, data?: any): Promise { - const { headers = [], errorLevel = 'warn', loadingContext = FALLBACK_CONTEXT, method = 'GET', path } = options; + const { headers = [], errorLevel = 'warn', loadingContext = FALLBACK_CONTEXT, method = 'GET', path, timeout = DEFAULT_HTTP_TIMEOUT } = options; const request: RequestInit = { method, @@ -40,6 +41,7 @@ export function http(options: HttpOptions, data?: any): Promise { }, redirect: 'follow', referrerPolicy: 'no-referrer-when-downgrade', + ...(AbortSignal?.timeout && { signal: AbortSignal?.timeout(timeout) }), ...(data && { body: JSON.stringify(data) }) }; diff --git a/packages/lib/src/core/Services/payment-status.ts b/packages/lib/src/core/Services/payment-status.ts index c89e3b04e8..4b577bb340 100644 --- a/packages/lib/src/core/Services/payment-status.ts +++ b/packages/lib/src/core/Services/payment-status.ts @@ -5,16 +5,18 @@ import { httpPost } from './http'; * @param paymentData - * @param clientKey - * @param loadingContext - + * @param timeout - in milliseconds * @returns a promise containing the response of the call */ -export default function checkPaymentStatus(paymentData, clientKey, loadingContext) { +export default function checkPaymentStatus(paymentData, clientKey, loadingContext, timeout) { if (!paymentData || !clientKey) { throw new Error('Could not check the payment status'); } const options = { loadingContext, - path: `services/PaymentInitiation/v1/status?clientKey=${clientKey}` + path: `services/PaymentInitiation/v1/status?clientKey=${clientKey}`, + timeout }; return httpPost(options, { paymentData }); diff --git a/packages/lib/src/core/config.ts b/packages/lib/src/core/config.ts index b51f719a5a..b5c3039a64 100644 --- a/packages/lib/src/core/config.ts +++ b/packages/lib/src/core/config.ts @@ -33,7 +33,10 @@ export const GENERIC_OPTIONS = [ 'setStatusAutomatically' ]; +export const DEFAULT_HTTP_TIMEOUT = 60000; + export default { FALLBACK_CONTEXT, - GENERIC_OPTIONS + GENERIC_OPTIONS, + DEFAULT_HTTP_TIMEOUT }; diff --git a/packages/lib/src/language/locales/ar.json b/packages/lib/src/language/locales/ar.json index b433bd43ca..ebd35ba2b2 100644 --- a/packages/lib/src/language/locales/ar.json +++ b/packages/lib/src/language/locales/ar.json @@ -2,8 +2,10 @@ "payButton": "دفع", "payButton.redirecting": "جارِ إعادة التوجيه...", "payButton.with": "ادفع %{value} باستخدام %{maskedData}", + "payButton.saveDetails": "حفظ التفاصيل", "close": "إغلاق", "storeDetails": "حفظ لمدفوعاتي القادمة", + "readMore": "اقرأ المزيد", "creditCard.holderName": "الاسم على البطاقة", "creditCard.holderName.invalid": "أدخل الاسم كما هو موضح على البطاقة", "creditCard.expiryDateField.month": "شهر", @@ -103,6 +105,7 @@ "donateButton": "التبرع", "notNowButton": "ليس الآن", "thanksForYourSupport": "شكرًا على دعمك!", + "resultMessages.preauthorized": "تم حفظ التفاصيل", "preauthorizeWith": "تفويض مسبق باستخدام", "confirmPreauthorization": "تأكيد التفويض المسبق", "confirmPurchase": "تأكيد الشراء", @@ -230,6 +233,7 @@ "ctp.otp.subtitle": "أدخل الرمز %@ الذي أرسلناه إلى ٪@ للتحقق من هويتك.", "ctp.otp.saveCookiesCheckbox.label": "تخطي التحقق في المرة القادمة", "ctp.otp.saveCookiesCheckbox.information": "حدد حفظ البيانات على جهازك ومتصفحك في المتاجر المشاركة لإتمام عملية الدفع بشكل أسرع. لا يوصى به للأجهزة المشتركة.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "حدد تذكرني على جهازك ومتصفحك.", "ctp.emptyProfile.message": "لا توجد بطاقات مسجلة في هذا الملف التعريفي على Click to Pay", "ctp.separatorText": "أو استخدم", "ctp.cards.title": "أكمل الدفع باستخدام بطاقة Click to Pay", @@ -287,5 +291,11 @@ "ach.bankAccountNumber.label": "رقم الحساب", "ach.bankLocationId.label": "رقم توجيه ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "حدد مقدم الخدمة", - "dragonpayVoucher.selectField.contextualText.bank": "حدد البنك الذي تتعامل معه" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "حدد البنك الذي تتعامل معه", + "payme.openPayMeApp": "أكمل الدفع في تطبيق PayMe من خلال الإذن بالدفع في التطبيق وانتظر التأكيد.", + "payme.redirectButtonLabel": "افتح تطبيق PayMe", + "payme.scanQrCode": "أكمل الدفع باستخدام رمز الاستجابة السريعة", + "payme.timeToPay": "رمز الاستجابة السريعة هذا صالح لـ %@", + "payme.instructions.steps": "افتح تطبيق PayMe. %@امسح رمز الاستجابة السريعة ضوئيًا للإذن بالدفع. %@أكمل عملية الدفع في التطبيق وانتظر التأكيد.", + "payme.instructions.footnote": "يرجى عدم إغلاق هذه الصفحة قبل إتمام الدفع" +} diff --git a/packages/lib/src/language/locales/cs-CZ.json b/packages/lib/src/language/locales/cs-CZ.json index 23ff695d13..1262b19ed7 100644 --- a/packages/lib/src/language/locales/cs-CZ.json +++ b/packages/lib/src/language/locales/cs-CZ.json @@ -2,8 +2,10 @@ "payButton": "Zaplatit", "payButton.redirecting": "Přesměrování...", "payButton.with": "Zaplatit %{value} pomocí %{maskedData}", + "payButton.saveDetails": "Uložit podrobnosti", "close": "Zavřít", "storeDetails": "Uložit pro příští platby", + "readMore": "Přečtěte si více", "creditCard.holderName": "Jméno na kartě", "creditCard.holderName.invalid": "Zadejte jméno, jak je uvedeno na kartě", "creditCard.expiryDateField.month": "Měsíc", @@ -103,6 +105,7 @@ "donateButton": "Přispět", "notNowButton": "Teď ne", "thanksForYourSupport": "Děkujeme vám za podporu!", + "resultMessages.preauthorized": "Uložené podrobnosti", "preauthorizeWith": "Předautorizovat pomocí", "confirmPreauthorization": "Potvrdit předautorizaci", "confirmPurchase": "Potvrdit nákup", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Zadejte kód %@, který jsme vám odeslali na %@ a ověřte sami sebe.", "ctp.otp.saveCookiesCheckbox.label": "Příště přeskočte ověřování", "ctp.otp.saveCookiesCheckbox.information": "Zvolte, že si je chcete zapamatovat ve svém zařízení a prohlížeči v zúčastněných obchodech, abyste se rychleji odhlásili. Nedoporučuje se pro sdílená zařízení.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Vyberte, abyste si je zapamatovali ve svém zařízení a prohlížeči", "ctp.emptyProfile.message": "V tomto profilu Click to Pay nejsou zaregistrovány žádné karty", "ctp.separatorText": "nebo použijte", "ctp.cards.title": "Dokončete platbu pomocí Click to Pay", @@ -283,5 +287,11 @@ "ach.bankAccountNumber.label": "Číslo účtu", "ach.bankLocationId.label": "Směrovací tranzitní číslo ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Vyberte svého poskytovatele", - "dragonpayVoucher.selectField.contextualText.bank": "Vyberte svou banku" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Vyberte svou banku", + "payme.openPayMeApp": "Dokončete platbu autorizací v aplikaci PayMe a počkejte na potvrzení.", + "payme.redirectButtonLabel": "Otevřete aplikaci PayMe", + "payme.scanQrCode": "Dokončete platbu pomocí QR kódu", + "payme.timeToPay": "Tento QR kód je platný pro %@", + "payme.instructions.steps": "Otevřete aplikaci PayMe.%@Autorizujte platbu naskenováním QR kódu.%@Dokončete platbu v aplikaci a počkejte na potvrzení.", + "payme.instructions.footnote": "Nezavírejte prosím tuto stránku před dokončením platby." +} diff --git a/packages/lib/src/language/locales/da-DK.json b/packages/lib/src/language/locales/da-DK.json index 31897ec876..21da617192 100644 --- a/packages/lib/src/language/locales/da-DK.json +++ b/packages/lib/src/language/locales/da-DK.json @@ -2,8 +2,10 @@ "payButton": "Betal", "payButton.redirecting": "Omdirigerer...", "payButton.with": "Betal %{value} med %{maskedData}", + "payButton.saveDetails": "Gem oplysninger", "close": "Luk", "storeDetails": "Gem til min næste betaling", + "readMore": "Læs mere", "creditCard.holderName": "Navn på kort", "creditCard.holderName.invalid": "Indtast navn som vist på kortet", "creditCard.expiryDateField.month": "Måned", @@ -104,6 +106,7 @@ "donateButton": "Giv et bidrag", "notNowButton": "Ikke nu", "thanksForYourSupport": "Tak for din støtte!", + "resultMessages.preauthorized": "Oplysningerne er gemt", "preauthorizeWith": "Forhåndsgodkend med", "confirmPreauthorization": "Bekræft forhåndsgodkendelse", "confirmPurchase": "Bekræft køb", @@ -230,6 +233,7 @@ "ctp.otp.subtitle": "Indtast den kode, vi har sendt til %@ for at bekræfte, at det er dig.", "ctp.otp.saveCookiesCheckbox.label": "Spring bekræftelse over næste gang", "ctp.otp.saveCookiesCheckbox.information": "Vælg dette for at blive husket på din enhed og browser i deltagende butikker for hurtigere betaling. Anbefales ikke på delte enheder.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Vælg dette for at blive husket på din enhed og browser", "ctp.emptyProfile.message": "Ingen kort registreret i denne Click to Pay-profil", "ctp.separatorText": "eller brug", "ctp.cards.title": "Gennemfør betaling med Click to Pay", @@ -284,5 +288,11 @@ "ach.bankAccountNumber.label": "Kontonummer", "ach.bankLocationId.label": "ABA-registreringsnummer", "dragonpayVoucher.selectField.contextualText.nonBank": "Vælg din udbyder", - "dragonpayVoucher.selectField.contextualText.bank": "Vælg din bank" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Vælg din bank", + "payme.openPayMeApp": "Gennemfør betalingen i PayMe-appen ved at godkende betalingen i appen og afvente bekræftelsen.", + "payme.redirectButtonLabel": "Åbn PayMe-appen", + "payme.scanQrCode": "Gennemfør din betaling med QR-kode", + "payme.timeToPay": "Denne QR-kode er gyldig i %@", + "payme.instructions.steps": "Åbn PayMe-appen.%@Scan QR-koden for at godkende betalingen.%@Gennemfør betalingen i appen, og afvent bekræftelse.", + "payme.instructions.footnote": "Luk ikke denne side, før betalingen er gennemført" +} diff --git a/packages/lib/src/language/locales/de-DE.json b/packages/lib/src/language/locales/de-DE.json index 6462d3ab70..47ccd02d97 100644 --- a/packages/lib/src/language/locales/de-DE.json +++ b/packages/lib/src/language/locales/de-DE.json @@ -1,9 +1,11 @@ { - "payButton": "Zahlen", + "payButton": "Zahle", "payButton.redirecting": "Umleiten…", "payButton.with": "%{value} mit %{maskedData} zahlen", + "payButton.saveDetails": "Angaben speichern", "close": "Schließen", "storeDetails": "Für zukünftige Zahlvorgänge speichern", + "readMore": "Mehr lesen", "creditCard.holderName": "Name auf der Karte", "creditCard.holderName.invalid": "Geben Sie den Namen wie auf der Karte gezeigt ein", "creditCard.expiryDateField.month": "Monat", @@ -103,6 +105,7 @@ "donateButton": "Spenden", "notNowButton": "Nicht jetzt", "thanksForYourSupport": "Danke für Ihre Unterstützung!", + "resultMessages.preauthorized": "Angaben gespeichert", "preauthorizeWith": "Vorautorisieren mit", "confirmPreauthorization": "Vorautorisierung bestätigen", "confirmPurchase": "Kauf bestätigen", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Geben Sie den Code ein, der von %@ an %@ gesendet wurde, um Ihre Identität zu bestätigen.", "ctp.otp.saveCookiesCheckbox.label": "Verifizierung beim nächsten Mal überspringen", "ctp.otp.saveCookiesCheckbox.information": "Wählen Sie diese Option, um bei teilnehmenden Geschäften auf Ihrem Gerät und Browser gespeichert zu werden, um den Bestellvorgang zu beschleunigen. Nicht für gemeinsam genutzte Geräte empfohlen.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Wählen Sie diese Option aus, um auf Ihrem Gerät und Browser gespeichert zu werden", "ctp.emptyProfile.message": "In diesem Click-to-Pay-Profil sind keine Karten registriert", "ctp.separatorText": "oder verwenden", "ctp.cards.title": "Zahlung mit Click to Pay abschließen", @@ -283,5 +287,11 @@ "ach.bankAccountNumber.label": "Kontonummer", "ach.bankLocationId.label": "ABA-Nummer", "dragonpayVoucher.selectField.contextualText.nonBank": "Anbieter auswählen", - "dragonpayVoucher.selectField.contextualText.bank": "Bank auswählen" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Bank auswählen", + "payme.openPayMeApp": "Schließen Sie Ihre Zahlung in der PayMe-App ab, indem Sie die Zahlung in der App autorisieren und auf die Bestätigung warten.", + "payme.redirectButtonLabel": "Öffnen Sie die PayMe-App", + "payme.scanQrCode": "Schließen Sie Ihre Zahlung per QR-Code ab", + "payme.timeToPay": "Dieser QR-Code gilt für %@", + "payme.instructions.steps": "Öffnen Sie die PayMe-App.%@Scannen Sie den QR-Code, um die Zahlung zu autorisieren.%@Schließen Sie die Zahlung in der App ab und warten Sie auf eine Bestätigung.", + "payme.instructions.footnote": "Bitte schließen Sie diese Seite nicht, bevor die Zahlung abgeschlossen ist" +} diff --git a/packages/lib/src/language/locales/el-GR.json b/packages/lib/src/language/locales/el-GR.json index 61a3b3fc6d..565e2edb64 100644 --- a/packages/lib/src/language/locales/el-GR.json +++ b/packages/lib/src/language/locales/el-GR.json @@ -2,8 +2,10 @@ "payButton": "Πληρωμή", "payButton.redirecting": "Ανακατεύθυνση...", "payButton.with": "Πληρωμή %{value} με %{maskedData}", + "payButton.saveDetails": "Αποθήκευση στοιχείων", "close": "Κλείσιμο", "storeDetails": "Αποθήκευση για την επόμενη πληρωμή μου", + "readMore": "Ανάγνωση περισσότερων", "creditCard.holderName": "Όνομα στην κάρτα", "creditCard.holderName.invalid": "Εισαγάγετε το όνομα όπως αναγράφεται στην κάρτα", "creditCard.expiryDateField.month": "Μήνας", @@ -103,6 +105,7 @@ "donateButton": "Δωρεά", "notNowButton": "Όχι τώρα", "thanksForYourSupport": "Σας ευχαριστούμε για την υποστήριξη!", + "resultMessages.preauthorized": "Τα στοιχεία αποθηκεύτηκαν", "preauthorizeWith": "Προεξουσιοδότηση με", "confirmPreauthorization": "Επιβεβαίωση προεξουσιοδότησης", "confirmPurchase": "Επιβεβαίωση αγοράς", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Εισαγάγετε τον κωδικό %@ που στάλθηκε στο %@ για να επαληθεύσετε ότι είστε εσείς.", "ctp.otp.saveCookiesCheckbox.label": "Παράβλεψη επαλήθευσης την επόμενη φορά", "ctp.otp.saveCookiesCheckbox.information": "Επιλέξτε να απομνημονεύεται στη συσκευή και στο πρόγραμμα περιήγησής σας στα συμμετέχοντα καταστήματα για ταχύτερη ολοκλήρωση της πληρωμής. Δεν συνιστάται για κοινόχρηστες συσκευές.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Επιλέξτε να απομνημονεύεται στη συσκευή και στο πρόγραμμα περιήγησής σας", "ctp.emptyProfile.message": "Δεν υπάρχουν καταχωρισμένες κάρτες σε αυτό το προφίλ Click to Pay", "ctp.separatorText": "ή χρησιμοποιήστε το", "ctp.cards.title": "Ολοκληρώστε την πληρωμή με το Click to Pay", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "Αριθμός λογαριασμού", "ach.bankLocationId.label": "Αριθμός δρομολόγησης ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Επιλέξτε τον πάροχό σας", - "dragonpayVoucher.selectField.contextualText.bank": "Επιλέξτε την τράπεζά σας" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Επιλέξτε την τράπεζά σας", + "payme.openPayMeApp": "Ολοκληρώστε την πληρωμή σας στην εφαρμογή PayMe εξουσιοδοτώντας την πληρωμή στην εφαρμογή και περιμένετε την επιβεβαίωση.", + "payme.redirectButtonLabel": "Ανοίξτε την εφαρμογή PayMe", + "payme.scanQrCode": "Ολοκληρώστε την πληρωμή σας με κωδικό QR", + "payme.timeToPay": "Αυτός ο κωδικός QR ισχύει για %@", + "payme.instructions.steps": "Ανοίξτε την εφαρμογή PayMe.%@Σκανάρετε τον κωδικό QR για να εξουσιοδοτήσετε την πληρωμή.%@Ολοκληρώστε την πληρωμή στην εφαρμογή και περιμένετε την επιβεβαίωση.", + "payme.instructions.footnote": "Μην κλείσετε αυτήν τη σελίδα προτού ολοκληρωθεί η πληρωμή." +} diff --git a/packages/lib/src/language/locales/en-US.json b/packages/lib/src/language/locales/en-US.json index 48ae7c4dc2..4c273ff411 100644 --- a/packages/lib/src/language/locales/en-US.json +++ b/packages/lib/src/language/locales/en-US.json @@ -2,8 +2,10 @@ "payButton": "Pay", "payButton.redirecting": "Redirecting...", "payButton.with": "Pay %{value} with %{maskedData}", + "payButton.saveDetails": "Save details", "close": "Close", "storeDetails": "Save for my next payment", + "readMore": "Read more", "creditCard.holderName": "Name on card", "creditCard.holderName.invalid": "Enter name as shown on card", "creditCard.expiryDateField.month": "Month", @@ -104,6 +106,7 @@ "donateButton": "Donate", "notNowButton": "Not now", "thanksForYourSupport": "Thanks for your support!", + "resultMessages.preauthorized": "Details saved", "preauthorizeWith": "Preauthorize with", "confirmPreauthorization": "Confirm preauthorization", "confirmPurchase": "Confirm purchase", @@ -231,6 +234,7 @@ "ctp.otp.subtitle": "Enter the code %@ sent to %@ to verify it‘s you.", "ctp.otp.saveCookiesCheckbox.label": "Skip verification next time", "ctp.otp.saveCookiesCheckbox.information": "Select to be remembered on your device and browser at participating stores for faster checkout. Not recommended for shared devices.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Select to be remembered on your device and browser", "ctp.emptyProfile.message": "No cards registered in this Click to Pay profile", "ctp.separatorText": "or use", "ctp.cards.title": "Complete payment with Click to Pay", @@ -288,5 +292,11 @@ "ach.bankAccountNumber.label": "Account number", "ach.bankLocationId.label": "ABA routing number", "dragonpayVoucher.selectField.contextualText.nonBank": "Select your provider", - "dragonpayVoucher.selectField.contextualText.bank": "Select your bank" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Select your bank", + "payme.openPayMeApp": "Complete your payment in the PayMe app by authorizing the payment in the app and wait for the confirmation.", + "payme.redirectButtonLabel": "Open PayMe app", + "payme.scanQrCode": "Complete your payment by QR code", + "payme.timeToPay": "This QR code is valid for %@", + "payme.instructions.steps": "Open the PayMe app.%@Scan the QR code to authorize the payment.%@Complete the payment in the app and wait for confirmation.", + "payme.instructions.footnote": "Please do not close this page before the payment is completed" +} diff --git a/packages/lib/src/language/locales/es-ES.json b/packages/lib/src/language/locales/es-ES.json index 9017618654..1df0b5b602 100644 --- a/packages/lib/src/language/locales/es-ES.json +++ b/packages/lib/src/language/locales/es-ES.json @@ -2,8 +2,10 @@ "payButton": "Pagar", "payButton.redirecting": "Redirigiendo...", "payButton.with": "Pague %{value} con %{maskedData}", + "payButton.saveDetails": "Guardar los detalles", "close": "Cerrar", "storeDetails": "Recordar para mi próximo pago", + "readMore": "Leer más", "creditCard.holderName": "Nombre en la tarjeta", "creditCard.holderName.invalid": "Introduzca el nombre como se muestra en la tarjeta", "creditCard.expiryDateField.month": "Mes", @@ -103,6 +105,7 @@ "donateButton": "Donar", "notNowButton": "Ahora no", "thanksForYourSupport": "¡Gracias por su contribución!", + "resultMessages.preauthorized": "Se han guardado los detalles", "preauthorizeWith": "Preautorizar con", "confirmPreauthorization": "Confirmar preautorización", "confirmPurchase": "Confirmar compra", @@ -227,6 +230,7 @@ "ctp.otp.subtitle": "Introduzca el código %@ que le hemos enviado a %@ para verificar que es usted.", "ctp.otp.saveCookiesCheckbox.label": "Omitir verificación la próxima vez", "ctp.otp.saveCookiesCheckbox.information": "Seleccione esta opción para recordarle en su dispositivo y navegador en las tiendas participantes para agilizar el proceso de pago. No lo recomendamos para dispositivos compartidos.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Seleccione esta opción para recordarle en su dispositivo y navegador", "ctp.emptyProfile.message": "No hay tarjetas registradas en este perfil de Click to Pay", "ctp.separatorText": "o utilice", "ctp.cards.title": "Completar el pago con Click to Pay", @@ -281,5 +285,11 @@ "ach.bankAccountNumber.label": "Número de cuenta", "ach.bankLocationId.label": "Número de ruta ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Seleccione su proveedor", - "dragonpayVoucher.selectField.contextualText.bank": "Seleccione su banco" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Seleccione su banco", + "payme.openPayMeApp": "Completa tu pago en la aplicación PayMe autorizando el pago en la aplicación y espera por la confirmación.", + "payme.redirectButtonLabel": "Abrir aplicación PayMe", + "payme.scanQrCode": "Completa tu pago con código QR", + "payme.timeToPay": "Este código QR es válido para %@", + "payme.instructions.steps": "Abre la aplicación PayMe.%@Escanea el código QR para autorizar el pago.%@Completa el pago en la aplicación y espera por la confirmación.", + "payme.instructions.footnote": "No cierres esta página antes de que se complete el pago" +} diff --git a/packages/lib/src/language/locales/fi-FI.json b/packages/lib/src/language/locales/fi-FI.json index 8273590ac4..60309801f7 100644 --- a/packages/lib/src/language/locales/fi-FI.json +++ b/packages/lib/src/language/locales/fi-FI.json @@ -2,8 +2,10 @@ "payButton": "Maksa", "payButton.redirecting": "Uudelleenohjataan...", "payButton.with": "Maksa %{value} käyttäen maksutapaa %{maskedData}", + "payButton.saveDetails": "Tallenna tiedot", "close": "Sulje", "storeDetails": "Tallenna seuraavaa maksuani varten", + "readMore": "Lue lisää", "creditCard.holderName": "Nimi kortilla", "creditCard.holderName.invalid": "Syötä nimi sellaisena kuin se on kortissa", "creditCard.expiryDateField.month": "Kuukausi", @@ -103,6 +105,7 @@ "donateButton": "Lahjoita", "notNowButton": "Ei nyt", "thanksForYourSupport": "Kiitos tuestasi!", + "resultMessages.preauthorized": "Tiedot tallennettu", "preauthorizeWith": "Ennkkolupa käyttäjän kanssa", "confirmPreauthorization": "Vahvista ennakkolupa", "confirmPurchase": "Vahvista hankinta", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Syötä koodi, jonka %@ lähetti osoitteeseen %@ vahvistaaksesi, että kyseessä olet sinä.", "ctp.otp.saveCookiesCheckbox.label": "Ohita vahvistus seuraavalla kerralla", "ctp.otp.saveCookiesCheckbox.information": "Valitse, että sinut muistetaan laitteellasi ja selaimessa osallistuvissa myymälöissä, jotta voit maksaa nopeammin. Ei suositella jaetuilla laitteilla.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Valitse, jotta sinut muistetaan laitteellasi ja selaimessa", "ctp.emptyProfile.message": "Tähän Click to Pay -profiiliin ei ole rekisteröity kortteja", "ctp.separatorText": "tai käytä", "ctp.cards.title": "Suorita maksu Click to Paylla", @@ -283,5 +287,11 @@ "ach.bankAccountNumber.label": "Tilinumero", "ach.bankLocationId.label": "ABA-reititysnumero", "dragonpayVoucher.selectField.contextualText.nonBank": "Valitse toimittajasi", - "dragonpayVoucher.selectField.contextualText.bank": "Valitse pankkisi" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Valitse pankkisi", + "payme.openPayMeApp": "Viimeistele maksu PayMe-sovelluksessa hyväksymällä maksu sovelluksessa, ja odota vahvistusta.", + "payme.redirectButtonLabel": "Avaa PayMe-sovellus", + "payme.scanQrCode": "Viimeistele maksusi QR-koodilla", + "payme.timeToPay": "Tämä QR-koodi on voimassa %@", + "payme.instructions.steps": "Avaa PayMe-sovellus.%@Hyväksy maksu skannaamalla QR-koodi.%@Viimeistele maksu sovelluksessa, ja odota vahvistusta.", + "payme.instructions.footnote": "Älä sulje tätä sivua ennen kuin maksu on suoritettu" +} diff --git a/packages/lib/src/language/locales/fr-FR.json b/packages/lib/src/language/locales/fr-FR.json index 34ca05398e..5238dc5bea 100644 --- a/packages/lib/src/language/locales/fr-FR.json +++ b/packages/lib/src/language/locales/fr-FR.json @@ -2,8 +2,10 @@ "payButton": "Payer", "payButton.redirecting": "Redirection...", "payButton.with": "Payer %{value} avec %{maskedData}", + "payButton.saveDetails": "Enregistrer les détails", "close": "Fermer", "storeDetails": "Sauvegarder pour mon prochain paiement", + "readMore": "Lire la suite", "creditCard.holderName": "Nom sur la carte", "creditCard.holderName.invalid": "Entrez le nom tel qu'il apparaît sur la carte", "creditCard.expiryDateField.month": "Mois", @@ -103,6 +105,7 @@ "donateButton": "Faire un don", "notNowButton": "Pas maintenant", "thanksForYourSupport": "Merci de votre soutien !", + "resultMessages.preauthorized": "Détails enregistrés", "preauthorizeWith": "Pré-autoriser avec", "confirmPreauthorization": "Confirmer la pré-autorisation", "confirmPurchase": "Confirmer l'achat", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Saisissez le code %@ envoyé à %@ pour vérifier votre identité.", "ctp.otp.saveCookiesCheckbox.label": "Ignorer la vérification la prochaine fois", "ctp.otp.saveCookiesCheckbox.information": "Sélectionnez cette option pour mémoriser les informations sur votre appareil et votre navigateur afin d'accélérer le paiement dans les magasins participants. Non recommandé pour les appareils partagés.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Sélectionner cette option pour mémoriser les informations sur votre appareil et votre navigateur", "ctp.emptyProfile.message": "Aucune carte enregistrée dans ce profil Click to Pay", "ctp.separatorText": "ou utilisez", "ctp.cards.title": "Terminez le paiement avec Click to Pay", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "Numéro du compte", "ach.bankLocationId.label": "Code ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Sélectionnez votre fournisseur", - "dragonpayVoucher.selectField.contextualText.bank": "Sélectionnez votre banque" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Sélectionnez votre banque", + "payme.openPayMeApp": "Finalisez votre paiement dans l'application PayMe en autorisant le paiement dans l'application, puis attendez la confirmation.", + "payme.redirectButtonLabel": "Ouvrir l'application PayMe", + "payme.scanQrCode": "Effectuez votre paiement avec un code QR", + "payme.timeToPay": "Ce code QR est valide pendant %@", + "payme.instructions.steps": "Ouvrez l'application PayMe.%@Scannez le code QR pour autoriser le paiement.%@Effectuez le paiement dans l'application et attendez la confirmation.", + "payme.instructions.footnote": "Veuillez ne pas fermer cette page avant que le paiement ne soit terminé." +} diff --git a/packages/lib/src/language/locales/hr-HR.json b/packages/lib/src/language/locales/hr-HR.json index 5186ae4953..5db7f1dd4b 100644 --- a/packages/lib/src/language/locales/hr-HR.json +++ b/packages/lib/src/language/locales/hr-HR.json @@ -2,8 +2,10 @@ "payButton": "Platiti", "payButton.redirecting": "Preusmjeravanje...", "payButton.with": "Platite iznos od %{value} uporabom stavke %{maskedData}", + "payButton.saveDetails": "Spremi pojedinosti", "close": "Zatvori", "storeDetails": "Pohrani za moje sljedeće plaćanje", + "readMore": "Opširnije", "creditCard.holderName": "Ime na kartici", "creditCard.holderName.invalid": "Unesite ime kako je prikazano na kartici", "creditCard.expiryDateField.month": "Mjesec", @@ -103,6 +105,7 @@ "donateButton": "Doniraj", "notNowButton": "Ne sada", "thanksForYourSupport": "Hvala na podršci!", + "resultMessages.preauthorized": "Spremljeni podatci", "preauthorizeWith": "Prethodno odobri s", "confirmPreauthorization": "Potvrdite prethodno odobrenje", "confirmPurchase": "Potvrdite kupnju", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Upišite kôd %@ koji smo poslali na %@ da bismo potvrdili da ste to vi.", "ctp.otp.saveCookiesCheckbox.label": "Sljedeći puta preskoči provjeru", "ctp.otp.saveCookiesCheckbox.information": "Odaberite kako biste bili zapamćeni na svom uređaju i pregledniku u uključenim trgovinama za bržu naplatu. Ne preporučuje se za zajedničke uređaje.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Odaberite ako želite da vas se zapamti na vašem uređaju i pregledniku", "ctp.emptyProfile.message": "Nema kartica registriranih u sklopu ovog profila usluge Click to Pay", "ctp.separatorText": "ili upotrijebite", "ctp.cards.title": "Dovrši plaćanje uslugom Click to Pay", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "Broj računa", "ach.bankLocationId.label": "ABA identifikacijski broj", "dragonpayVoucher.selectField.contextualText.nonBank": "Odaberite davatelja usluge", - "dragonpayVoucher.selectField.contextualText.bank": "Odaberite banku" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Odaberite banku", + "payme.openPayMeApp": "Dovršite plaćanje u aplikaciji PayMe: autorizirajte plaćanje u aplikaciji i pričekajte potvrdu.", + "payme.redirectButtonLabel": "Otvaranje aplikacije PayMe", + "payme.scanQrCode": "Dovršite svoje plaćanja QR kodom", + "payme.timeToPay": "Ovaj QR kôd vrijedi za %@", + "payme.instructions.steps": "Otvorite aplikaciju PayMe.%@Skenirajte QR kod za autorizaciju plaćanja.%@Dovršite plaćanje u aplikaciji i pričekajte potvrdu.", + "payme.instructions.footnote": "Ne zatvarajte ovu stranicu prije nego što se plaćanje završi" +} diff --git a/packages/lib/src/language/locales/hu-HU.json b/packages/lib/src/language/locales/hu-HU.json index 63d9d39fa2..a13268116e 100644 --- a/packages/lib/src/language/locales/hu-HU.json +++ b/packages/lib/src/language/locales/hu-HU.json @@ -2,8 +2,10 @@ "payButton": "Fizetés", "payButton.redirecting": "Átirányítás...", "payButton.with": "%{value} fizetése a következővel: %{maskedData}", + "payButton.saveDetails": "Részletek mentése", "close": "Bezárás", "storeDetails": "Mentés a következő fizetéshez", + "readMore": "Bővebben", "creditCard.holderName": "A kártyán szereplő név", "creditCard.holderName.invalid": "Adja meg a kártyán szereplő nevet", "creditCard.expiryDateField.month": "Hónap", @@ -103,6 +105,7 @@ "donateButton": "Adományozás", "notNowButton": "Most nem", "thanksForYourSupport": "Köszönjük a támogatását!", + "resultMessages.preauthorized": "Részletek mentve", "preauthorizeWith": "Előzetes meghatalmazás a következővel:", "confirmPreauthorization": "Előzetes meghatalmazás jóváhagyása", "confirmPurchase": "Fizetés jóváhagyása", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Írja be a kódot, amit a(z) %@ küldött %@ részére ellenőrzés céljából.", "ctp.otp.saveCookiesCheckbox.label": "Ellenőrzés kihagyása a következő alkalommal", "ctp.otp.saveCookiesCheckbox.information": "Jelölje be, hogy a résztvevő üzletek a gyorsabb fizetés érdekében emlékezzenek az eszközre és a böngészőre. Megosztott eszközök esetén nem ajánlott.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Jelölje be, hogy a rendszer emlékezzen az eszközre és a böngészőre", "ctp.emptyProfile.message": "Ebben a Click to Pay-profilban nincs regisztrált kártya", "ctp.separatorText": "vagy használja a következőt", "ctp.cards.title": "Fizetés Click to Pay használatával", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "Számlaszám", "ach.bankLocationId.label": "ABA-irányítószám", "dragonpayVoucher.selectField.contextualText.nonBank": "Szolgáltató kiválasztása", - "dragonpayVoucher.selectField.contextualText.bank": "Bank kiválasztása" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Bank kiválasztása", + "payme.openPayMeApp": "A fizetésnek a PayMe alkalmazásban való engedélyezésével hajtsa végre a fizetést, és várja meg a visszaigazolást.", + "payme.redirectButtonLabel": "PayMe alkalmazás megnyitása", + "payme.scanQrCode": "Fizetés végrehajtása QR-kóddal", + "payme.timeToPay": "A QR-kód ennyi ideig érvényes: %@", + "payme.instructions.steps": "Nyissa meg a PayMe alkalmazást.%@A fizetés engedélyezéséhez olvassa be a QR-kódot.%@Hajtsa végre a fizetést az alkalmazásban, és várja meg a visszaigazolást.", + "payme.instructions.footnote": "A fizetés befejezése előtt ne zárja be ezt az oldalt" +} diff --git a/packages/lib/src/language/locales/it-IT.json b/packages/lib/src/language/locales/it-IT.json index 3cde60c8a4..da9f3718eb 100644 --- a/packages/lib/src/language/locales/it-IT.json +++ b/packages/lib/src/language/locales/it-IT.json @@ -2,8 +2,10 @@ "payButton": "Paga", "payButton.redirecting": "Reindirizzamento...", "payButton.with": "Paga %{value} con %{maskedData}", + "payButton.saveDetails": "Salva dettagli", "close": "Chiudi", "storeDetails": "Salva per il prossimo pagamento", + "readMore": "Leggi di più", "creditCard.holderName": "Titolare carta", "creditCard.holderName.invalid": "Immetti il nome riportato sulla carta", "creditCard.expiryDateField.month": "Mese", @@ -103,6 +105,7 @@ "donateButton": "Dona", "notNowButton": "Non ora", "thanksForYourSupport": "Grazie per il tuo sostegno!", + "resultMessages.preauthorized": "Dettagli salvati", "preauthorizeWith": "Preautorizza con", "confirmPreauthorization": "Conferma preautorizzazione", "confirmPurchase": "Conferma acquisto", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Inserisci il codice che %@ ha inviato a %@ per verificare la tua identità.", "ctp.otp.saveCookiesCheckbox.label": "Salta la verifica la prossima volta", "ctp.otp.saveCookiesCheckbox.information": "Scegli di essere ricordato sul dispositivo e il browser che utilizzi per velocizzare il pagamento nei negozi partecipanti. Non consigliato per dispositivi condivisi.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Seleziona per essere ricordato sul tuo dispositivo e sul browser", "ctp.emptyProfile.message": "Non esistono carte registrate in questo profilo Click to Pay", "ctp.separatorText": "o utilizza", "ctp.cards.title": "Completa il pagamento con Click to Pay", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "Numero di conto", "ach.bankLocationId.label": "Codice ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Seleziona il tuo fornitore", - "dragonpayVoucher.selectField.contextualText.bank": "Seleziona la banca" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Seleziona la banca", + "payme.openPayMeApp": "Completa il pagamento nell'app PayMe concedendo l'autorizzazione e attendendo il messaggio di conferma.", + "payme.redirectButtonLabel": "Apri l'app PayMe", + "payme.scanQrCode": "Completa il pagamento con il codice QR", + "payme.timeToPay": "Questo codice QR è valido per %@", + "payme.instructions.steps": "Apri l'app PayMe.%@Scansiona il codice QR per autorizzare il pagamento.%@Completa il pagamento nell'app e attendi la conferma.", + "payme.instructions.footnote": "Rimani su questa pagina fino a quando il pagamento sarà completato" +} diff --git a/packages/lib/src/language/locales/ja-JP.json b/packages/lib/src/language/locales/ja-JP.json index 981a3f405a..7037900e11 100644 --- a/packages/lib/src/language/locales/ja-JP.json +++ b/packages/lib/src/language/locales/ja-JP.json @@ -2,8 +2,10 @@ "payButton": "支払う", "payButton.redirecting": "リダイレクトしています...", "payButton.with": "%{value}を%{maskedData}で支払う", + "payButton.saveDetails": "詳細を保存", "close": "終了", "storeDetails": "次回のお支払いのため詳細を保存", + "readMore": "詳細を確認", "creditCard.holderName": "カード上の名前", "creditCard.holderName.invalid": "カードに記載されている名前を入力してください", "creditCard.expiryDateField.month": "月", @@ -83,13 +85,15 @@ "voucher.introduction": "お買い上げありがとうございます。以下のクーポンを使用して、お支払いを完了してください。", "voucher.expirationDate": "有効期限", "voucher.alternativeReference": "別の参照", - "voucher.paymentReferenceLabel": "支払いの参照", + "dragonpay.voucher.non.bank.selectField.placeholder": "プロバイダーを選択してください", + "dragonpay.voucher.bank.selectField.placeholder": "銀行を選択してください", + "voucher.paymentReferenceLabel": "確認番号", "voucher.surcharge": "%@ の追加料金を含む", "voucher.introduction.doku": "お買い上げありがとうございます。以下の情報を使用して、お支払いを完了してください。", "voucher.shopperName": "購入者氏名", "voucher.merchantName": "業者", "voucher.introduction.econtext": "お買い上げありがとうございます。以下の情報を使用して、お支払いを完了してください。", - "voucher.telephoneNumber": "電話番号", + "voucher.telephoneNumber": "電話番号(お客様番号)", "voucher.shopperReference": "購入者向け参考情報", "voucher.collectionInstitutionNumber": "収納機関番号", "voucher.econtext.telephoneNumber.invalid": "電話番号は10桁または11桁にしてください", @@ -103,6 +107,7 @@ "donateButton": "寄付する", "notNowButton": "今はしない", "thanksForYourSupport": "ご支援いただきありがとうございます。", + "resultMessages.preauthorized": "詳細が保存されました", "preauthorizeWith": "次で事前認証する:", "confirmPreauthorization": "事前承認を確認する", "confirmPurchase": "購入を確認する", @@ -229,6 +234,7 @@ "ctp.otp.subtitle": "%@から%@に送信されたコードを入力して、ご本人であることを確認してください。", "ctp.otp.saveCookiesCheckbox.label": "次回は検証をスキップする", "ctp.otp.saveCookiesCheckbox.information": "対象店舗で、お使いのデバイスとブラウザで記憶させるように選択すると、より迅速なチェックアウトが可能になります。共有デバイスでは推奨されません。", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "選択すると、お使いのデバイスとブラウザに記憶されます", "ctp.emptyProfile.message": "このClick to Payプロフィールに登録されているカードはありません。", "ctp.separatorText": "または以下を使用", "ctp.cards.title": "Click to Payで支払いを完了する", @@ -286,5 +292,11 @@ "ach.bankAccountNumber.label": "口座番号", "ach.bankLocationId.label": "ABAナンバー", "dragonpayVoucher.selectField.contextualText.nonBank": "プロバイダーを選択してください", - "dragonpayVoucher.selectField.contextualText.bank": "銀行を選択してください" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "銀行を選択してください", + "payme.openPayMeApp": "PayMeアプリで承認して支払いを完了し、確認を待ちます。", + "payme.redirectButtonLabel": "PayMeアプリを開く", + "payme.scanQrCode": "QRコードで支払いを完了する", + "payme.timeToPay": "このQRコードは%@有効です", + "payme.instructions.steps": "PayMeアプリを開きます。%@QRコードをスキャンして支払いを承認します。%@アプリで支払いを完了し、確認を待ちます。", + "payme.instructions.footnote": "支払いが完了するまでこのページを閉じないでください。" +} diff --git a/packages/lib/src/language/locales/ko-KR.json b/packages/lib/src/language/locales/ko-KR.json index 664182e0cd..2c8e6e95fc 100644 --- a/packages/lib/src/language/locales/ko-KR.json +++ b/packages/lib/src/language/locales/ko-KR.json @@ -2,8 +2,10 @@ "payButton": "결제", "payButton.redirecting": "리디렉션 중...", "payButton.with": "%{maskedData}(으)로 %{value} 결제", + "payButton.saveDetails": "세부 정보 저장", "close": "닫기", "storeDetails": "다음 결제를 위해 이 수단 저장", + "readMore": "자세히 보기", "creditCard.holderName": "카드상의 이름", "creditCard.holderName.invalid": "카드에 표시된 대로 이름을 입력합니다.", "creditCard.expiryDateField.month": "월", @@ -103,6 +105,7 @@ "donateButton": "기부하기", "notNowButton": "다음에 하기", "thanksForYourSupport": "도와주셔서 감사합니다!", + "resultMessages.preauthorized": "세부 정보 저장됨", "preauthorizeWith": "사전 승인 방법:", "confirmPreauthorization": "사전 승인 확인", "confirmPurchase": "구매 확인", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "%@로 전송된 %@ 코드를 입력하여 본인 인증을 진행합니다.", "ctp.otp.saveCookiesCheckbox.label": "다음 번에는 인증 건너뛰기", "ctp.otp.saveCookiesCheckbox.information": "더 빠른 결제를 위해 사용자 기기와 참여 매장의 브라우저에서 정보를 기억하도록 선택하세요. 공유 기기에는 권장하지 않습니다.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "장치와 브라우저에서 기억되도록 선택하기", "ctp.emptyProfile.message": "해당 Click to Pay 프로필에 등록된 카드가 없습니다", "ctp.separatorText": "또는 사용", "ctp.cards.title": "Click to Pay로 결제 완료", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "계좌 번호", "ach.bankLocationId.label": "ABA 라우팅 번호", "dragonpayVoucher.selectField.contextualText.nonBank": "제공 업체 선택", - "dragonpayVoucher.selectField.contextualText.bank": "은행 선택" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "은행 선택", + "payme.openPayMeApp": "PayMe 앱에서 결제를 승인하여 결제를 완료하고 확인을 기다립니다.", + "payme.redirectButtonLabel": "PayMe 앱 열기", + "payme.scanQrCode": "QR 코드로 결제 완료", + "payme.timeToPay": "이 QR 코드는 %@ 동안 유효합니다", + "payme.instructions.steps": "PayMe 앱을 엽니다.%@QR 코드를 스캔하여 결제를 승인합니다.%@앱에서 결제를 완료하고 확인을 기다립니다.", + "payme.instructions.footnote": "결제가 완료되기 전에 이 페이지를 닫지 마세요." +} diff --git a/packages/lib/src/language/locales/nl-NL.json b/packages/lib/src/language/locales/nl-NL.json index 15fcb5019a..61ad7702a9 100644 --- a/packages/lib/src/language/locales/nl-NL.json +++ b/packages/lib/src/language/locales/nl-NL.json @@ -2,8 +2,10 @@ "payButton": "Betaal", "payButton.redirecting": "U wordt doorverwezen...", "payButton.with": "Betaal %{value} met %{maskedData}", + "payButton.saveDetails": "Gegevens opslaan", "close": "Sluiten", "storeDetails": "Bewaar voor mijn volgende betaling", + "readMore": "Verder lezen", "creditCard.holderName": "Naam op kaart", "creditCard.holderName.invalid": "Voer naam in zoals weergegeven op kaart", "creditCard.expiryDateField.month": "Maand", @@ -103,6 +105,7 @@ "donateButton": "Doneren", "notNowButton": "Niet nu", "thanksForYourSupport": "Bedankt voor uw donatie!", + "resultMessages.preauthorized": "Gegevens opgeslagen", "preauthorizeWith": "Preautorisatie uitvoeren met", "confirmPreauthorization": "Preautorisatie bevestigen", "confirmPurchase": "Aankoop bevestigen", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Voer de code %@ in die naar %@ is gestuurd, zodat we kunnen verifiëren dat u het bent.", "ctp.otp.saveCookiesCheckbox.label": "Verificatie de volgende keer overslaan", "ctp.otp.saveCookiesCheckbox.information": "Selecteer om te worden onthouden op uw apparaat en in uw browser bij deelnemende winkels, zodat u sneller kunt afrekenen. Niet aanbevolen voor gedeelde apparaten.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Selecteer om onthouden te worden op uw apparaat en browser", "ctp.emptyProfile.message": "Geen kaarten geregistreerd voor dit Click to Pay-profiel", "ctp.separatorText": "of gebruik", "ctp.cards.title": "Voltooi de betaling met Click to Pay", @@ -283,5 +287,11 @@ "ach.bankAccountNumber.label": "Rekeningnummer", "ach.bankLocationId.label": "Routingnummer (ABA)", "dragonpayVoucher.selectField.contextualText.nonBank": "Selecteer uw aanbieder", - "dragonpayVoucher.selectField.contextualText.bank": "Selecteer uw bank" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Selecteer uw bank", + "payme.openPayMeApp": "Voltooi uw betaling door deze te autoriseren in de PayMe-app en de bevestiging af te wachten.", + "payme.redirectButtonLabel": "Open de PayMe-app", + "payme.scanQrCode": "Voltooi uw betaling met een QR-code", + "payme.timeToPay": "Deze QR-code is %@ geldig", + "payme.instructions.steps": "Open de PayMe-app.%@Scan de QR-code om de betaling te autoriseren.%@Voltooi de betaling in de app en wacht de bevestiging af.", + "payme.instructions.footnote": "Sluit deze pagina niet voordat de betaling is voltooid" +} diff --git a/packages/lib/src/language/locales/no-NO.json b/packages/lib/src/language/locales/no-NO.json index bdd507814d..289a8bfb1e 100644 --- a/packages/lib/src/language/locales/no-NO.json +++ b/packages/lib/src/language/locales/no-NO.json @@ -2,8 +2,10 @@ "payButton": "Betal", "payButton.redirecting": "Omdirigerer...", "payButton.with": "Betal %{value} med %{maskedData}", + "payButton.saveDetails": "Lagre detaljer", "close": "Lukk", "storeDetails": "Lagre til min neste betaling", + "readMore": "Les mer", "creditCard.holderName": "Navn på kortet", "creditCard.holderName.invalid": "Skriv inn navnet som vist på kortet", "creditCard.expiryDateField.month": "Måned", @@ -103,6 +105,7 @@ "donateButton": "Donér", "notNowButton": "Ikke nå", "thanksForYourSupport": "Takk for din støtte!", + "resultMessages.preauthorized": "Detaljer lagret", "preauthorizeWith": "Forhåndsgodkjenn med", "confirmPreauthorization": "Bekreft forhåndsgodkjenning", "confirmPurchase": "Bekreft kjøp", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Tast inn koden vi sendte til %@, for å verifisere at det er deg.", "ctp.otp.saveCookiesCheckbox.label": "Hopp over verifisering neste gang", "ctp.otp.saveCookiesCheckbox.information": "Velg for å bli husket på enheten og i nettleseren din i deltakende butikker for raskere betaling. Anbefales ikke for delte enheter.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Velg for å bli husket på enheten og i nettleseren din", "ctp.emptyProfile.message": "Ingen kort er registrert i denne Click to Pay-profilen", "ctp.separatorText": "eller bruk", "ctp.cards.title": "Fullfør betalingen med Click to Pay", @@ -283,5 +287,11 @@ "ach.bankAccountNumber.label": "Kontonummer", "ach.bankLocationId.label": "ABA-dirigeringsnummer", "dragonpayVoucher.selectField.contextualText.nonBank": "Velg din leverandør", - "dragonpayVoucher.selectField.contextualText.bank": "Velg din bank" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Velg din bank", + "payme.openPayMeApp": "Fullfør betalingen i PayMe-appen ved å godkjenne betalingen i appen og vente på bekreftelse.", + "payme.redirectButtonLabel": "Åpne PayMe-appen", + "payme.scanQrCode": "Fullfør betalingen via QR-kode", + "payme.timeToPay": "Denne QR-koden er gyldig i %@", + "payme.instructions.steps": "Åpne PayMe-appen.%@Skann QR-koden for å autorisere betalingen.%@Fullfør betalingen i appen, og vent på bekreftelsen.", + "payme.instructions.footnote": "Ikke lukk denne siden før betalingen er fullført" +} diff --git a/packages/lib/src/language/locales/pl-PL.json b/packages/lib/src/language/locales/pl-PL.json index 16fd0c948b..5aa9f599b2 100644 --- a/packages/lib/src/language/locales/pl-PL.json +++ b/packages/lib/src/language/locales/pl-PL.json @@ -2,8 +2,10 @@ "payButton": "Zapłać", "payButton.redirecting": "Przekierowywanie...", "payButton.with": "Zapłać %{value} za pomocą %{maskedData}", + "payButton.saveDetails": "Zapisz dane", "close": "Zamknij", "storeDetails": "Zapisz na potrzeby następnej płatności", + "readMore": "Czytaj więcej", "creditCard.holderName": "Imię i nazwisko na karcie", "creditCard.holderName.invalid": "Wpisz imię i nazwisko w takim formacie, jak na karcie", "creditCard.expiryDateField.month": "Miesiąc", @@ -103,6 +105,7 @@ "donateButton": "Przekaż darowiznę", "notNowButton": "Nie teraz", "thanksForYourSupport": "Dziękujemy za wsparcie!", + "resultMessages.preauthorized": "Zapisano dane", "preauthorizeWith": "Autoryzuj wstępnie za pomocą:", "confirmPreauthorization": "Potwierdź autoryzację wstępną", "confirmPurchase": "Potwierdź zakup", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Wprowadź kod %@ wysłany na adres %@, aby potwierdzić swoją tożsamość.", "ctp.otp.saveCookiesCheckbox.label": "Następnym razem pomiń weryfikację", "ctp.otp.saveCookiesCheckbox.information": "Wybierz opcję zapamiętywania na urządzeniu i w przeglądarce w sklepach uczestniczących w programie, aby przyspieszyć realizację transakcji. Niezalecane dla współużytkowanych urządzeń.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Wybierz, aby zapamiętać dane logowania na Twoim urządzeniu i przeglądarce", "ctp.emptyProfile.message": "W tym profilu Click to Pay nie zarejestrowano żadnych kart", "ctp.separatorText": "lub przypadków jej użycia", "ctp.cards.title": "Dokończ płatność za pomocą funkcji Click to Pay", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "Numer rachunku", "ach.bankLocationId.label": "Kod bankowy ABA Routing Number", "dragonpayVoucher.selectField.contextualText.nonBank": "Wybierz dostawcę", - "dragonpayVoucher.selectField.contextualText.bank": "Wybierz swój bank" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Wybierz swój bank", + "payme.openPayMeApp": "Dokończ płatność w aplikacji PayMe, autoryzując płatność w aplikacji, i poczekaj na potwierdzenie.", + "payme.redirectButtonLabel": "Otwórz aplikację PayMe", + "payme.scanQrCode": "Dokończ płatność za pomocą kodu QR", + "payme.timeToPay": "Okres ważności kodu QR: %@", + "payme.instructions.steps": "Otwórz aplikację PayMe.%@Zeskanuj kod QR, aby autoryzować płatność.%@Dokończ płatność w aplikacji i poczekaj na potwierdzenie.", + "payme.instructions.footnote": "Nie zamykaj tej strony, dopóki płatność nie zostanie zakończona" +} diff --git a/packages/lib/src/language/locales/pt-BR.json b/packages/lib/src/language/locales/pt-BR.json index b003ba30ea..e88a7465c7 100644 --- a/packages/lib/src/language/locales/pt-BR.json +++ b/packages/lib/src/language/locales/pt-BR.json @@ -2,8 +2,10 @@ "payButton": "Pagar", "payButton.redirecting": "Redirecionando...", "payButton.with": "Pague %{value} com %{maskedData}", + "payButton.saveDetails": "Salvar informações", "close": "Fechar", "storeDetails": "Salvar para meu próximo pagamento", + "readMore": "Leia mais", "creditCard.holderName": "Nome no cartão", "creditCard.holderName.invalid": "Digite o nome conforme mostrado no cartão", "creditCard.expiryDateField.month": "Mês", @@ -103,6 +105,7 @@ "donateButton": "Doar", "notNowButton": "Agora não", "thanksForYourSupport": "Obrigado pelo apoio!", + "resultMessages.preauthorized": "Informações salvas", "preauthorizeWith": "Pré-autorizar com", "confirmPreauthorization": "Confirmar pré-autorização", "confirmPurchase": "Confirmar compra", @@ -230,6 +233,7 @@ "ctp.otp.subtitle": "Digite o código %@ enviado para %@ para confirmar que é você.", "ctp.otp.saveCookiesCheckbox.label": "Ignorar verificação da próxima vez", "ctp.otp.saveCookiesCheckbox.information": "Selecione esta opção para ser lembrado no seu dispositivo e navegador, agilizando o pagamento nas lojas participantes. Não recomendado para dispositivos compartilhados.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Selecione para ser lembrado em seu dispositivo e navegador", "ctp.emptyProfile.message": "Não há nenhum cartão cadastrado neste perfil do Click to Pay", "ctp.separatorText": "ou use", "ctp.cards.title": "Concluir o pagamento com o Click to Pay", @@ -287,5 +291,11 @@ "ach.bankAccountNumber.label": "Número da conta", "ach.bankLocationId.label": "Número de roteamento ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Selecione o seu fornecedor", - "dragonpayVoucher.selectField.contextualText.bank": "Selecione seu banco" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Selecione seu banco", + "payme.openPayMeApp": "Para concluir o pagamento no PayMe, autorize-o no aplicativo e aguarde a confirmação.", + "payme.redirectButtonLabel": "Abrir o aplicativo PayMe", + "payme.scanQrCode": "Concluir o pagamento com um código QR", + "payme.timeToPay": "Este QR code é válido para %@", + "payme.instructions.steps": "Abra o aplicativo PayMe.%@Escaneie o QR code para autorizar o pagamento.%@Conclua o pagamento no aplicativo e aguarde a confirmação.", + "payme.instructions.footnote": "Não feche esta página antes de concluir o pagamento" +} diff --git a/packages/lib/src/language/locales/pt-PT.json b/packages/lib/src/language/locales/pt-PT.json index c6eab63dc1..5550ee5823 100644 --- a/packages/lib/src/language/locales/pt-PT.json +++ b/packages/lib/src/language/locales/pt-PT.json @@ -2,8 +2,10 @@ "payButton": "Pagar", "payButton.redirecting": "Redirecionar...", "payButton.with": "Pagar %{value} com %{maskedData}", + "payButton.saveDetails": "Guardar detalhes", "close": "Fechar", "storeDetails": "Guardar para o meu próximo pagamento", + "readMore": "Ler mais", "creditCard.holderName": "Nome no cartão", "creditCard.holderName.invalid": "Introduzir o nome como mostrado no cartão", "creditCard.expiryDateField.month": "Mês", @@ -104,6 +106,7 @@ "donateButton": "Doar", "notNowButton": "Agora não", "thanksForYourSupport": "Obrigado pelo seu apoio!", + "resultMessages.preauthorized": "Detalhes guardados", "preauthorizeWith": "Pré-autorizar com", "confirmPreauthorization": "Confirmar pré-autorização", "confirmPurchase": "Confirmar compra", @@ -231,6 +234,7 @@ "ctp.otp.subtitle": "Introduza o código %@ enviado para %@ para verificar se é você.", "ctp.otp.saveCookiesCheckbox.label": "Saltar verificação da próxima vez", "ctp.otp.saveCookiesCheckbox.information": "Selecione para ser memorizado no seu dispositivo e navegador nas lojas participantes, para uma finalização de compra mais rápida. Não recomendado para dispositivos partilhados.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Selecione para ser memorizado no seu dispositivo e navegador", "ctp.emptyProfile.message": "Nenhum cartão registado neste perfil Click to Pay", "ctp.separatorText": "ou utilize", "ctp.cards.title": "Pagamento completo com Click to Pay", @@ -288,5 +292,11 @@ "ach.bankAccountNumber.label": "Número da conta", "ach.bankLocationId.label": "Número de encaminhamento da ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Selecione seu fornecedor", - "dragonpayVoucher.selectField.contextualText.bank": "Selecione o seu banco" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Selecione o seu banco", + "payme.openPayMeApp": "Conclua o seu pagamento na app PayMe autorizando o pagamento na app e aguardando a confirmação.", + "payme.redirectButtonLabel": "Abrir a app PayMe", + "payme.scanQrCode": "Conclua o seu pagamento por código QR", + "payme.timeToPay": "Este código QR é válido para %@", + "payme.instructions.steps": "Abra a app PayMe.%@Leia o código QR para autorizar o pagamento.%@Conclua o pagamento na app e aguarde a confirmação.", + "payme.instructions.footnote": "Não feche esta página antes de concluir o pagamento" +} diff --git a/packages/lib/src/language/locales/ro-RO.json b/packages/lib/src/language/locales/ro-RO.json index b90cf04af0..79b40ee5fc 100644 --- a/packages/lib/src/language/locales/ro-RO.json +++ b/packages/lib/src/language/locales/ro-RO.json @@ -2,8 +2,10 @@ "payButton": "Plătiți", "payButton.redirecting": "Se redirecționează...", "payButton.with": "Plătiți %{value} cu %{maskedData}", + "payButton.saveDetails": "Salvați detaliile", "close": "Închidere", "storeDetails": "Salvează pentru următoarea mea plată", + "readMore": "Citiți mai mult", "creditCard.holderName": "Numele de pe card", "creditCard.holderName.invalid": "Completați numele după cum figurează pe card", "creditCard.expiryDateField.month": "Lună", @@ -103,6 +105,7 @@ "donateButton": "Donați", "notNowButton": "Nu acum", "thanksForYourSupport": "Vă mulțumim pentru sprijin!", + "resultMessages.preauthorized": "Detalii salvate", "preauthorizeWith": "Preautorizare cu", "confirmPreauthorization": "Confirmați preautorizarea", "confirmPurchase": "Confirmați achiziția", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Introduceți codul %@ pe care l-am trimis la %@ pentru a confirma că sunteți dvs.", "ctp.otp.saveCookiesCheckbox.label": "Omiteți verificarea data viitoare", "ctp.otp.saveCookiesCheckbox.information": "Selectați pentru a fi reținut pe dispozitiv și în browserul dvs. la magazinele participante pentru o plată mai rapidă. Nerecomandat pentru dispozitivele partajate.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Selectați pentru a fi memorat pe dispozitiv și browser", "ctp.emptyProfile.message": "Niciun card înregistrat în acest profil Click to Pay", "ctp.separatorText": "sau folosiți", "ctp.cards.title": "Finalizați plata cu Click to Pay", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "Număr de cont", "ach.bankLocationId.label": "Număr de direcționare ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Selectați furnizorul dvs.", - "dragonpayVoucher.selectField.contextualText.bank": "Selectați banca dvs." -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Selectați banca dvs.", + "payme.openPayMeApp": "Finalizați plata în aplicația PayMe, autorizând plata în aplicație, și așteptați confirmarea.", + "payme.redirectButtonLabel": "Deschideți aplicația PayMe", + "payme.scanQrCode": "Finalizați plata folosind codul QR", + "payme.timeToPay": "Acest cod QR este valabil pentru %@", + "payme.instructions.steps": "Deschideți aplicația PayMe.%@Scanați codul QR pentru a autoriza plata.%@Finalizați plata în aplicație și așteptați confirmarea.", + "payme.instructions.footnote": "Nu închideți această pagină înainte de finalizarea plății." +} diff --git a/packages/lib/src/language/locales/ru-RU.json b/packages/lib/src/language/locales/ru-RU.json index 449afe8953..a475184a70 100644 --- a/packages/lib/src/language/locales/ru-RU.json +++ b/packages/lib/src/language/locales/ru-RU.json @@ -2,8 +2,10 @@ "payButton": "Заплатить", "payButton.redirecting": "Перенаправление...", "payButton.with": "Оплатить %{value} %{maskedData}", + "payButton.saveDetails": "Сохранить данные", "close": "Закрыть", "storeDetails": "Сохранить для следующего платежа", + "readMore": "Подробнее", "creditCard.holderName": "Имя на карте", "creditCard.holderName.invalid": "Введите имя, как оно указано на карте", "creditCard.expiryDateField.month": "Месяц", @@ -103,6 +105,7 @@ "donateButton": "Пожертвовать", "notNowButton": "Позже", "thanksForYourSupport": "Благодарим за поддержку!", + "resultMessages.preauthorized": "Данные сохранены", "preauthorizeWith": "Предавторизация в", "confirmPreauthorization": "Подтвердить предавторизацию", "confirmPurchase": "Подтвердить покупку", @@ -227,6 +230,7 @@ "ctp.otp.subtitle": "Для подтверждения своей личности введите код %@, отправленный %@.", "ctp.otp.saveCookiesCheckbox.label": "Пропустить подтверждение в следующий раз", "ctp.otp.saveCookiesCheckbox.information": "Чтобы в соответствующих магазинах запомнили ваше устройство и браузер, выберите эту опцию. Это позволит быстрее оформлять заказы. Не рекомендуется на устройствах совместного использования.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Выберите, чтобы запомнили ваше устройство и браузер", "ctp.emptyProfile.message": "В данном профиле Click to Pay нет зарегистрированных карт", "ctp.separatorText": "или используйте", "ctp.cards.title": "Оформите оплату с помощью Click to Pay", @@ -284,5 +288,11 @@ "ach.bankAccountNumber.label": "Номер счета", "ach.bankLocationId.label": "Маршрутный номер ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Выберите своего оператора", - "dragonpayVoucher.selectField.contextualText.bank": "Выберите банк" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Выберите банк", + "payme.openPayMeApp": "Для завершения оплаты разрешите ее в приложении PayMe и дождитесь подтверждения.", + "payme.redirectButtonLabel": "Открыть приложение PayMe", + "payme.scanQrCode": "Завершить платеж с помощью QR-кода", + "payme.timeToPay": "Данный QR-код действителен в течение %@", + "payme.instructions.steps": "Откройте приложение PayMe.%@Отсканируйте QR-код, чтобы разрешить платеж.%@Завершите платеж в приложении и дождитесь подтверждения.", + "payme.instructions.footnote": "Не закрывайте эту страницу до завершения платежа" +} diff --git a/packages/lib/src/language/locales/sk-SK.json b/packages/lib/src/language/locales/sk-SK.json index 777badf3e9..d41224caa7 100644 --- a/packages/lib/src/language/locales/sk-SK.json +++ b/packages/lib/src/language/locales/sk-SK.json @@ -2,8 +2,10 @@ "payButton": "Zaplatiť", "payButton.redirecting": "Prebieha presmerovanie...", "payButton.with": "Zaplatiť %{value} pomocou %{maskedData}", + "payButton.saveDetails": "Uložiť údaje", "close": "Zavrieť", "storeDetails": "Uložiť pre moju ďalšiu platbu", + "readMore": "Prečítajte si viac", "creditCard.holderName": "Meno na karte", "creditCard.holderName.invalid": "Zadajte meno tak, ako je uvedené na karte", "creditCard.expiryDateField.month": "Mesiac", @@ -103,6 +105,7 @@ "donateButton": "Prispieť", "notNowButton": "Teraz nie", "thanksForYourSupport": "Ďakujeme za podporu!", + "resultMessages.preauthorized": "Údaje boli uložené", "preauthorizeWith": "Predbežne autorizovať pomocou", "confirmPreauthorization": "Potvrďte predbežnú autorizáciu", "confirmPurchase": "Potvrďte nákup", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Zadajte kód %@, ktorý sme poslali na adresu %@, aby sme overili, že ste to vy.", "ctp.otp.saveCookiesCheckbox.label": "Nabudúce preskočiť overovanie", "ctp.otp.saveCookiesCheckbox.information": "Vyberte túto možnosť na zapamätanie v zariadení a prehliadači v zúčastnených obchodoch, čím urýchlite platbu. Neodporúča sa pre zdieľané zariadenia.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Vyberte túto možnosť na zapamätanie v zariadení a prehliadači", "ctp.emptyProfile.message": "V tomto profile Click to Pay nie sú registrované žiadne karty", "ctp.separatorText": "alebo použite", "ctp.cards.title": "Dokončite platbu v službe Click to Pay", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "Číslo účtu", "ach.bankLocationId.label": "Smerovacie číslo ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Vyberte si poskytovateľa", - "dragonpayVoucher.selectField.contextualText.bank": "Vyberte svoju banku" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Vyberte svoju banku", + "payme.openPayMeApp": "Dokončite platbu v aplikácii PayMe tak, že v aplikácii autorizujete platbu a počkáte na potvrdenie.", + "payme.redirectButtonLabel": "Otvoriť aplikáciu PayMe", + "payme.scanQrCode": "Dokončite platbu pomocou kódu QR", + "payme.timeToPay": "Tento kód QR platí %@", + "payme.instructions.steps": "Otvorte aplikáciu PayMe.%@Autorizujte platbu naskenovaním QR kódu.%@Dokončite platbu v aplikácii a počkajte na potvrdenie.", + "payme.instructions.footnote": "Nezatvárajte túto stránku pred dokončením platby" +} diff --git a/packages/lib/src/language/locales/sl-SI.json b/packages/lib/src/language/locales/sl-SI.json index c2a9484e0a..a0f69c5ef5 100644 --- a/packages/lib/src/language/locales/sl-SI.json +++ b/packages/lib/src/language/locales/sl-SI.json @@ -2,8 +2,10 @@ "payButton": "Plačilo", "payButton.redirecting": "Preusmerjanje...", "payButton.with": "Plačajte %{value} z %{maskedData}", + "payButton.saveDetails": "Shrani podrobnosti", "close": "Zapri", "storeDetails": "Shrani za moje naslednje plačilo", + "readMore": "Preberi več", "creditCard.holderName": "Ime na kartici", "creditCard.holderName.invalid": "Vnesite ime, kot je prikazano na kartici", "creditCard.expiryDateField.month": "Mesec", @@ -103,6 +105,7 @@ "donateButton": "Donirajte", "notNowButton": "Ne zdaj", "thanksForYourSupport": "Zahvaljujemo se vam za podporo!", + "resultMessages.preauthorized": "Podrobnosti so shranjene", "preauthorizeWith": "Predhodna odobritev s/z:", "confirmPreauthorization": "Potrdi predhodno odobritev", "confirmPurchase": "Potrditev nakupa", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Vnesite kodo %@, ki smo jo poslali na %@, da potrdite, da ste to res vi.", "ctp.otp.saveCookiesCheckbox.label": "Naslednjič preskoči preverjanje", "ctp.otp.saveCookiesCheckbox.information": "Izberite če želite, da se v vaši napravi in brskalniku v sodelujočih trgovinah vaši podatki shranijo za hitrejši zaključek zakupa. Ni priporočljivo za naprave v skupni rabi.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Izberite, če želite, da se v vaši napravi in brskalniku shranijo vaši podatki", "ctp.emptyProfile.message": "V tem profilu Click to Pay ni registriranih nobenih kartic", "ctp.separatorText": "ali uporabite", "ctp.cards.title": "Dokončajte plačilo s storitvijo Click to Pay", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "Številka računa", "ach.bankLocationId.label": "Koda banke ABA", "dragonpayVoucher.selectField.contextualText.nonBank": "Izberite svojega ponudnika", - "dragonpayVoucher.selectField.contextualText.bank": "Izberite svojo banko" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Izberite svojo banko", + "payme.openPayMeApp": "Plačilo dokončajte v aplikaciji PayMe tako, da ga odobrite v aplikaciji in počakate na potrditev.", + "payme.redirectButtonLabel": "Odprite aplikacijo PayMe", + "payme.scanQrCode": "Izvedite plačilo s kodo QR", + "payme.timeToPay": "Ta koda QR velja za %@", + "payme.instructions.steps": "Odprite aplikacijo PayMe.%@Skenirajte kodo QR, da odobrite plačilo.%@Zaključite plačilo v aplikaciji in počakajte na potrditev.", + "payme.instructions.footnote": "Ne zapirajte te strani, preden je plačilo zaključeno." +} diff --git a/packages/lib/src/language/locales/sv-SE.json b/packages/lib/src/language/locales/sv-SE.json index 8563c9b3f2..8add065808 100644 --- a/packages/lib/src/language/locales/sv-SE.json +++ b/packages/lib/src/language/locales/sv-SE.json @@ -2,8 +2,10 @@ "payButton": "Betala", "payButton.redirecting": "Omdirigerar…", "payButton.with": "Betala %{value} med %{maskedData}", + "payButton.saveDetails": "Spara information", "close": "Stäng", "storeDetails": "Spara till min nästa betalning", + "readMore": "Läs mer", "creditCard.holderName": "Namn på kort", "creditCard.holderName.invalid": "Ange namnet som det står på kortet", "creditCard.expiryDateField.month": "Månad", @@ -103,6 +105,7 @@ "donateButton": "Donera", "notNowButton": "Inte nu", "thanksForYourSupport": "Tack för ditt stöd!", + "resultMessages.preauthorized": "Information sparad", "preauthorizeWith": "Förauktorisera med", "confirmPreauthorization": "Bekräfta förauktorisering", "confirmPurchase": "Bekräfta köp", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "Ange koden som %@ skickade till %@ för att verifiera att det är du.", "ctp.otp.saveCookiesCheckbox.label": "Hoppa över verifieringen nästa gång", "ctp.otp.saveCookiesCheckbox.information": "Välj att bli ihågkommen på din enhet och webbläsare i deltagande butiker för snabbare utcheckning. Rekommenderas inte för delade enheter.", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "Välj att bli ihågkommen på din enhet och i din webbläsare", "ctp.emptyProfile.message": "Inga kort registrerade i denna Click to Pay-profil", "ctp.separatorText": "eller använd", "ctp.cards.title": "Slutför betalningen med Click to Pay", @@ -284,5 +288,11 @@ "ach.bankAccountNumber.label": "Kontonummer", "ach.bankLocationId.label": "ABA-nummer", "dragonpayVoucher.selectField.contextualText.nonBank": "Välj din leverantör", - "dragonpayVoucher.selectField.contextualText.bank": "Välj din bank" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "Välj din bank", + "payme.openPayMeApp": "Slutför din betalning i PayMe-appen genom att godkänna betalningen i appen och vänta på bekräftelse.", + "payme.redirectButtonLabel": "Öppna PayMe-appen", + "payme.scanQrCode": "Slutför din betalning med QR-kod", + "payme.timeToPay": "Denna QR-kod är giltig i %@", + "payme.instructions.steps": "Öppna PayMe-appen.%@Skanna QR-koden för att godkänna betalningen.%@Slutför betalningen i appen och vänta på bekräftelse.", + "payme.instructions.footnote": "Stäng inte denna sida innan betalningen är slutförd" +} diff --git a/packages/lib/src/language/locales/zh-CN.json b/packages/lib/src/language/locales/zh-CN.json index 38f24743d6..508ed1d477 100644 --- a/packages/lib/src/language/locales/zh-CN.json +++ b/packages/lib/src/language/locales/zh-CN.json @@ -2,8 +2,10 @@ "payButton": "支付", "payButton.redirecting": "正在重定向...", "payButton.with": "使用 %{maskedData} 支付 %{value}", + "payButton.saveDetails": "保存详情", "close": "关闭", "storeDetails": "保存以便下次支付使用", + "readMore": "阅读更多", "creditCard.holderName": "卡片上的姓名", "creditCard.holderName.invalid": "输入卡片上显示的姓名", "creditCard.expiryDateField.month": "月", @@ -103,6 +105,7 @@ "donateButton": "捐赠", "notNowButton": "暂不", "thanksForYourSupport": "感谢您的支持!", + "resultMessages.preauthorized": "详情已保存", "preauthorizeWith": "预先授权", "confirmPreauthorization": "确认预先授权", "confirmPurchase": "确认购买", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "请输入我们发送至 %@ 的代码 (%@),以验证是您本人。", "ctp.otp.saveCookiesCheckbox.label": "下次跳过验证", "ctp.otp.saveCookiesCheckbox.information": "在您的设备和浏览器上,在参与活动的店铺中选择记住该选项,以便更快地结账。不建议用于共享设备。", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "选择在您的设备和浏览器上记住", "ctp.emptyProfile.message": "此 Click to Pay 个人资料中尚未注册任何卡片", "ctp.separatorText": "或使用", "ctp.cards.title": "借助 Click to Pay 完成付款", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "账号", "ach.bankLocationId.label": "ABA 路由电汇编码", "dragonpayVoucher.selectField.contextualText.nonBank": "选择您的提供商", - "dragonpayVoucher.selectField.contextualText.bank": "选择您的银行" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "选择您的银行", + "payme.openPayMeApp": "在 PayMe 应用中授权付款并等待确认,即可完成您在该应用中的付款。", + "payme.redirectButtonLabel": "打开 PayMe 应用", + "payme.scanQrCode": "通过二维码完成付款", + "payme.timeToPay": "此二维码有效期为 %@", + "payme.instructions.steps": "打开 PayMe 应用。%@扫描二维码即可授权付款。%@在应用中完成支付并等待确认。", + "payme.instructions.footnote": "付款完成前,请勿关闭此页面" +} diff --git a/packages/lib/src/language/locales/zh-TW.json b/packages/lib/src/language/locales/zh-TW.json index 560748e340..43738153a7 100644 --- a/packages/lib/src/language/locales/zh-TW.json +++ b/packages/lib/src/language/locales/zh-TW.json @@ -2,8 +2,10 @@ "payButton": "支付", "payButton.redirecting": "重新導向中......", "payButton.with": "用 %{maskedData} 支付 %{value}", + "payButton.saveDetails": "儲存詳細資料", "close": "關閉", "storeDetails": "儲存以供下次付款使用", + "readMore": "閱讀全文", "creditCard.holderName": "信用卡上的姓名", "creditCard.holderName.invalid": "輸入卡上所示的姓名", "creditCard.expiryDateField.month": "月份", @@ -103,6 +105,7 @@ "donateButton": "捐贈", "notNowButton": "稍後再說", "thanksForYourSupport": "感謝您的支持!", + "resultMessages.preauthorized": "已儲存詳細資料", "preauthorizeWith": "透過以下方式進行預先授權:", "confirmPreauthorization": "確認預先授權", "confirmPurchase": "確認購買", @@ -229,6 +232,7 @@ "ctp.otp.subtitle": "請輸入我們傳送至 %@ 的驗證碼 %@,以驗證是您本人。", "ctp.otp.saveCookiesCheckbox.label": "下次略過驗證", "ctp.otp.saveCookiesCheckbox.information": "選擇在裝置和瀏覽器中記住參與商店的驗證資訊,以加快結帳速度。不建議在共享裝置上選取該設定。", + "ctp.otp.saveCookiesCheckbox.shorterInfo": "選擇在裝置和瀏覽器中記住", "ctp.emptyProfile.message": "此 Click to Pay 個人資料中未註冊任何卡", "ctp.separatorText": "或使用", "ctp.cards.title": "使用 Click to Pay 完成付款", @@ -286,5 +290,11 @@ "ach.bankAccountNumber.label": "帳戶號碼", "ach.bankLocationId.label": "ABA 匯款路徑編號", "dragonpayVoucher.selectField.contextualText.nonBank": "選擇您的供應商", - "dragonpayVoucher.selectField.contextualText.bank": "選取您的銀行" -} \ No newline at end of file + "dragonpayVoucher.selectField.contextualText.bank": "選取您的銀行", + "payme.openPayMeApp": "在 PayMe 應用程式中授權付款,完成付款並等待確認。", + "payme.redirectButtonLabel": "開啟 PayMe 應用程式", + "payme.scanQrCode": "使用 QR 代碼完成付款", + "payme.timeToPay": "此 QR 代碼對 %@ 有效", + "payme.instructions.steps": "開啟 PayMe 應用程式。%@掃描 QR 代碼授權付款。%@在應用程式中完成付款並等待確認。", + "payme.instructions.footnote": "完成付款前,請勿關閉此頁面" +} diff --git a/packages/lib/src/utils/detectInIframe.ts b/packages/lib/src/utils/detectInIframe.ts deleted file mode 100644 index 5307d7028e..0000000000 --- a/packages/lib/src/utils/detectInIframe.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Returns true if the page is being run in an iframe -export default () => window.location !== window.parent.location; diff --git a/packages/lib/src/utils/detectInIframeInSameOrigin.ts b/packages/lib/src/utils/detectInIframeInSameOrigin.ts new file mode 100644 index 0000000000..dcb58ccec8 --- /dev/null +++ b/packages/lib/src/utils/detectInIframeInSameOrigin.ts @@ -0,0 +1,14 @@ +/** + * Returns true if the page is being run in an iframe with the same origin as the parent. + * In this scenario, if the merchant has set redirectFromTopWhenInIframe: true, then we can perform the redirect on the top level, parent, window; + * rather than on the iframe's window + */ +export default () => { + try { + if (window.parent.location.href) { + return window.location !== window.parent.location; // iframe check: locations will differ if we're in an iframe + } + } catch (e) { + return false; // we cannot access window.parent.location.href - so consider us "not to be in an iframe" for the purpose of Redirects + } +}; diff --git a/packages/lib/src/utils/isScreenSmall.ts b/packages/lib/src/utils/isScreenSmall.ts new file mode 100644 index 0000000000..3dd210b0ab --- /dev/null +++ b/packages/lib/src/utils/isScreenSmall.ts @@ -0,0 +1,3 @@ +const isScreenSmall = (): boolean => window.matchMedia('(max-width: 480px)').matches; + +export default isScreenSmall; diff --git a/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx b/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx index 0c13445d89..3687e3d160 100644 --- a/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx +++ b/packages/lib/storybook/stories/wallets/ApplePayExpress.stories.tsx @@ -16,10 +16,16 @@ const meta: MetaConfiguration = { title: 'Wallets/ApplePay' }; -const getShippingMethods = (countryCode: string): ApplePayJS.ApplePayShippingMethod[] => { +/** + * Method that fetches the shipping options according to the country. + * This function in most of the cases is asynchronous, as it will request shipping options in the backend + * + * @param countryCode - country code + */ +const getShippingMethods = (countryCode: string): Promise => { switch (countryCode) { case 'US': { - return [ + return Promise.resolve([ { label: 'Standard Shipping', detail: 'Arrives in 5 to 7 days', @@ -32,11 +38,11 @@ const getShippingMethods = (countryCode: string): ApplePayJS.ApplePayShippingMet amount: '10.99', identifier: 'Express' } - ]; + ]); } case 'NL': default: { - return [ + return Promise.resolve([ { label: 'Free Shipping', detail: 'Arrives in 10 to 15 days', @@ -55,7 +61,7 @@ const getShippingMethods = (countryCode: string): ApplePayJS.ApplePayShippingMet amount: '15.99', identifier: 'Express' } - ]; + ]); } } }; @@ -177,7 +183,7 @@ export const Express: ApplePayStory = { return; } - const newShippingMethods = getShippingMethods(countryCode); + const newShippingMethods = await getShippingMethods(countryCode); const newLineItems = createLineItems(newShippingMethods[0]); const newTotal = createApplePayTotal(newLineItems); diff --git a/packages/lib/storybook/stories/wallets/GooglePayExpress.stories.tsx b/packages/lib/storybook/stories/wallets/GooglePayExpress.stories.tsx index 762804c281..0a61a09004 100644 --- a/packages/lib/storybook/stories/wallets/GooglePayExpress.stories.tsx +++ b/packages/lib/storybook/stories/wallets/GooglePayExpress.stories.tsx @@ -18,6 +18,11 @@ const meta: MetaConfiguration = { title: 'Wallets/GooglePay' }; +/** + * Method that calculate the shipping costs based on the country/shipping options + * + * @param countryCode - country code + */ function getShippingCost(countryCode) { switch (countryCode) { case 'BR': @@ -35,10 +40,16 @@ function getShippingCost(countryCode) { } } +/** + * Method that fetches the shipping options according to the country. + * This function in most of the cases is asynchronous, as it will request shipping options in the backend + * + * @param countryCode - country code + */ function getShippingOptions(countryCode?: string) { switch (countryCode) { case 'BR': { - return { + return Promise.resolve({ defaultSelectedOptionId: 'shipping-001', shippingOptions: [ { @@ -52,10 +63,10 @@ function getShippingOptions(countryCode?: string) { description: 'Standard shipping delivered in 2 business days.' } ] - }; + }); } default: { - return { + return Promise.resolve({ defaultSelectedOptionId: 'shipping-001', shippingOptions: [ { @@ -74,7 +85,7 @@ function getShippingOptions(countryCode?: string) { description: 'Express shipping delivered in 1 business day.' } ] - }; + }); } } } @@ -180,7 +191,8 @@ export const Express: GooglePayStory = { paymentDataCallbacks: { onPaymentDataChanged(intermediatePaymentData) { - return new Promise(resolve => { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async resolve => { const { callbackTrigger, shippingAddress, shippingOptionData } = intermediatePaymentData; const paymentDataRequestUpdate: google.payments.api.PaymentDataRequestUpdate = {}; @@ -194,7 +206,7 @@ export const Express: GooglePayStory = { } /** If it initializes or changes the shipping address, we calculate the shipping options and transaction info */ if (callbackTrigger === 'INITIALIZE' || callbackTrigger === 'SHIPPING_ADDRESS') { - paymentDataRequestUpdate.newShippingOptionParameters = getShippingOptions(shippingAddress.countryCode); + paymentDataRequestUpdate.newShippingOptionParameters = await getShippingOptions(shippingAddress.countryCode); const selectedShippingOptionId = paymentDataRequestUpdate.newShippingOptionParameters.defaultSelectedOptionId; paymentDataRequestUpdate.newTransactionInfo = calculateNewTransactionInfo( shippingAddress.countryCode, diff --git a/packages/lib/tsconfig.json b/packages/lib/tsconfig.json index 4c42945821..1a545ee6c6 100644 --- a/packages/lib/tsconfig.json +++ b/packages/lib/tsconfig.json @@ -32,11 +32,13 @@ "jsxFactory": "h", /* Include types for the browser: window, document, etc */ - "lib": ["es2022","dom", "dom.iterable"] + "lib": ["es2022","dom", "dom.iterable"], + + "types": ["@testing-library/jest-dom", "applepayjs", "googlepay", "node"], }, "include": [ "./src/**/*", - "./src/types/custom.d.ts" + "./src/types/custom.d.ts", ], - "exclude": ["node_modules", "./dist/**", "**/*.test.*", "**/*.spec.*", "**/*.scss"] + "exclude": ["node_modules", "./dist/**", "**/*.scss"] } diff --git a/packages/playground/package.json b/packages/playground/package.json index 36aa2dc40c..4b30b4ca15 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -29,7 +29,7 @@ "dotenv": "^16.0.3", "html-webpack-plugin": "5.5.1", "postcss": "^8.4.31", - "postcss-loader": "^4.3.0", + "postcss-loader": "^7.0.0", "sass-loader": "^10.2.0", "source-map-loader": "^1.1.3", "style-loader": "^2.0.0", @@ -41,6 +41,6 @@ "whatwg-fetch": "^3.6.2" }, "dependencies": { - "@adyen/adyen-web": "5.53.3" + "@adyen/adyen-web": "5.56.1" } } diff --git a/packages/playground/src/config/commonConfig.js b/packages/playground/src/config/commonConfig.js index d28a3dd70f..8ef8892fc7 100644 --- a/packages/playground/src/config/commonConfig.js +++ b/packages/playground/src/config/commonConfig.js @@ -18,7 +18,7 @@ export const amount = { export const useSession = urlParams.session !== 'manual'; -export const returnUrl = 'http://localhost:3020/result'; +export const returnUrl = `${window.location.protocol}//localhost:3020/result`; export default { amount, diff --git a/packages/playground/src/pages/Cards/Cards.html b/packages/playground/src/pages/Cards/Cards.html index 9c15fd0e22..5e619541c1 100644 --- a/packages/playground/src/pages/Cards/Cards.html +++ b/packages/playground/src/pages/Cards/Cards.html @@ -11,7 +11,6 @@

-

Card with Click to Pay

diff --git a/packages/playground/src/pages/Cards/Cards.js b/packages/playground/src/pages/Cards/Cards.js index d65c7fc89f..a6a1c03682 100644 --- a/packages/playground/src/pages/Cards/Cards.js +++ b/packages/playground/src/pages/Cards/Cards.js @@ -10,16 +10,18 @@ import { MockReactApp } from './MockReactApp'; import getTranslationFile from '../../config/getTranslation'; import { searchFunctionExample } from '../../utils'; +const onlyShowCard = false; + const showComps = { clickToPay: true, storedCard: true, card: true, cardWithInstallments: true, + cardInReact: true, bcmcCard: true, avsCard: true, avsPartialCard: true, addressLookup: true, - cardInReact: true, kcpCard: true }; @@ -49,7 +51,7 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = }); // Stored Card - if (showComps.storedCard) { + if (!onlyShowCard && showComps.storedCard) { if (checkout.paymentMethodsResponse.storedPaymentMethods && checkout.paymentMethodsResponse.storedPaymentMethods.length > 0) { const storedCardData = checkout.paymentMethodsResponse.storedPaymentMethods[2]; @@ -67,21 +69,27 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = // }).mount('.storedcard-field'); } } - // - if (showComps.card) { - const card = new Card({ + + if (onlyShowCard || showComps.card) { + window.card = new Card({ core: checkout, challengeWindowSize: '01', - _disableClickToPay: true + _disableClickToPay: true, // hasHolderName: true, // holderNameRequired: true, // maskSecurityCode: true, // enableStoreDetails: true + onError: obj => { + console.log('### Cards::onError:: obj=', obj); + }, + onBinLookup: obj => { + console.log('### Cards::onBinLookup:: obj=', obj); + } }).mount('.card-field'); } // Credit card with installments - if (showComps.cardWithInstallments) { + if (!onlyShowCard && showComps.cardWithInstallments) { window.cardWithInstallments = new Card({ core: checkout, _disableClickToPay: true, @@ -111,18 +119,18 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = // // Card mounted in a React app - if (showComps.cardInReact) { + if (!onlyShowCard && showComps.cardInReact) { window.cardReact = new Card({ core: checkout }); MockReactApp(window, 'cardReact', document.querySelector('.react-card-field'), false); } // Bancontact card - if (showComps.bcmcCard) { + if (!onlyShowCard && showComps.bcmcCard) { window.bancontact = new Bancontact({ core: checkout }).mount('.bancontact-field'); } // Credit card with AVS - if (showComps.avsCard) { + if (!onlyShowCard && showComps.avsCard) { window.cardAvs = new Card({ core: checkout, // type: 'scheme', @@ -158,7 +166,16 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = }).mount('.card-avs-field'); } - if (showComps.avsPartialCard) { + if (!onlyShowCard && showComps.addressLookup) { + window.addressLookupCard = new Card({ + core: checkout, + brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro'], + billingAddressRequired: true, + onAddressLookup: searchFunctionExample + }).mount('.card-address-lookup-field'); + } + + if (!onlyShowCard && showComps.avsPartialCard) { window.avsPartialCard = new Card({ core: checkout, // type: 'scheme', @@ -172,17 +189,8 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = }).mount('.card-avs-partial-field'); } - if (showComps.addressLookup) { - window.addressLookupCard = new Card({ - core: checkout, - brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro'], - billingAddressRequired: true, - onAddressLookup: searchFunctionExample - }).mount('.card-address-lookup-field'); - } - // Credit card with KCP Authentication - if (showComps.kcpCard) { + if (!onlyShowCard && showComps.kcpCard) { window.kcpCard = new Card({ core: checkout, // type: 'scheme', @@ -196,7 +204,7 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = }).mount('.card-kcp-field'); } - if (showComps.clickToPay) { + if (!onlyShowCard && showComps.clickToPay) { /** * Make sure that the initialization values are being set in the /paymentMethods response, * as part of the 'scheme' configuration object diff --git a/packages/playground/src/pages/Dropin/manual.js b/packages/playground/src/pages/Dropin/manual.js index 281691e6ae..b536c29bfe 100644 --- a/packages/playground/src/pages/Dropin/manual.js +++ b/packages/playground/src/pages/Dropin/manual.js @@ -1,7 +1,7 @@ import { AdyenCheckout, Dropin, Card, GooglePay, PayPal, Ach, Affirm, WeChat, Giftcard, AmazonPay } from '@adyen/adyen-web'; import '@adyen/adyen-web/styles/adyen.css'; import { getPaymentMethods, makePayment, checkBalance, createOrder, cancelOrder, makeDetailsCall } from '../../services'; -import { amount, shopperLocale, countryCode, returnUrl } from '../../config/commonConfig'; +import { amount, shopperLocale, countryCode } from '../../config/commonConfig'; import { getSearchParameters } from '../../utils'; import getTranslationFile from '../../config/getTranslation'; diff --git a/yarn.lock b/yarn.lock index f0a02f4dda..bc9c54b1d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,11 +7,16 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@adobe/css-tools@^4.3.0-rc.1", "@adobe/css-tools@^4.3.1": +"@adobe/css-tools@^4.3.0-rc.1": version "4.3.1" resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28" integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg== +"@adobe/css-tools@^4.3.2": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.2.tgz#a6abc715fb6884851fca9dad37fc34739a04fd11" + integrity sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw== + "@adyen/bento-design-tokens@0.5.4": version "0.5.4" resolved "https://registry.yarnpkg.com/@adyen/bento-design-tokens/-/bento-design-tokens-0.5.4.tgz#07584bed6c4f1cb121ddf5d2dc085aa63989059c" @@ -54,12 +59,20 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" +"@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5": version "7.21.9" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.9.tgz#10a2e7fda4e51742c907938ac3b7229426515514" integrity sha512-FUGed8kfhyWvbYug/Un/VPJD41rDIgoVVcR+FuzhzOYyRz5uED+Gd3SLZml0Uw2l2aHFb7ZgdW5mGA3G2cCCnQ== -"@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6": +"@babel/compat-data@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.6.tgz#15606a20341de59ba02cd2fcc5086fcbe73bf544" integrity sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg== @@ -69,6 +82,16 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== +"@babel/compat-data@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.3.tgz#3febd552541e62b5e883a25eb3effd7c7379db11" + integrity sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ== + +"@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== + "@babel/core@^7.11.6", "@babel/core@^7.12.1", "@babel/core@^7.12.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.3.tgz#5ec09c8803b91f51cc887dedc2654a35852849c9" @@ -90,7 +113,7 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/core@^7.13.16", "@babel/core@^7.22.1": +"@babel/core@^7.22.1": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.6.tgz#aafafbe86e9a1679d876b99dc46382964ef72494" integrity sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw== @@ -111,25 +134,25 @@ gensync "^1.0.0-beta.2" json5 "^2.2.2" -"@babel/core@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" - integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== +"@babel/core@^7.23.0", "@babel/core@^7.23.2": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.6.tgz#8be77cd77c55baadcc1eae1c33df90ab6d2151d4" + integrity sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.9" - "@babel/helper-module-transforms" "^7.22.9" - "@babel/helpers" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.8" - "@babel/types" "^7.22.5" - convert-source-map "^1.7.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.6" + "@babel/parser" "^7.23.6" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.6" + "@babel/types" "^7.23.6" + convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.2" + json5 "^2.2.3" semver "^6.3.1" "@babel/generator@^7.22.5": @@ -142,16 +165,6 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/generator@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" - integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== - dependencies: - "@babel/types" "^7.22.5" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - "@babel/generator@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" @@ -172,6 +185,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== + dependencies: + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/generator@^7.7.2": version "7.21.9" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.9.tgz#3a1b706e07d836e204aee0650e8ee878d3aaa241" @@ -189,21 +212,25 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": +"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6", "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== dependencies: "@babel/types" "^7.22.15" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz#a3f4758efdd0190d8927fcffd261755937c71878" - integrity sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" + integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== dependencies: - "@babel/types" "^7.22.5" + "@babel/compat-data" "^7.21.5" + "@babel/helper-validator-option" "^7.21.0" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.21.5", "@babel/helper-compilation-targets@^7.22.15": +"@babel/helper-compilation-targets@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== @@ -214,18 +241,7 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-compilation-targets@^7.20.7": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" - integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== - dependencies: - "@babel/compat-data" "^7.21.5" - "@babel/helper-validator-option" "^7.21.0" - browserslist "^4.21.3" - lru-cache "^5.1.1" - semver "^6.3.0" - -"@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": +"@babel/helper-compilation-targets@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz#e30d61abe9480aa5a83232eb31c111be922d2e52" integrity sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA== @@ -236,14 +252,14 @@ browserslist "^4.21.9" lru-cache "^5.1.1" -"@babel/helper-compilation-targets@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" - integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.5" - browserslist "^4.21.9" + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" lru-cache "^5.1.1" semver "^6.3.1" @@ -316,7 +332,7 @@ regexpu-core "^5.3.1" semver "^6.3.0" -"@babel/helper-create-regexp-features-plugin@^7.20.5": +"@babel/helper-create-regexp-features-plugin@^7.20.5", "@babel/helper-create-regexp-features-plugin@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== @@ -346,17 +362,6 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-define-polyfill-provider@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz#82c825cadeeeee7aad237618ebbe8fa1710015d7" - integrity sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw== - dependencies: - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-plugin-utils" "^7.22.5" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - "@babel/helper-define-polyfill-provider@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz#a71c10f7146d809f4a256c373f462d9bba8cf6ba" @@ -507,7 +512,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== -"@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.22.5": +"@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== @@ -522,24 +527,26 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-remap-async-to-generator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz#14a38141a7bf2165ad38da61d61cf27b43015da2" - integrity sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g== +"@babel/helper-remap-async-to-generator@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-wrap-function" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.22.9": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" - integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== +"@babel/helper-replace-supers@^7.18.6": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz#a6ad005ba1c7d9bc2973dfde05a1bba7065dde3c" + integrity sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg== dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-member-expression-to-functions" "^7.22.15" - "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-member-expression-to-functions" "^7.21.5" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" "@babel/helper-replace-supers@^7.21.5": version "7.22.9" @@ -550,6 +557,15 @@ "@babel/helper-member-expression-to-functions" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" +"@babel/helper-replace-supers@^7.22.20", "@babel/helper-replace-supers@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz#71bc5fb348856dea9fdc4eafd7e2e49f585145dc" @@ -595,7 +611,17 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.22.15": +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + +"@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-identifier@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044" integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ== @@ -625,6 +651,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + "@babel/helper-wrap-function@^7.18.9": version "7.22.17" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.17.tgz#222ac3ff9cc8f9b617cc1e5db75c0b538e722801" @@ -634,15 +665,14 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.17" -"@babel/helper-wrap-function@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz#44d205af19ed8d872b4eefb0d2fa65f45eb34f06" - integrity sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw== +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== dependencies: "@babel/helper-function-name" "^7.22.5" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.19" "@babel/helpers@^7.22.6": version "7.22.6" @@ -662,6 +692,15 @@ "@babel/traverse" "^7.23.2" "@babel/types" "^7.23.0" +"@babel/helpers@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.6.tgz#d03af2ee5fb34691eec0cda90f5ecbb4d4da145a" + integrity sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.6" + "@babel/types" "^7.23.6" + "@babel/highlight@^7.18.6", "@babel/highlight@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" @@ -680,31 +719,40 @@ chalk "^2.4.2" js-tokens "^4.0.0" +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.9": version "7.21.9" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.9.tgz#ab18ea3b85b4bc33ba98a8d4c2032c557d23cf14" integrity sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g== -"@babel/parser@^7.13.16", "@babel/parser@^7.22.5", "@babel/parser@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.6.tgz#201f8b47be20c76c7c5743b9c16129760bf9a975" - integrity sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw== - "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== -"@babel/parser@^7.22.7": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" - integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== +"@babel/parser@^7.22.5", "@babel/parser@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.6.tgz#201f8b47be20c76c7c5743b9c16129760bf9a975" + integrity sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw== "@babel/parser@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.3.tgz#0ce0be31a4ca4f1884b5786057cadcb6c3be58f9" integrity sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw== +"@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962" @@ -712,10 +760,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" - integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a" + integrity sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -728,14 +776,22 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-optional-chaining" "^7.22.15" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca" - integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz#f6652bb16b94f8f9c20c50941e16e9756898dc5d" + integrity sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.23.3" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz#20c60d4639d18f7da8602548512e9d3a4c8d7098" + integrity sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-proposal-async-generator-functions@^7.12.1", "@babel/plugin-proposal-async-generator-functions@^7.20.7": version "7.20.7" @@ -747,7 +803,7 @@ "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@^7.12.1", "@babel/plugin-proposal-class-properties@^7.13.0", "@babel/plugin-proposal-class-properties@^7.18.6": +"@babel/plugin-proposal-class-properties@^7.12.1", "@babel/plugin-proposal-class-properties@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== @@ -807,7 +863,7 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": +"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== @@ -842,7 +898,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.21.0": +"@babel/plugin-proposal-optional-chaining@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== @@ -931,24 +987,38 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-flow@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.22.5.tgz#163b820b9e7696ce134df3ee716d9c0c98035859" - integrity sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ== +"@babel/plugin-syntax-flow@^7.18.6": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz#3e37fca4f06d93567c1cd9b75156422e90a67107" + integrity sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-syntax-flow@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz#084564e0f3cc21ea6c70c44cff984a1c0509729a" + integrity sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-syntax-import-assertions@^7.20.0", "@babel/plugin-syntax-import-assertions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" - integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== +"@babel/plugin-syntax-import-assertions@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" + integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/plugin-syntax-import-assertions@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz#9c05a7f592982aff1a2768260ad84bcd3f0c77fc" + integrity sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-syntax-import-attributes@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" - integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg== +"@babel/plugin-syntax-import-attributes@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz#992aee922cf04512461d7dae3ff6951b90a2dc06" + integrity sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1065,21 +1135,28 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-arrow-functions@^7.21.5", "@babel/plugin-transform-arrow-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" - integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== +"@babel/plugin-transform-arrow-functions@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" + integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + +"@babel/plugin-transform-arrow-functions@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz#94c6dcfd731af90f27a79509f9ab7fb2120fc38b" + integrity sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-async-generator-functions@^7.22.7": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz#053e76c0a903b72b573cb1ab7d6882174d460a1b" - integrity sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg== +"@babel/plugin-transform-async-generator-functions@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz#93ac8e3531f347fba519b4703f9ff2a75c6ae27a" + integrity sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw== dependencies: - "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-remap-async-to-generator" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.20" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-transform-async-to-generator@^7.12.1", "@babel/plugin-transform-async-to-generator@^7.20.7": @@ -1091,14 +1168,14 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-remap-async-to-generator" "^7.18.9" -"@babel/plugin-transform-async-to-generator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" - integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== +"@babel/plugin-transform-async-to-generator@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz#d1f513c7a8a506d43f47df2bf25f9254b0b051fa" + integrity sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw== dependencies: - "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-remap-async-to-generator" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.20" "@babel/plugin-transform-block-scoped-functions@^7.18.6": version "7.18.6" @@ -1107,24 +1184,24 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoped-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" - integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== +"@babel/plugin-transform-block-scoped-functions@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz#fe1177d715fb569663095e04f3598525d98e8c77" + integrity sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-block-scoping@^7.21.0": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz#494eb82b87b5f8b1d8f6f28ea74078ec0a10a841" - integrity sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" + integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-block-scoping@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz#8bfc793b3a4b2742c0983fadc1480d843ecea31b" - integrity sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg== +"@babel/plugin-transform-block-scoping@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz#b2d38589531c6c80fbe25e6b58e763622d2d3cf5" + integrity sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1136,12 +1213,20 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-class-static-block@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba" - integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA== +"@babel/plugin-transform-class-properties@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz#35c377db11ca92a785a718b6aa4e3ed1eb65dc48" + integrity sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-static-block@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz#2a202c8787a8964dd11dfcedf994d36bfc844ab5" + integrity sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" @@ -1160,18 +1245,18 @@ "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" -"@babel/plugin-transform-classes@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363" - integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ== +"@babel/plugin-transform-classes@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz#e7a75f815e0c534cc4c9a39c56636c84fc0d64f2" + integrity sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" @@ -1183,13 +1268,13 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/template" "^7.20.7" -"@babel/plugin-transform-computed-properties@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" - integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== +"@babel/plugin-transform-computed-properties@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz#652e69561fcc9d2b50ba4f7ac7f60dcf65e86474" + integrity sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" - "@babel/template" "^7.22.5" + "@babel/template" "^7.22.15" "@babel/plugin-transform-destructuring@^7.21.3": version "7.22.15" @@ -1198,10 +1283,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-destructuring@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz#d3aca7438f6c26c78cdd0b0ba920a336001b27cc" - integrity sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ== +"@babel/plugin-transform-destructuring@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz#8c9ee68228b12ae3dff986e56ed1ba4f3c446311" + integrity sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1213,12 +1298,12 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-dotall-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" - integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== +"@babel/plugin-transform-dotall-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz#3f7af6054882ede89c378d0cf889b854a993da50" + integrity sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-duplicate-keys@^7.18.9": @@ -1228,22 +1313,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-duplicate-keys@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" - integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== +"@babel/plugin-transform-duplicate-keys@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz#664706ca0a5dfe8d066537f99032fc1dc8b720ce" + integrity sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-dynamic-import@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e" - integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ== +"@babel/plugin-transform-dynamic-import@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz#c7629e7254011ac3630d47d7f34ddd40ca535143" + integrity sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-transform-exponentiation-operator@^7.12.1": +"@babel/plugin-transform-exponentiation-operator@^7.12.1", "@babel/plugin-transform-exponentiation-operator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== @@ -1251,29 +1336,37 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-exponentiation-operator@^7.18.6", "@babel/plugin-transform-exponentiation-operator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" - integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== +"@babel/plugin-transform-exponentiation-operator@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz#ea0d978f6b9232ba4722f3dbecdd18f450babd18" + integrity sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-export-namespace-from@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b" - integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg== +"@babel/plugin-transform-export-namespace-from@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz#084c7b25e9a5c8271e987a08cf85807b80283191" + integrity sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-transform-flow-strip-types@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.22.5.tgz#0bb17110c7bf5b35a60754b2f00c58302381dee2" - integrity sha512-tujNbZdxdG0/54g/oua8ISToaXTFBf8EnSb5PgQSciIXWOWKX3S4+JR7ZE9ol8FZwf9kxitzkGQ+QWeov/mCiA== +"@babel/plugin-transform-flow-strip-types@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz#6aeca0adcb81dc627c8986e770bfaa4d9812aff5" + integrity sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-flow" "^7.18.6" + +"@babel/plugin-transform-flow-strip-types@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz#cfa7ca159cc3306fab526fc67091556b51af26ff" + integrity sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q== dependencies: "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-flow" "^7.22.5" + "@babel/plugin-syntax-flow" "^7.23.3" "@babel/plugin-transform-for-of@^7.12.1": version "7.21.5" @@ -1289,12 +1382,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-for-of@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" - integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== +"@babel/plugin-transform-for-of@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e" + integrity sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" @@ -1305,19 +1399,19 @@ "@babel/helper-function-name" "^7.18.9" "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" - integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== +"@babel/plugin-transform-function-name@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz#8f424fcd862bf84cb9a1a6b42bc2f47ed630f8dc" + integrity sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw== dependencies: - "@babel/helper-compilation-targets" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-function-name" "^7.23.0" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-json-strings@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0" - integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A== +"@babel/plugin-transform-json-strings@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz#a871d9b6bd171976efad2e43e694c961ffa3714d" + integrity sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" @@ -1329,17 +1423,17 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" - integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== +"@babel/plugin-transform-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz#8214665f00506ead73de157eba233e7381f3beb4" + integrity sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-logical-assignment-operators@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c" - integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA== +"@babel/plugin-transform-logical-assignment-operators@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz#e599f82c51d55fac725f62ce55d3a0886279ecb5" + integrity sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" @@ -1351,10 +1445,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-member-expression-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" - integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== +"@babel/plugin-transform-member-expression-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz#e37b3f0502289f477ac0e776b05a833d853cabcc" + integrity sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1366,22 +1460,13 @@ "@babel/helper-module-transforms" "^7.20.11" "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-modules-amd@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526" - integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ== - dependencies: - "@babel/helper-module-transforms" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" - integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== +"@babel/plugin-transform-modules-amd@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz#e19b55436a1416829df0a1afc495deedfae17f7d" + integrity sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw== dependencies: - "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.3" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-simple-access" "^7.22.5" "@babel/plugin-transform-modules-commonjs@^7.21.5": version "7.22.15" @@ -1392,7 +1477,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" -"@babel/plugin-transform-modules-commonjs@^7.23.3": +"@babel/plugin-transform-modules-commonjs@^7.23.0", "@babel/plugin-transform-modules-commonjs@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz#661ae831b9577e52be57dd8356b734f9700b53b4" integrity sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA== @@ -1411,15 +1496,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.5" -"@babel/plugin-transform-modules-systemjs@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" - integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== +"@babel/plugin-transform-modules-systemjs@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz#fa7e62248931cb15b9404f8052581c302dd9de81" + integrity sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ== dependencies: "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.3" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" "@babel/plugin-transform-modules-umd@^7.18.6": version "7.18.6" @@ -1429,12 +1514,12 @@ "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-modules-umd@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" - integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== +"@babel/plugin-transform-modules-umd@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz#5d4395fccd071dfefe6585a4411aa7d6b7d769e9" + integrity sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg== dependencies: - "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.3" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-named-capturing-groups-regex@^7.20.5": @@ -1453,39 +1538,46 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-new-target@^7.18.6", "@babel/plugin-transform-new-target@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" - integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== +"@babel/plugin-transform-new-target@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" + integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-new-target@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz#5491bb78ed6ac87e990957cea367eab781c4d980" + integrity sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381" - integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA== +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11", "@babel/plugin-transform-nullish-coalescing-operator@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz#45556aad123fc6e52189ea749e33ce090637346e" + integrity sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-numeric-separator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58" - integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g== +"@babel/plugin-transform-numeric-separator@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz#03d08e3691e405804ecdd19dd278a40cca531f29" + integrity sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-rest-spread@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1" - integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ== +"@babel/plugin-transform-object-rest-spread@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz#2b9c2d26bf62710460bdc0d1730d4f1048361b83" + integrity sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g== dependencies: - "@babel/compat-data" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.5" + "@babel/compat-data" "^7.23.3" + "@babel/helper-compilation-targets" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.22.5" + "@babel/plugin-transform-parameters" "^7.23.3" "@babel/plugin-transform-object-super@^7.18.6": version "7.18.6" @@ -1495,18 +1587,18 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-object-super@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" - integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== +"@babel/plugin-transform-object-super@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz#81fdb636dcb306dd2e4e8fd80db5b2362ed2ebcd" + integrity sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" -"@babel/plugin-transform-optional-catch-binding@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333" - integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg== +"@babel/plugin-transform-optional-catch-binding@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz#318066de6dacce7d92fa244ae475aa8d91778017" + integrity sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" @@ -1520,10 +1612,10 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.22.5", "@babel/plugin-transform-optional-chaining@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz#4bacfe37001fe1901117672875e931d439811564" - integrity sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg== +"@babel/plugin-transform-optional-chaining@^7.23.0", "@babel/plugin-transform-optional-chaining@^7.23.3", "@babel/plugin-transform-optional-chaining@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz#6acf61203bdfc4de9d4e52e64490aeb3e52bd017" + integrity sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -1536,10 +1628,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-parameters@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" - integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== +"@babel/plugin-transform-parameters@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz#83ef5d1baf4b1072fa6e54b2b0999a7b2527e2af" + integrity sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1551,13 +1643,21 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-private-property-in-object@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32" - integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ== +"@babel/plugin-transform-private-methods@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz#b2d7a3c97e278bfe59137a978d53b2c2e038c0e4" + integrity sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-private-property-in-object@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz#3ec711d05d6608fd173d9b8de39872d8dbf68bf5" + integrity sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" @@ -1568,10 +1668,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-property-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" - integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== +"@babel/plugin-transform-property-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz#54518f14ac4755d22b92162e4a852d308a560875" + integrity sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1627,13 +1727,13 @@ "@babel/helper-plugin-utils" "^7.22.5" regenerator-transform "^0.15.2" -"@babel/plugin-transform-regenerator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz#cd8a68b228a5f75fa01420e8cc2fc400f0fc32aa" - integrity sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw== +"@babel/plugin-transform-regenerator@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz#141afd4a2057298602069fce7f2dc5173e6c561c" + integrity sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" - regenerator-transform "^0.15.1" + regenerator-transform "^0.15.2" "@babel/plugin-transform-reserved-words@^7.18.6": version "7.18.6" @@ -1642,10 +1742,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-reserved-words@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" - integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== +"@babel/plugin-transform-reserved-words@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz#4130dcee12bd3dd5705c587947eb715da12efac8" + integrity sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1661,10 +1761,17 @@ babel-plugin-polyfill-regenerator "^0.5.3" semver "^6.3.1" -"@babel/plugin-transform-shorthand-properties@^7.18.6", "@babel/plugin-transform-shorthand-properties@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" - integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== +"@babel/plugin-transform-shorthand-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-shorthand-properties@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz#97d82a39b0e0c24f8a981568a8ed851745f59210" + integrity sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1676,10 +1783,10 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" -"@babel/plugin-transform-spread@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" - integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== +"@babel/plugin-transform-spread@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz#41d17aacb12bde55168403c6f2d6bdca563d362c" + integrity sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -1691,10 +1798,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-sticky-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" - integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== +"@babel/plugin-transform-sticky-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz#dec45588ab4a723cb579c609b294a3d1bd22ff04" + integrity sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1705,10 +1812,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-template-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" - integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== +"@babel/plugin-transform-template-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz#5f0f028eb14e50b5d0f76be57f90045757539d07" + integrity sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1719,10 +1826,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-typeof-symbol@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" - integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== +"@babel/plugin-transform-typeof-symbol@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz#9dfab97acc87495c0c449014eb9c547d8966bca4" + integrity sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1743,19 +1850,19 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-escapes@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz#ce0c248522b1cb22c7c992d88301a5ead70e806c" - integrity sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg== +"@babel/plugin-transform-unicode-escapes@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz#1f66d16cab01fab98d784867d24f70c1ca65b925" + integrity sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-property-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" - integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A== +"@babel/plugin-transform-unicode-property-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz#19e234129e5ffa7205010feec0d94c251083d7ad" + integrity sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-unicode-regex@^7.18.6": @@ -1766,20 +1873,20 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-unicode-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" - integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== +"@babel/plugin-transform-unicode-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz#26897708d8f42654ca4ce1b73e96140fbad879dc" + integrity sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-sets-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" - integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg== +"@babel/plugin-transform-unicode-sets-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz#4fb6f0a719c2c5859d11f6b55a050cc987f3799e" + integrity sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@^7.12.1": @@ -1864,25 +1971,26 @@ core-js-compat "^3.25.1" semver "^6.3.0" -"@babel/preset-env@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.9.tgz#57f17108eb5dfd4c5c25a44c1977eba1df310ac7" - integrity sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g== +"@babel/preset-env@^7.23.2": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.23.6.tgz#ad0ea799d5a3c07db5b9a172819bbd444092187a" + integrity sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ== dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.9" + "@babel/compat-data" "^7.23.5" + "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.5" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.23.3" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.23.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.23.3" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.22.5" - "@babel/plugin-syntax-import-attributes" "^7.22.5" + "@babel/plugin-syntax-import-assertions" "^7.23.3" + "@babel/plugin-syntax-import-attributes" "^7.23.3" "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" @@ -1894,70 +2002,87 @@ "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.22.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.7" - "@babel/plugin-transform-async-to-generator" "^7.22.5" - "@babel/plugin-transform-block-scoped-functions" "^7.22.5" - "@babel/plugin-transform-block-scoping" "^7.22.5" - "@babel/plugin-transform-class-properties" "^7.22.5" - "@babel/plugin-transform-class-static-block" "^7.22.5" - "@babel/plugin-transform-classes" "^7.22.6" - "@babel/plugin-transform-computed-properties" "^7.22.5" - "@babel/plugin-transform-destructuring" "^7.22.5" - "@babel/plugin-transform-dotall-regex" "^7.22.5" - "@babel/plugin-transform-duplicate-keys" "^7.22.5" - "@babel/plugin-transform-dynamic-import" "^7.22.5" - "@babel/plugin-transform-exponentiation-operator" "^7.22.5" - "@babel/plugin-transform-export-namespace-from" "^7.22.5" - "@babel/plugin-transform-for-of" "^7.22.5" - "@babel/plugin-transform-function-name" "^7.22.5" - "@babel/plugin-transform-json-strings" "^7.22.5" - "@babel/plugin-transform-literals" "^7.22.5" - "@babel/plugin-transform-logical-assignment-operators" "^7.22.5" - "@babel/plugin-transform-member-expression-literals" "^7.22.5" - "@babel/plugin-transform-modules-amd" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.5" - "@babel/plugin-transform-modules-systemjs" "^7.22.5" - "@babel/plugin-transform-modules-umd" "^7.22.5" + "@babel/plugin-transform-arrow-functions" "^7.23.3" + "@babel/plugin-transform-async-generator-functions" "^7.23.4" + "@babel/plugin-transform-async-to-generator" "^7.23.3" + "@babel/plugin-transform-block-scoped-functions" "^7.23.3" + "@babel/plugin-transform-block-scoping" "^7.23.4" + "@babel/plugin-transform-class-properties" "^7.23.3" + "@babel/plugin-transform-class-static-block" "^7.23.4" + "@babel/plugin-transform-classes" "^7.23.5" + "@babel/plugin-transform-computed-properties" "^7.23.3" + "@babel/plugin-transform-destructuring" "^7.23.3" + "@babel/plugin-transform-dotall-regex" "^7.23.3" + "@babel/plugin-transform-duplicate-keys" "^7.23.3" + "@babel/plugin-transform-dynamic-import" "^7.23.4" + "@babel/plugin-transform-exponentiation-operator" "^7.23.3" + "@babel/plugin-transform-export-namespace-from" "^7.23.4" + "@babel/plugin-transform-for-of" "^7.23.6" + "@babel/plugin-transform-function-name" "^7.23.3" + "@babel/plugin-transform-json-strings" "^7.23.4" + "@babel/plugin-transform-literals" "^7.23.3" + "@babel/plugin-transform-logical-assignment-operators" "^7.23.4" + "@babel/plugin-transform-member-expression-literals" "^7.23.3" + "@babel/plugin-transform-modules-amd" "^7.23.3" + "@babel/plugin-transform-modules-commonjs" "^7.23.3" + "@babel/plugin-transform-modules-systemjs" "^7.23.3" + "@babel/plugin-transform-modules-umd" "^7.23.3" "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" - "@babel/plugin-transform-new-target" "^7.22.5" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5" - "@babel/plugin-transform-numeric-separator" "^7.22.5" - "@babel/plugin-transform-object-rest-spread" "^7.22.5" - "@babel/plugin-transform-object-super" "^7.22.5" - "@babel/plugin-transform-optional-catch-binding" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.6" - "@babel/plugin-transform-parameters" "^7.22.5" - "@babel/plugin-transform-private-methods" "^7.22.5" - "@babel/plugin-transform-private-property-in-object" "^7.22.5" - "@babel/plugin-transform-property-literals" "^7.22.5" - "@babel/plugin-transform-regenerator" "^7.22.5" - "@babel/plugin-transform-reserved-words" "^7.22.5" - "@babel/plugin-transform-shorthand-properties" "^7.22.5" - "@babel/plugin-transform-spread" "^7.22.5" - "@babel/plugin-transform-sticky-regex" "^7.22.5" - "@babel/plugin-transform-template-literals" "^7.22.5" - "@babel/plugin-transform-typeof-symbol" "^7.22.5" - "@babel/plugin-transform-unicode-escapes" "^7.22.5" - "@babel/plugin-transform-unicode-property-regex" "^7.22.5" - "@babel/plugin-transform-unicode-regex" "^7.22.5" - "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.22.5" - babel-plugin-polyfill-corejs2 "^0.4.4" - babel-plugin-polyfill-corejs3 "^0.8.2" - babel-plugin-polyfill-regenerator "^0.5.1" + "@babel/plugin-transform-new-target" "^7.23.3" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.23.4" + "@babel/plugin-transform-numeric-separator" "^7.23.4" + "@babel/plugin-transform-object-rest-spread" "^7.23.4" + "@babel/plugin-transform-object-super" "^7.23.3" + "@babel/plugin-transform-optional-catch-binding" "^7.23.4" + "@babel/plugin-transform-optional-chaining" "^7.23.4" + "@babel/plugin-transform-parameters" "^7.23.3" + "@babel/plugin-transform-private-methods" "^7.23.3" + "@babel/plugin-transform-private-property-in-object" "^7.23.4" + "@babel/plugin-transform-property-literals" "^7.23.3" + "@babel/plugin-transform-regenerator" "^7.23.3" + "@babel/plugin-transform-reserved-words" "^7.23.3" + "@babel/plugin-transform-shorthand-properties" "^7.23.3" + "@babel/plugin-transform-spread" "^7.23.3" + "@babel/plugin-transform-sticky-regex" "^7.23.3" + "@babel/plugin-transform-template-literals" "^7.23.3" + "@babel/plugin-transform-typeof-symbol" "^7.23.3" + "@babel/plugin-transform-unicode-escapes" "^7.23.3" + "@babel/plugin-transform-unicode-property-regex" "^7.23.3" + "@babel/plugin-transform-unicode-regex" "^7.23.3" + "@babel/plugin-transform-unicode-sets-regex" "^7.23.3" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.6" + babel-plugin-polyfill-corejs3 "^0.8.5" + babel-plugin-polyfill-regenerator "^0.5.3" core-js-compat "^3.31.0" semver "^6.3.1" -"@babel/preset-flow@^7.12.1", "@babel/preset-flow@^7.13.13": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.22.5.tgz#876f24ab6b38bd79703a93f32020ca2162312784" - integrity sha512-ta2qZ+LSiGCrP5pgcGt8xMnnkXQrq8Sa4Ulhy06BOlF5QbLw9q5hIx7bn5MrsvyTGAfh6kTOo07Q+Pfld/8Y5Q== +"@babel/preset-flow@^7.12.1": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.21.4.tgz#a5de2a1cafa61f0e0b3af9b30ff0295d38d3608f" + integrity sha512-F24cSq4DIBmhq4OzK3dE63NHagb27OPE3eWR+HLekt4Z3Y5MzIIUGF3LlLgV0gN8vzbDViSY7HnrReNVCJXTeA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.21.0" + "@babel/plugin-transform-flow-strip-types" "^7.21.0" + +"@babel/preset-flow@^7.22.15": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.23.3.tgz#8084e08b9ccec287bd077ab288b286fab96ffab1" + integrity sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" - "@babel/plugin-transform-flow-strip-types" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-transform-flow-strip-types" "^7.23.3" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" "@babel/preset-modules@^0.1.5": version "0.1.5" @@ -1982,7 +2107,7 @@ "@babel/plugin-transform-react-jsx-development" "^7.22.5" "@babel/plugin-transform-react-pure-annotations" "^7.23.3" -"@babel/preset-typescript@^7.13.0": +"@babel/preset-typescript@^7.23.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz#14534b34ed5b6d435aa05f1ae1c5e7adcc01d913" integrity sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ== @@ -1993,10 +2118,10 @@ "@babel/plugin-transform-modules-commonjs" "^7.23.3" "@babel/plugin-transform-typescript" "^7.23.3" -"@babel/register@^7.13.16": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.22.5.tgz#e4d8d0f615ea3233a27b5c6ada6750ee59559939" - integrity sha512-vV6pm/4CijSQ8Y47RH5SopXzursN35RQINfGJkmOlcpAtGuf94miFvIPhCKGQN7WGIcsgG1BHEX2KVdTYwTwUQ== +"@babel/register@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.22.15.tgz#c2c294a361d59f5fa7bcc8b97ef7319c32ecaec7" + integrity sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -2057,7 +2182,7 @@ "@babel/parser" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/traverse@^7.22.5", "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": +"@babel/traverse@^7.21.5", "@babel/traverse@^7.22.5", "@babel/traverse@^7.22.6": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== @@ -2089,6 +2214,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5" + integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.20.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" @@ -2116,6 +2257,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.22.19", "@babel/types@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@babel/types@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" @@ -2352,20 +2502,20 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== -"@csstools/css-parser-algorithms@^2.3.0": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.2.tgz#1e0d581dbf4518cb3e939c3b863cb7180c8cedad" - integrity sha512-sLYGdAdEY2x7TSw9FtmdaTrh2wFtRJO5VMbBrA8tEqEod7GEggFmxTSK9XqExib3yMuYNcvcTdCZIP6ukdjAIA== +"@csstools/css-parser-algorithms@^2.3.1": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz#0c03cd5418a9f404a05ff2ffcb1b69d04e8ec532" + integrity sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ== -"@csstools/css-tokenizer@^2.1.1": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.2.0.tgz#9d70e6dcbe94e44c7400a2929928db35c4de32b5" - integrity sha512-wErmsWCbsmig8sQKkM6pFhr/oPha1bHfvxsUY5CYSQxwyhA9Ulrs8EqCgClhg4Tgg2XapVstGqSVcz0xOYizZA== +"@csstools/css-tokenizer@^2.2.0": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz#b099d543ea57b64f495915a095ead583866c50c6" + integrity sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg== -"@csstools/media-query-list-parser@^2.1.2": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.5.tgz#94bc8b3c3fd7112a40b7bf0b483e91eba0654a0f" - integrity sha512-IxVBdYzR8pYe89JiyXQuYk4aVVoCPhMJkz6ElRwlVysjwURTsTk/bmY/z4FfeRE+CRBMlykPwXEVUg8lThv7AQ== +"@csstools/media-query-list-parser@^2.1.4": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.7.tgz#a4836e3dbd693081a30b32ce9c2a781e1be16788" + integrity sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ== "@csstools/selector-specificity@^3.0.0": version "3.0.0" @@ -2546,10 +2696,25 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.52.0.tgz#78fe5f117840f69dc4a353adf9b9cd926353378c" - integrity sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA== +"@eslint/eslintrc@^2.1.3": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.53.0": + version "8.53.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d" + integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w== "@eslint/js@^8.47.0": version "8.47.0" @@ -2710,7 +2875,7 @@ "@types/node" "*" jest-mock "^29.7.0" -"@jest/expect-utils@^29.5.0", "@jest/expect-utils@^29.7.0": +"@jest/expect-utils@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== @@ -3577,205 +3742,140 @@ nanoid "^3.3.6" webpack "^5.88.2" -"@storybook/addon-a11y@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-7.4.6.tgz#b1d1d98e11d1130d98b737decbc27eaa2a0bf9b3" - integrity sha512-RE8sXk9KEqgmjsFmG31eObgPMTOvvWnoNZIaZEHs88X30tNHtFwjc0jzvCR/xriKsBtQdYQTUSsB7pSjaJHNzQ== +"@storybook/addon-a11y@^7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-7.6.6.tgz#8689deea09382903c884290f5a297c3a0bb97aa9" + integrity sha512-D7TlxmPB4qoqN1q1WoVvRCIAB6RhDN7s0JL4qvznK3cdDDkQyfk3zvip97NhbT5ZSLjpszyElV9CfWht1O341w== dependencies: - "@storybook/addon-highlight" "7.4.6" - "@storybook/channels" "7.4.6" - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/core-events" "7.4.6" - "@storybook/global" "^5.0.0" - "@storybook/manager-api" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/theming" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/addon-highlight" "7.6.6" axe-core "^4.2.0" - lodash "^4.17.21" - react-resize-detector "^7.1.2" -"@storybook/addon-actions@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-7.4.6.tgz#080bd1612a744cc3fc4a435a07a63d2d8c05f030" - integrity sha512-SsqZr3js5NinKPnC8AeNI7Ij+Q6fIl9tRdRmSulEgjksjOg7E5S1/Wsn5Bb2CCgj7MaX6VxGyC7s3XskQtDiIQ== +"@storybook/addon-actions@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-7.6.6.tgz#37b183af6303db8b44a7d9e9620aaca991a51a62" + integrity sha512-mLJip9Evb2Chj7ymKbpaybe5NgDy3Du7oSWeURPy/0qXJ2cBqHWnhZ8CTK2DasrstsUhQSJaZVXHhaENT+fn+g== dependencies: - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/core-events" "7.4.6" + "@storybook/core-events" "7.6.6" "@storybook/global" "^5.0.0" - "@storybook/manager-api" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/theming" "7.4.6" - "@storybook/types" "7.4.6" + "@types/uuid" "^9.0.1" dequal "^2.0.2" - lodash "^4.17.21" polished "^4.2.2" - prop-types "^15.7.2" - react-inspector "^6.0.0" - telejson "^7.2.0" - ts-dedent "^2.0.0" uuid "^9.0.0" -"@storybook/addon-backgrounds@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-7.4.6.tgz#315ac726e606259ce868b78c7b966cc9489b51ea" - integrity sha512-+LHTZB/ZYMAzkyD5ZxSriBsqmsrvIaW/Nnd/BeuXGbkrVKKqM0qAKiFZAfjc2WchA1piVNy0/1Rsf+kuYCEiJw== +"@storybook/addon-backgrounds@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.6.tgz#e4e5edb5dc6ea61bb19d259660800ee588f9ab21" + integrity sha512-w5dZ/0cOe55M2G/Lx9f+Ptk4txUPb+Ng+KqEvTaTNqHoh0Xw4QxEn/ciJwmh1u1g3aMZsOgOvwPwug7ykmLgsA== dependencies: - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/core-events" "7.4.6" "@storybook/global" "^5.0.0" - "@storybook/manager-api" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/theming" "7.4.6" - "@storybook/types" "7.4.6" memoizerific "^1.11.3" ts-dedent "^2.0.0" -"@storybook/addon-controls@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-7.4.6.tgz#b1db7a0faacb25b9a6f54c2dff2ba94d06619bd4" - integrity sha512-4lq3sycEUIsK8SUWDYc60QgF4vV9FZZ3lDr6M7j2W9bOnvGw49d2fbdlnq+bX1ZprZZ9VgglQpBAorQB3BXZRw== - dependencies: - "@storybook/blocks" "7.4.6" - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/core-common" "7.4.6" - "@storybook/core-events" "7.4.6" - "@storybook/manager-api" "7.4.6" - "@storybook/node-logger" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/theming" "7.4.6" - "@storybook/types" "7.4.6" +"@storybook/addon-controls@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-7.6.6.tgz#a8b4619fb4b7672cceeb7744d78eadab9434e434" + integrity sha512-VAXXfPLi1M3RXhBf3uIBZ2hrD9UPDe7yvXHIlCzgj1HIJELODCFyUc+RtvN0mPc/nnlEfzhGfJtenZou5LYwIw== + dependencies: + "@storybook/blocks" "7.6.6" lodash "^4.17.21" ts-dedent "^2.0.0" -"@storybook/addon-docs@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-7.4.6.tgz#f2cc635a77cfb3e2910d6ca813add9a16785595d" - integrity sha512-dLaub+XWFq4hChw+xfuF9yYg0Txp77FUawKoAigccfjWXx+OOhRV3XTuAcknpXkYq94GWynHgUFXosXT9kbDNA== +"@storybook/addon-docs@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-7.6.6.tgz#3224071f6ea44b00727e344a8020ddc4313ccdae" + integrity sha512-l4gtoNTn1wHE11x44te1cDkqfm+/w+eNonHe56bwgSqETclS5z18wvM9bQZF32G6C9fpSefaJW3cxVvcuJL1fg== dependencies: "@jest/transform" "^29.3.1" "@mdx-js/react" "^2.1.5" - "@storybook/blocks" "7.4.6" - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/csf-plugin" "7.4.6" - "@storybook/csf-tools" "7.4.6" + "@storybook/blocks" "7.6.6" + "@storybook/client-logger" "7.6.6" + "@storybook/components" "7.6.6" + "@storybook/csf-plugin" "7.6.6" + "@storybook/csf-tools" "7.6.6" "@storybook/global" "^5.0.0" "@storybook/mdx2-csf" "^1.0.0" - "@storybook/node-logger" "7.4.6" - "@storybook/postinstall" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/react-dom-shim" "7.4.6" - "@storybook/theming" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/node-logger" "7.6.6" + "@storybook/postinstall" "7.6.6" + "@storybook/preview-api" "7.6.6" + "@storybook/react-dom-shim" "7.6.6" + "@storybook/theming" "7.6.6" + "@storybook/types" "7.6.6" fs-extra "^11.1.0" remark-external-links "^8.0.0" remark-slug "^6.0.0" ts-dedent "^2.0.0" -"@storybook/addon-essentials@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-7.4.6.tgz#b9c83dbdae0ffd6f24fa9328b36488d563714260" - integrity sha512-dWodufrt71TK7ELkeIvVae/x4PzECUlbOm57Iqqt4yQCyR291CgvI4PjeB8un2HbpcXCGZ+N/Oj3YkytvzBi4A== - dependencies: - "@storybook/addon-actions" "7.4.6" - "@storybook/addon-backgrounds" "7.4.6" - "@storybook/addon-controls" "7.4.6" - "@storybook/addon-docs" "7.4.6" - "@storybook/addon-highlight" "7.4.6" - "@storybook/addon-measure" "7.4.6" - "@storybook/addon-outline" "7.4.6" - "@storybook/addon-toolbars" "7.4.6" - "@storybook/addon-viewport" "7.4.6" - "@storybook/core-common" "7.4.6" - "@storybook/manager-api" "7.4.6" - "@storybook/node-logger" "7.4.6" - "@storybook/preview-api" "7.4.6" +"@storybook/addon-essentials@^7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-7.6.6.tgz#116b03fded21d1178a3f96e18d76e67820bc5812" + integrity sha512-OQ8A6r06mg/HvyIk/j2Gt9DK5Qtqgtwq2Ydm5IgVW6gZsuRnv1FAeUG6okf8oXowAzpYoHdsDmCVwNOAGWGO7w== + dependencies: + "@storybook/addon-actions" "7.6.6" + "@storybook/addon-backgrounds" "7.6.6" + "@storybook/addon-controls" "7.6.6" + "@storybook/addon-docs" "7.6.6" + "@storybook/addon-highlight" "7.6.6" + "@storybook/addon-measure" "7.6.6" + "@storybook/addon-outline" "7.6.6" + "@storybook/addon-toolbars" "7.6.6" + "@storybook/addon-viewport" "7.6.6" + "@storybook/core-common" "7.6.6" + "@storybook/manager-api" "7.6.6" + "@storybook/node-logger" "7.6.6" + "@storybook/preview-api" "7.6.6" ts-dedent "^2.0.0" -"@storybook/addon-highlight@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-7.4.6.tgz#ee688232fe260f1b926205ddc1ceb1e0781dcbaf" - integrity sha512-zCufxxD2KS5VwczxfkcBxe1oR/juTTn2H1Qm8kYvWCJQx3UxzX0+G9cwafbpV7eivqaufLweEwROkH+0KjAtkQ== +"@storybook/addon-highlight@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-7.6.6.tgz#f39e174addfc97c44e1cfee6edbb0fd43b9356cd" + integrity sha512-B85UaCts2uMpa0yHBSnupzy2WCdW4vfB+lfaBug9beyOyZQdel07BumblE0KwSJftYgdCNPUZ5MRlqEDzMLTWQ== dependencies: - "@storybook/core-events" "7.4.6" "@storybook/global" "^5.0.0" - "@storybook/preview-api" "7.4.6" -"@storybook/addon-measure@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-7.4.6.tgz#12cb5ffab78c922809178c0ee8574b264ff2de5d" - integrity sha512-nCymMLaHnxv8TE3yEM1A9Tulb1NuRXRNmtsdHTkjv7P1aWCxZo8A/GZaottKe/GLT8jSRjZ+dnpYWrbAhw6wTQ== +"@storybook/addon-measure@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-7.6.6.tgz#890867470a82d5d1a6b248a46f5dba5d6b5d1e5a" + integrity sha512-b4hyCudlhsbYN1We8pfZHZJ0i0sfC8+GJvrqZQqdSqGicUmA00mggY1GE+gEoHziQ5/4auxFRS3HfUgaQWUNjg== dependencies: - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/core-events" "7.4.6" "@storybook/global" "^5.0.0" - "@storybook/manager-api" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/types" "7.4.6" tiny-invariant "^1.3.1" -"@storybook/addon-outline@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-7.4.6.tgz#d375046bb6858ea9ec09fdaf03d5802a543b2a30" - integrity sha512-errNUblRVDLpuEaHQPr/nsrnsUkD2ARmXawkRvizgDWLIDMDJYjTON3MUCaVx3x+hlZ3I6X//G5TVcma8tCc8A== +"@storybook/addon-outline@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-7.6.6.tgz#f0878972c050fe2d07d2ab1bf23a5237e6ed8c0c" + integrity sha512-BMjpjzNEnN8LC7JK92WCXyWgmJwAaEQjRDinr7eD4cBt4Uas5kbciw1g8PtTnh0GbYUsImKao0nzakSVObAdzg== dependencies: - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/core-events" "7.4.6" "@storybook/global" "^5.0.0" - "@storybook/manager-api" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/types" "7.4.6" ts-dedent "^2.0.0" -"@storybook/addon-toolbars@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-7.4.6.tgz#04d270bb45f6cea45cecce084f7713c7dda047f9" - integrity sha512-L9m2FBcKeteGq7qIYsMJr0LEfiH7Wdrv5IDcldZTn68eZUJTh1p4GdJZcOmzX1P5IFRr76hpu03iWsNlWQjpbQ== - dependencies: - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/manager-api" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/theming" "7.4.6" - -"@storybook/addon-viewport@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-7.4.6.tgz#ff6fbe9b67310d16cd7b4a38dbc445a8fede6373" - integrity sha512-INDtk54j7bi7NgxMfd2ATmbA0J7nAd6X8itMkLIyPuPJtx8bYHPDORyemDOd0AojgmAdTOAyUtDYdI/PFeo4Cw== - dependencies: - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/core-events" "7.4.6" - "@storybook/global" "^5.0.0" - "@storybook/manager-api" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/theming" "7.4.6" +"@storybook/addon-toolbars@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-7.6.6.tgz#eb669ecff9035644a4d7aeab2e9e7e0f08358986" + integrity sha512-sQm5+FcoSMSGn1ioXHoukO6OhUlcNZil0/fonAY50uvp6Z4DyI0FTU7BKIm/NoMqAExQk3sZRfAC/nZZ9Epb0Q== + +"@storybook/addon-viewport@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-7.6.6.tgz#814c1461ac56de0285e7ac843d2f0725419fd928" + integrity sha512-/ijbzDf1Iq30LvZW2NE8cO4TeHusw0N+jTDUK1+vhxGNMFo9DUIgRkAi6VpFEfS0aQ5d82523WSWzVso7b/Hmg== + dependencies: memoizerific "^1.11.3" - prop-types "^15.7.2" - -"@storybook/blocks@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-7.4.6.tgz#03134130fa20d6c36c6985008bc2c38892c5b8f5" - integrity sha512-HxBSAeOiTZW2jbHQlo1upRWFgoMsaAyKijUFf5MwwMNIesXCuuTGZDJ3xTABwAVLK2qC9Ektfbo0CZCiPVuDRQ== - dependencies: - "@storybook/channels" "7.4.6" - "@storybook/client-logger" "7.4.6" - "@storybook/components" "7.4.6" - "@storybook/core-events" "7.4.6" - "@storybook/csf" "^0.1.0" - "@storybook/docs-tools" "7.4.6" + +"@storybook/blocks@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-7.6.6.tgz#19bde920298b16ac31525ae321f15912f102fe8c" + integrity sha512-QLqkiSNrtGnh8RK9ipD63jVAUenkRu+72xR31DViZWRV9V8G2hzky5E/RoZWPEx+DfmBIUJ7Tcef6cCRcxEj9A== + dependencies: + "@storybook/channels" "7.6.6" + "@storybook/client-logger" "7.6.6" + "@storybook/components" "7.6.6" + "@storybook/core-events" "7.6.6" + "@storybook/csf" "^0.1.2" + "@storybook/docs-tools" "7.6.6" "@storybook/global" "^5.0.0" - "@storybook/manager-api" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/theming" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/manager-api" "7.6.6" + "@storybook/preview-api" "7.6.6" + "@storybook/theming" "7.6.6" + "@storybook/types" "7.6.6" "@types/lodash" "^4.14.167" color-convert "^2.0.1" dequal "^2.0.2" @@ -3789,15 +3889,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-manager@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-7.4.6.tgz#942a1a5e0a8b5956bd30867841fa1c542eb3d0bf" - integrity sha512-zylZCD2rmyLOOFBFmUgtJg6UNUKmRNgXiig1XApzS2TkIbTZP827DsVEUl0ey/lskCe0uArkrEBR6ICba8p/Rw== +"@storybook/builder-manager@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-7.6.6.tgz#50628c51b163d792001f4cd1dbe035bdb99b809e" + integrity sha512-96vmtUqh016H2n80xhvBZU2w5flTOzY7S0nW9nfxbY4UY4b39WajgwJ5wpg8l0YmCwQTrxCwY9/VE2Pd6CCqPA== dependencies: "@fal-works/esbuild-plugin-global-externals" "^2.1.2" - "@storybook/core-common" "7.4.6" - "@storybook/manager" "7.4.6" - "@storybook/node-logger" "7.4.6" + "@storybook/core-common" "7.6.6" + "@storybook/manager" "7.6.6" + "@storybook/node-logger" "7.6.6" "@types/ejs" "^3.1.1" "@types/find-cache-dir" "^3.2.1" "@yarnpkg/esbuild-plugin-pnp" "^3.0.0-rc.10" @@ -3811,20 +3911,19 @@ process "^0.11.10" util "^0.12.4" -"@storybook/builder-vite@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/builder-vite/-/builder-vite-7.4.6.tgz#b5aabb74c1e394b5e56be699408f0538eb483b1f" - integrity sha512-xV9STYK+TkqWWTf2ydm6jx+7P70fjD2UPd1XTUw08uKszIjhuuxk+bG/OF5R1E25mPunAKXm6kBFh351AKejBg== - dependencies: - "@storybook/channels" "7.4.6" - "@storybook/client-logger" "7.4.6" - "@storybook/core-common" "7.4.6" - "@storybook/csf-plugin" "7.4.6" - "@storybook/mdx2-csf" "^1.0.0" - "@storybook/node-logger" "7.4.6" - "@storybook/preview" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/types" "7.4.6" +"@storybook/builder-vite@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/builder-vite/-/builder-vite-7.6.6.tgz#28b692a436bb70a4611bbb3327313b097d3d1de2" + integrity sha512-vDBHjsswnVScVgGHeIZ22R/LoRt5T1F62p5czusydBSxKGzma5Va4JHQJp4/IKXwiCZbXcua/Cs7VKtBLO+50A== + dependencies: + "@storybook/channels" "7.6.6" + "@storybook/client-logger" "7.6.6" + "@storybook/core-common" "7.6.6" + "@storybook/csf-plugin" "7.6.6" + "@storybook/node-logger" "7.6.6" + "@storybook/preview" "7.6.6" + "@storybook/preview-api" "7.6.6" + "@storybook/types" "7.6.6" "@types/find-cache-dir" "^3.2.1" browser-assert "^1.2.1" es-module-lexer "^0.9.3" @@ -3832,39 +3931,37 @@ find-cache-dir "^3.0.0" fs-extra "^11.1.0" magic-string "^0.30.0" - remark-external-links "^8.0.0" - remark-slug "^6.0.0" rollup "^2.25.0 || ^3.3.0" -"@storybook/channels@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.4.6.tgz#cadd16b91db08005c2b9e4938d3e1d1290d27a40" - integrity sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A== +"@storybook/channels@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.6.6.tgz#c3de381a88897ae62043430a9c469aa37c0de41d" + integrity sha512-vvo7fBe2WffPonNNOA7Xx7jcHAto8qJYlq+VMysfheXrsRRbhHl3WQOA18Vm8hV9txtqdqk0hwQiXOWvhYVpeQ== dependencies: - "@storybook/client-logger" "7.4.6" - "@storybook/core-events" "7.4.6" + "@storybook/client-logger" "7.6.6" + "@storybook/core-events" "7.6.6" "@storybook/global" "^5.0.0" qs "^6.10.0" telejson "^7.2.0" tiny-invariant "^1.3.1" -"@storybook/cli@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-7.4.6.tgz#c322fcfdacf839a55a9c2aaa592a820fdaa1422c" - integrity sha512-rRwaH8pOL+FHz/pJMEkNpMH2xvZvWsrl7obBYw26NQiHmiVSAkfHJicndSN1mwc+p5w+9iXthrgzbLtSAOSvkA== +"@storybook/cli@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-7.6.6.tgz#401c1e9fd04bcc4b1f1486a7582e3c5317c0af3f" + integrity sha512-FLmWrbmGOqe1VYwqyIWxU2lJcYPssORmSbSVVPw6OqQIXx3NrNBrmZDLncMwbVCDQ8eU54J1zb+MyDmSqMbVFg== dependencies: - "@babel/core" "^7.22.9" - "@babel/preset-env" "^7.22.9" - "@babel/types" "^7.22.5" + "@babel/core" "^7.23.2" + "@babel/preset-env" "^7.23.2" + "@babel/types" "^7.23.0" "@ndelangen/get-tarball" "^3.0.7" - "@storybook/codemod" "7.4.6" - "@storybook/core-common" "7.4.6" - "@storybook/core-events" "7.4.6" - "@storybook/core-server" "7.4.6" - "@storybook/csf-tools" "7.4.6" - "@storybook/node-logger" "7.4.6" - "@storybook/telemetry" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/codemod" "7.6.6" + "@storybook/core-common" "7.6.6" + "@storybook/core-events" "7.6.6" + "@storybook/core-server" "7.6.6" + "@storybook/csf-tools" "7.6.6" + "@storybook/node-logger" "7.6.6" + "@storybook/telemetry" "7.6.6" + "@storybook/types" "7.6.6" "@types/semver" "^7.3.4" "@yarnpkg/fslib" "2.10.3" "@yarnpkg/libzip" "2.3.0" @@ -3881,7 +3978,7 @@ get-port "^5.1.1" giget "^1.0.0" globby "^11.0.2" - jscodeshift "^0.14.0" + jscodeshift "^0.15.1" leven "^3.1.0" ora "^5.4.1" prettier "^2.8.0" @@ -3895,72 +3992,72 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/client-logger@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.4.6.tgz#3346f3ae51abb3ce61bf1a7d083d32f27b8f718f" - integrity sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw== +"@storybook/client-logger@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.6.6.tgz#a8bc9568a1763c34d16d7154498bea971a181b06" + integrity sha512-WEvVyuQR5oNF8jcMmGA13zDjxP/l46kOBBvB6JSc8toUdtLZ/kZWSnU0ioNM8+ECpFqXHjBcF2K6uSJOEb6YEg== dependencies: "@storybook/global" "^5.0.0" -"@storybook/codemod@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-7.4.6.tgz#b884304c246de6de22faa94e76cd38f129bd827e" - integrity sha512-lxmwEpwksCaAq96APN2YlooSDfKjJ1vKzN5Ni2EqQzf2TEXl7XQjLacHd7OOaII1kfsy+D5gNG4N5wBo7Ub30g== +"@storybook/codemod@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-7.6.6.tgz#d8ebf6ad22e3a6da19410be5e2205a99d364e1e4" + integrity sha512-6QwW6T6ZgwwbTkEoZ7CAoX7lUUob7Sy7bRkMHhSjJe2++wEVFOYLvzHcLUJCupK59+WhmsJU9PpUMlXEKi40TQ== dependencies: - "@babel/core" "^7.22.9" - "@babel/preset-env" "^7.22.9" - "@babel/types" "^7.22.5" - "@storybook/csf" "^0.1.0" - "@storybook/csf-tools" "7.4.6" - "@storybook/node-logger" "7.4.6" - "@storybook/types" "7.4.6" + "@babel/core" "^7.23.2" + "@babel/preset-env" "^7.23.2" + "@babel/types" "^7.23.0" + "@storybook/csf" "^0.1.2" + "@storybook/csf-tools" "7.6.6" + "@storybook/node-logger" "7.6.6" + "@storybook/types" "7.6.6" "@types/cross-spawn" "^6.0.2" cross-spawn "^7.0.3" globby "^11.0.2" - jscodeshift "^0.14.0" + jscodeshift "^0.15.1" lodash "^4.17.21" prettier "^2.8.0" recast "^0.23.1" -"@storybook/components@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-7.4.6.tgz#e804407bd3a047e9f6026edfbbe188244661b55a" - integrity sha512-nIRBhewAgrJJVafyCzuaLx1l+YOfvvD5dOZ0JxZsxJsefOdw1jFpUqUZ5fIpQ2moyvrR0mAUFw378rBfMdHz5Q== +"@storybook/components@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-7.6.6.tgz#14c9e9c876cb7b930c9bfc64ea0442dd89868351" + integrity sha512-FSfcRxdmV4+LJHjMk0eodGVnZdb2qrKKmbtsn0O/434z586zPA287/wJJsm4JS/Xr1WS9oTvU6mYMDChkcxgeQ== dependencies: "@radix-ui/react-select" "^1.2.2" "@radix-ui/react-toolbar" "^1.0.4" - "@storybook/client-logger" "7.4.6" - "@storybook/csf" "^0.1.0" + "@storybook/client-logger" "7.6.6" + "@storybook/csf" "^0.1.2" "@storybook/global" "^5.0.0" - "@storybook/theming" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/theming" "7.6.6" + "@storybook/types" "7.6.6" memoizerific "^1.11.3" use-resize-observer "^9.1.0" util-deprecate "^1.0.2" -"@storybook/core-client@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-7.4.6.tgz#880ab2a431133912d0b76f2273cefe95b287d8ca" - integrity sha512-tfgxAHeCvMcs6DsVgtb4hQSDaCHeAPJOsoyhb47eDQfk4OmxzriM0qWucJV5DePSMi+KutX/rN2u0JxfOuN68g== +"@storybook/core-client@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-7.6.6.tgz#738173e13728020303530532789ff54102a3e294" + integrity sha512-P100aNf+WpvzlfULZp1NPd60/nxsppLmft2DdIyAx1j4QPMZvUJyJB+hdBMzTFiPEhIUncIMoIVf2R3UXC5DfA== dependencies: - "@storybook/client-logger" "7.4.6" - "@storybook/preview-api" "7.4.6" + "@storybook/client-logger" "7.6.6" + "@storybook/preview-api" "7.6.6" -"@storybook/core-common@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-7.4.6.tgz#8cb3fd94c4c7c5d307fb1606dcb0e06fc8672410" - integrity sha512-05MJFmOM86qvTLtgDskokIFz9txe0Lbhq4L3by1FtF0GwgH+p+W6I94KI7c6ANER+kVZkXQZhiRzwBFnVTW+Cg== +"@storybook/core-common@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-7.6.6.tgz#8df954f783d1de6c4064c8dcee8fe34f6fb69ad8" + integrity sha512-DpbFSYw8LHuwpeU2ec5uWryxrSqslFJnWTfNA7AvpzCviWXkz4kq+YYrDee9XExF6OozNwILmG6m52SnraysBA== dependencies: - "@storybook/core-events" "7.4.6" - "@storybook/node-logger" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/core-events" "7.6.6" + "@storybook/node-logger" "7.6.6" + "@storybook/types" "7.6.6" "@types/find-cache-dir" "^3.2.1" - "@types/node" "^16.0.0" + "@types/node" "^18.0.0" "@types/node-fetch" "^2.6.4" "@types/pretty-hrtime" "^1.0.0" chalk "^4.1.0" esbuild "^0.18.0" - esbuild-register "^3.4.0" + esbuild-register "^3.5.0" file-system-cache "2.3.0" find-cache-dir "^3.0.0" find-up "^5.0.0" @@ -3975,35 +4072,35 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" -"@storybook/core-events@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.4.6.tgz#f0b652c623c1afebda42d1702d631cecc1c137bf" - integrity sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w== +"@storybook/core-events@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.6.6.tgz#64b911db989acc05058e45400a2f8bad602113cd" + integrity sha512-7+q9HiZiLxaQcwpaSLQrLdjHNHBoOoUY9ZcZXI9iNFSopOgb/ItDnzzlpv08NC7CbKae1hVKJM/t5aSTl7tCMw== dependencies: ts-dedent "^2.0.0" -"@storybook/core-server@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-7.4.6.tgz#82a3834d9a063ff01a126f7c1724c8997bdd1555" - integrity sha512-jqmRTGCJ1W0WReImivkisPVaLFT5sjtLnFoAk0feHp6QS5j7EYOPN7CYzliyQmARWTLUEXOVaFf3VD6nJZQhJQ== +"@storybook/core-server@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-7.6.6.tgz#7816b98ab9b2dd02a11876f5113e7bcf713685b1" + integrity sha512-QFVahaExgGtq9swBXgQAMUiCqpCcyVXOiKTIy1j+1uAhPVqhpCxBkkFoXruih5hbIMZyohE4mLPCAr/ivicoDg== dependencies: "@aw-web-design/x-default-browser" "1.4.126" "@discoveryjs/json-ext" "^0.5.3" - "@storybook/builder-manager" "7.4.6" - "@storybook/channels" "7.4.6" - "@storybook/core-common" "7.4.6" - "@storybook/core-events" "7.4.6" - "@storybook/csf" "^0.1.0" - "@storybook/csf-tools" "7.4.6" + "@storybook/builder-manager" "7.6.6" + "@storybook/channels" "7.6.6" + "@storybook/core-common" "7.6.6" + "@storybook/core-events" "7.6.6" + "@storybook/csf" "^0.1.2" + "@storybook/csf-tools" "7.6.6" "@storybook/docs-mdx" "^0.1.0" "@storybook/global" "^5.0.0" - "@storybook/manager" "7.4.6" - "@storybook/node-logger" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/telemetry" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/manager" "7.6.6" + "@storybook/node-logger" "7.6.6" + "@storybook/preview-api" "7.6.6" + "@storybook/telemetry" "7.6.6" + "@storybook/types" "7.6.6" "@types/detect-port" "^1.3.0" - "@types/node" "^16.0.0" + "@types/node" "^18.0.0" "@types/pretty-hrtime" "^1.0.0" "@types/semver" "^7.3.4" better-opn "^3.0.2" @@ -4029,25 +4126,25 @@ watchpack "^2.2.0" ws "^8.2.3" -"@storybook/csf-plugin@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-7.4.6.tgz#63b4498c9be329ba9cdd53bb8cbe66ef225230ec" - integrity sha512-yi7Qa4NSqKOyiJTWCxlB0ih2ijXq6oY5qZKW6MuMMBP14xJNRGLbH5KabpfXgN2T7YECcOWG1uWaGj2veJb1KA== +"@storybook/csf-plugin@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-7.6.6.tgz#3420003d78921bfc974b372eb7d07a91ef416eb0" + integrity sha512-SqdffT14+XNpf+7vA29Elur28VArXtFv4cXMlsCbswbRuY+a0A8vYNwVIfCUy9u4WHTcQX1/tUkDAMh80lrVRQ== dependencies: - "@storybook/csf-tools" "7.4.6" + "@storybook/csf-tools" "7.6.6" unplugin "^1.3.1" -"@storybook/csf-tools@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-7.4.6.tgz#3987e31d5975dcaa8f3dfa4f5e0fb5cd457cbae6" - integrity sha512-ocKpcIUtTBy6hlLY34RUFQyX403cWpB2gGfqvkHbpGe2BQj7EyV0zpWnjsfVxvw+M9OWlCdxHWDOPUgXM33ELw== +"@storybook/csf-tools@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-7.6.6.tgz#b143f68d660a8418e2cfa2653a589376e47ac092" + integrity sha512-VXOZCzfSVJL832u17pPhFu1x3PPaAN9d8VXNFX+t/2raga7tK3T7Qhe7lWfP7EZcrVvSCEEp0aMRz2EzzDGVtw== dependencies: - "@babel/generator" "^7.22.9" - "@babel/parser" "^7.22.7" - "@babel/traverse" "^7.22.8" - "@babel/types" "^7.22.5" - "@storybook/csf" "^0.1.0" - "@storybook/types" "7.4.6" + "@babel/generator" "^7.23.0" + "@babel/parser" "^7.23.0" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + "@storybook/csf" "^0.1.2" + "@storybook/types" "7.6.6" fs-extra "^11.1.0" recast "^0.23.1" ts-dedent "^2.0.0" @@ -4059,10 +4156,10 @@ dependencies: lodash "^4.17.15" -"@storybook/csf@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.1.tgz#abccc8c3e49aed0a6a7e87beb0d1c262b1921c06" - integrity sha512-4hE3AlNVxR60Wc5KSC68ASYzUobjPqtSKyhV6G+ge0FIXU55N5nTY7dXGRZHQGDBPq+XqchMkIdlkHPRs8nTHg== +"@storybook/csf@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.2.tgz#8e7452f0097507f5841b5ade3f5da1525bc9afb2" + integrity sha512-ePrvE/pS1vsKR9Xr+o+YwdqNgHUyXvg+1Xjx0h9LrVx7Zq4zNe06pd63F5EvzTbCbJsHj7GHr9tkiaqm7U8WRA== dependencies: type-fest "^2.19.0" @@ -4071,15 +4168,16 @@ resolved "https://registry.yarnpkg.com/@storybook/docs-mdx/-/docs-mdx-0.1.0.tgz#33ba0e39d1461caf048b57db354b2cc410705316" integrity sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg== -"@storybook/docs-tools@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-7.4.6.tgz#cbba8dadd13adc7a9a3c792261ca875ff12ae451" - integrity sha512-nZj1L/8WwKWWJ41FW4MaKGajZUtrhnr9UwflRCkQJaWhAKmDfOb5M5TqI93uCOULpFPOm5wpoMBz2IHInQ2Lrg== +"@storybook/docs-tools@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-7.6.6.tgz#9abd981fb6197010c7ae0f06b7ff5de6eba2a011" + integrity sha512-nc5ZjN2s8SC2PtsZoFf9Wm6gD8TcSlkYbF/mjtyLCGN+Fi+k5B5iudqoa65H19hwiLlzBdcnpQ8C89AiK33J9Q== dependencies: - "@storybook/core-common" "7.4.6" - "@storybook/preview-api" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/core-common" "7.6.6" + "@storybook/preview-api" "7.6.6" + "@storybook/types" "7.6.6" "@types/doctrine" "^0.0.3" + assert "^2.1.0" doctrine "^3.0.0" lodash "^4.17.21" @@ -4088,19 +4186,19 @@ resolved "https://registry.yarnpkg.com/@storybook/global/-/global-5.0.0.tgz#b793d34b94f572c1d7d9e0f44fac4e0dbc9572ed" integrity sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ== -"@storybook/manager-api@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-7.4.6.tgz#e74bd0a0a983d6b9b7f66dfe0d94d8465f5e7a34" - integrity sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w== +"@storybook/manager-api@7.6.6", "@storybook/manager-api@^7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-7.6.6.tgz#8001e1cb756bde85cd639e69025ea005b8b36509" + integrity sha512-euRAbSZAUzHDt6z1Pq/g45N/RNqta9RaQAym18zt/oLWiYOIrkLmdf7kCuFYsmuA5XQBytiJqwkAD7uF1aLe0g== dependencies: - "@storybook/channels" "7.4.6" - "@storybook/client-logger" "7.4.6" - "@storybook/core-events" "7.4.6" - "@storybook/csf" "^0.1.0" + "@storybook/channels" "7.6.6" + "@storybook/client-logger" "7.6.6" + "@storybook/core-events" "7.6.6" + "@storybook/csf" "^0.1.2" "@storybook/global" "^5.0.0" - "@storybook/router" "7.4.6" - "@storybook/theming" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/router" "7.6.6" + "@storybook/theming" "7.6.6" + "@storybook/types" "7.6.6" dequal "^2.0.2" lodash "^4.17.21" memoizerific "^1.11.3" @@ -4109,57 +4207,57 @@ telejson "^7.2.0" ts-dedent "^2.0.0" -"@storybook/manager@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-7.4.6.tgz#96acb0ab60e05b74947d7895e04efa557fd8892f" - integrity sha512-kA1hUDxpn1i2SO9OinvLvVXDeL4xgJkModp+pbE8IXv4NJWReNq1ecMeQCzPLS3Sil2gnrullQ9uYXsnZ9bxxA== +"@storybook/manager@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-7.6.6.tgz#03a831aa99da2ea096eec34cdf3358821d1f81fb" + integrity sha512-Ga3LcSu/xxSyg+cLlO9AS8QjW+D667V+c9qQPmsFyU6qfFc6m6mVqcRLSmFVD5e7P/o0FL7STOf9jAKkDcW8xw== "@storybook/mdx2-csf@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@storybook/mdx2-csf/-/mdx2-csf-1.1.0.tgz#97f6df04d0bf616991cc1005a073ac004a7281e5" integrity sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw== -"@storybook/node-logger@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.4.6.tgz#d92eb7e99cb8aefffe67eb63583a21398ce9a0ab" - integrity sha512-djZb310Q27GviDug1XBv0jOEDLCiwr4hhDE0aifCEKZpfNCi/EaP31nbWimFzZwxu4hE/YAPWExzScruR1zw9Q== +"@storybook/node-logger@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.6.6.tgz#800ebdd67fadc79f2a453ad2c9ee5f54225c6644" + integrity sha512-b2OF9GRNI01MlBlnDGS8S6/yOpBNl8eH/0ONafuMPzFEZs5PouHGsFflJvQwwcdVTknMjF5uVS2eSmnLZ8spvA== -"@storybook/postinstall@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-7.4.6.tgz#2d5da361fc8baee1866c80b2244815a9e217b843" - integrity sha512-TqI5BucPAGRWrkh55BYiG2/gHLFtC0In4cuu0GsUzB/1jc4i51npLRorCwhmT7r7YliGl5F7JaP0Bni/qHN3Lg== +"@storybook/postinstall@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-7.6.6.tgz#7da3810db566c20fa7c234939612e9ea27cf60bd" + integrity sha512-jamn7QNTJPZiu22nu25LqfSTJohugFhCu4b48yqP+pdMdkQ3qVd3NdDYhBlgkH/Btar+kppiJP1gRtoiJF761w== -"@storybook/preact-vite@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/preact-vite/-/preact-vite-7.4.6.tgz#dbbcdbee7f78dd082665c2e869d1d02102e16741" - integrity sha512-nfoJ6H25IOZUL7rPvk81N/TaeC5ks1Mz8e9k9n3zsBc+zkoRtaYr9egHIx9pjvYt52A5xNg+jgSV/nhARPw4Nw== +"@storybook/preact-vite@^7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/preact-vite/-/preact-vite-7.6.6.tgz#7f163df6397bf1ff1361c468f9d3038707f67f3a" + integrity sha512-v8AyAQo1OGoSLkY44wLbImWJiyanHLVUqTH3r+NpfidiczvFYOaZbwbm2dcAXlPTs7y5s4L7f60PbVPF5LVq7A== dependencies: "@preact/preset-vite" "^2.0.0" - "@storybook/builder-vite" "7.4.6" - "@storybook/preact" "7.4.6" + "@storybook/builder-vite" "7.6.6" + "@storybook/preact" "7.6.6" -"@storybook/preact@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/preact/-/preact-7.4.6.tgz#d70ca27ec06b73a7cb68119b750034cce05db6d4" - integrity sha512-n5u2oU3XGjaixhc7eL3Ebov48r3q1t+3fOFj/3jz+9GgoM3n5T5+sGJkoRof8cCjqRt20zbSIkiQuVgzbyLUXQ== +"@storybook/preact@7.6.6", "@storybook/preact@^7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/preact/-/preact-7.6.6.tgz#244964985ec2a070182582f09fd2ca6b9f1328a6" + integrity sha512-B3uvc1R8U1wh0jFoWr3gyIJZKnpyIfPW1+J0YddKe+Tpq3pj6IJz+c3hQc/VgYwHgaOgadyFWV0lX3sjCoFFgQ== dependencies: - "@storybook/core-client" "7.4.6" + "@storybook/core-client" "7.6.6" "@storybook/global" "^5.0.0" - "@storybook/preview-api" "7.4.6" - "@storybook/types" "7.4.6" + "@storybook/preview-api" "7.6.6" + "@storybook/types" "7.6.6" ts-dedent "^2.0.0" -"@storybook/preview-api@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-7.4.6.tgz#a42749ff867216b89849ada6ac0f49f4fa8f03a8" - integrity sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA== +"@storybook/preview-api@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-7.6.6.tgz#72a3d214f1e40e4ca70377ded028e313cebbfe4f" + integrity sha512-Bt6xIAR5yZ/JWc90X4BbLOA97iL65glZ1SOBgFFv2mHrdZ1lcdKhAlQr2aeJAf1mLvBtalPjvKzi9EuVY3FZ4w== dependencies: - "@storybook/channels" "7.4.6" - "@storybook/client-logger" "7.4.6" - "@storybook/core-events" "7.4.6" - "@storybook/csf" "^0.1.0" + "@storybook/channels" "7.6.6" + "@storybook/client-logger" "7.6.6" + "@storybook/core-events" "7.6.6" + "@storybook/csf" "^0.1.2" "@storybook/global" "^5.0.0" - "@storybook/types" "7.4.6" + "@storybook/types" "7.6.6" "@types/qs" "^6.9.5" dequal "^2.0.2" lodash "^4.17.21" @@ -4169,55 +4267,55 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/preview@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/preview/-/preview-7.4.6.tgz#b0d9f5a843d4c7aea8857f6bc5d7253cc04c7c4b" - integrity sha512-2RPXusJ4CTDrIipIKKvbotD7fP0+8VzoFjImunflIrzN9rni+2rq5eMjqlXAaB+77w064zIR4uDUzI9fxsMDeQ== +"@storybook/preview@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/preview/-/preview-7.6.6.tgz#0099ed0297a8d3b5311450e10c8b0ca4275cf284" + integrity sha512-Rl+Pom2bNwzc0MdlbFALmvxsbCkbIwlpTZlRZZTh5Ah8JViV9htQgP9e8uwo3NZA2BhjbDLkmnZeloWxQnI5Ig== -"@storybook/react-dom-shim@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-7.4.6.tgz#7f7e01dbb6abe104ae140e25b7ed98c347a5fb9f" - integrity sha512-DSq8l9FDocUF1ooVI+TF83pddj1LynE/Hv0/y8XZhc3IgJ/HkuOQuUmfz29ezgfAi9gFYUR8raTIBi3/xdoRmw== +"@storybook/react-dom-shim@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-7.6.6.tgz#77dffcfa5b969a401b6ad9fecd6ce0c0275ceb44" + integrity sha512-WWNlXtCVoBWXX/kLNulUeMgzmlAEHi2aBrdIv2jz0DScPf0YxeWAkWmgK7F0zMot9mdwYncr+pk1AILbTBJSyg== -"@storybook/router@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-7.4.6.tgz#54d3014af26f82d79eae7dc5b0e6a89509b11912" - integrity sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ== +"@storybook/router@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-7.6.6.tgz#f97e77297eb4f17b2d4fb42bfcbc4132dac7e529" + integrity sha512-dkn81MtxrG7JMDbOHEcVZkTDVKsneg72CyqJ8ELZfC81iKQcDMQkV9mdmnMl45aKn6UrscudI4K23OxQmsevkw== dependencies: - "@storybook/client-logger" "7.4.6" + "@storybook/client-logger" "7.6.6" memoizerific "^1.11.3" qs "^6.10.0" -"@storybook/telemetry@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-7.4.6.tgz#748c978a188c988d688a50635025c12e7e90f924" - integrity sha512-c8p/C1NIH8EMBviZkBCx8MMDk6rrITJ+b29DEp5MaWSRlklIVyhGiC4RPIRv6sxJwlD41PnqWVFtfu2j2eXLdQ== +"@storybook/telemetry@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-7.6.6.tgz#c317e61385bca02b877ed7a8d5940d8f832d0f47" + integrity sha512-2WdDcrMrt1bPVgdMVO0tFmVxT6YIjiPRfKbH/7wwYMOGmV75m4mJ9Ha2gzZc/oXTSK1M4/fiK12IgW+S3ErcMg== dependencies: - "@storybook/client-logger" "7.4.6" - "@storybook/core-common" "7.4.6" - "@storybook/csf-tools" "7.4.6" + "@storybook/client-logger" "7.6.6" + "@storybook/core-common" "7.6.6" + "@storybook/csf-tools" "7.6.6" chalk "^4.1.0" detect-package-manager "^2.0.1" fetch-retry "^5.0.2" fs-extra "^11.1.0" read-pkg-up "^7.0.1" -"@storybook/theming@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-7.4.6.tgz#72f7e42a57347d84128cef9abfba5ac1a810118e" - integrity sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw== +"@storybook/theming@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-7.6.6.tgz#9eb190982c7e3f6f1383bfdf38a24c677d1803ee" + integrity sha512-hNZOOxaF55iAGUEM0dvAIP6LfGMgPKCJQIk/qyotFk+SKkg3PBqzph89XfFl9yCD3KiX5cryqarULgVuNawLJg== dependencies: "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" - "@storybook/client-logger" "7.4.6" + "@storybook/client-logger" "7.6.6" "@storybook/global" "^5.0.0" memoizerific "^1.11.3" -"@storybook/types@7.4.6": - version "7.4.6" - resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.4.6.tgz#536f21b82e1f809052b4d09802f99a580e960175" - integrity sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw== +"@storybook/types@7.6.6": + version "7.6.6" + resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.6.6.tgz#46159522742b6c00816ad3f5d213a9e949f24b52" + integrity sha512-77vbQp3GX93OD8UzFkY4a0fAmkZrqLe61XVo6yABrwbVDY0EcAwaCF5gcXRhOHldlH7KYbLfEQkDkkKTBjX7ow== dependencies: - "@storybook/channels" "7.4.6" + "@storybook/channels" "7.6.6" "@types/babel__core" "^7.0.0" "@types/express" "^4.7.0" file-system-cache "2.3.0" @@ -4309,17 +4407,17 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@6.1.4": - version "6.1.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.1.4.tgz#cf0835c33bc5ef00befb9e672b1e3e6a710e30e3" - integrity sha512-wpoYrCYwSZ5/AxcrjLxJmCU6I5QAJXslEeSiMQqaWmP2Kzpd1LvF/qxmAIW2qposULGWq2gw30GgVNFLSc2Jnw== +"@testing-library/jest-dom@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz#b572bd5cd6b29314487bac7ba393188e4987b4f7" + integrity sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw== dependencies: - "@adobe/css-tools" "^4.3.1" + "@adobe/css-tools" "^4.3.2" "@babel/runtime" "^7.9.2" aria-query "^5.0.0" chalk "^3.0.0" css.escape "^1.5.1" - dom-accessibility-api "^0.5.6" + dom-accessibility-api "^0.6.3" lodash "^4.17.15" redent "^3.0.0" @@ -4569,10 +4667,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@29.5.10": - version "29.5.10" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.10.tgz#a10fc5bab9e426081c12b2ef73d24d4f0c9b7f50" - integrity sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ== +"@types/jest@^29.5.11": + version "29.5.11" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" + integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -4654,21 +4752,18 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== -"@types/node@^16.0.0": - version "16.18.38" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.38.tgz#1dcdb6c54d02b323f621213745f2e44af30c73e6" - integrity sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ== +"@types/node@^18.0.0": + version "18.19.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.3.tgz#e4723c4cb385641d61b983f6fe0b716abd5f8fc0" + integrity sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg== + dependencies: + undici-types "~5.26.4" "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - "@types/pretty-hrtime@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz#72a26101dc567b0d68fd956cf42314556e42d601" @@ -4764,6 +4859,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/uuid@^9.0.1": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.7.tgz#b14cebc75455eeeb160d5fe23c2fcc0c64f724d8" + integrity sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g== + "@types/ws@^8.5.5": version "8.5.9" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.9.tgz#384c489f99c83225a53f01ebc3eddf3b8e202a8c" @@ -5193,6 +5293,11 @@ acorn@^8.1.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +add@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/add/-/add-2.0.6.tgz#248f0a9f6e5a528ef2295dbeec30532130ae2235" + integrity sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q== + address@^1.0.1: version "1.2.2" resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" @@ -5553,6 +5658,17 @@ assert@^2.0.0: object-is "^1.0.1" util "^0.12.0" +assert@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd" + integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw== + dependencies: + call-bind "^1.0.2" + is-nan "^1.3.2" + object-is "^1.1.5" + object.assign "^4.1.4" + util "^0.12.5" + assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -5563,13 +5679,6 @@ ast-types-flow@^0.0.8: resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== -ast-types@0.15.2: - version "0.15.2" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.15.2.tgz#39ae4809393c4b16df751ee563411423e85fb49d" - integrity sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg== - dependencies: - tslib "^2.0.1" - ast-types@^0.16.1: version "0.16.1" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.16.1.tgz#7a9da1617c9081bc121faafe91711b4c8bb81da2" @@ -5734,15 +5843,6 @@ babel-plugin-polyfill-corejs2@^0.3.3: "@babel/helper-define-polyfill-provider" "^0.3.3" semver "^6.1.1" -babel-plugin-polyfill-corejs2@^0.4.4: - version "0.4.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c" - integrity sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg== - dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.4.2" - semver "^6.3.1" - babel-plugin-polyfill-corejs2@^0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz#b2df0251d8e99f229a8e60fc4efa9a68b41c8313" @@ -5760,14 +5860,6 @@ babel-plugin-polyfill-corejs3@^0.6.0: "@babel/helper-define-polyfill-provider" "^0.3.3" core-js-compat "^3.25.1" -babel-plugin-polyfill-corejs3@^0.8.2: - version "0.8.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz#b4f719d0ad9bb8e0c23e3e630c0c8ec6dd7a1c52" - integrity sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.2" - core-js-compat "^3.31.0" - babel-plugin-polyfill-corejs3@^0.8.5: version "0.8.6" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz#25c2d20002da91fe328ff89095c85a391d6856cf" @@ -5783,13 +5875,6 @@ babel-plugin-polyfill-regenerator@^0.4.1: dependencies: "@babel/helper-define-polyfill-provider" "^0.3.3" -babel-plugin-polyfill-regenerator@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz#80d0f3e1098c080c8b5a65f41e9427af692dc326" - integrity sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.2" - babel-plugin-polyfill-regenerator@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz#d4c49e4b44614607c13fb769bcd85c72bb26a4a5" @@ -6029,6 +6114,16 @@ browserslist@^4.21.9: node-releases "^2.0.12" update-browserslist-db "^1.0.11" +browserslist@^4.22.2: + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== + dependencies: + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + bs-logger@0.x: version "0.2.6" resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" @@ -6188,6 +6283,11 @@ caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz#d4f92efc488aab3c7f92c738d3977c2a3180472b" integrity sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA== +caniuse-lite@^1.0.30001565: + version "1.0.30001570" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca" + integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -6205,12 +6305,19 @@ chai@4.3.4: pathval "^1.1.1" type-detect "^4.0.5" +"chainsaw@>=0.0.7 <0.1": + version "0.0.9" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.0.9.tgz#11a05102d1c4c785b6d0415d336d5a3a1612913e" + integrity sha512-nG8PYH+/4xB+8zkV4G844EtfvZ5tTiLFoX3dZ4nhF4t3OCKIb9UvaFyNmeZO2zOSmRWzBoTD+napN6hiL+EgcA== + dependencies: + traverse ">=0.3.0 <0.4" + chalk@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== -chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.0, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -6474,10 +6581,10 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-11.0.0.tgz#43e19c25dbedc8256203538e8d7e9346877a6f67" - integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== +commander@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" + integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== commander@2.11.x: version "2.11.0" @@ -6640,9 +6747,9 @@ core-js-compat@^3.33.1: browserslist "^4.22.1" core-js-pure@^3.25.3: - version "3.30.2" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.30.2.tgz#005a82551f4af3250dcfb46ed360fad32ced114e" - integrity sha512-p/npFUJXXBkCCTIlEGBdghofn00jWG6ZOtdoIXSJmAu2QBvN0IqpZXWweOytcwE6cfx8ZvVUy1vw8zxhe4Y2vg== + version "3.34.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.34.0.tgz#981e462500708664c91b827a75b011f04a8134a0" + integrity sha512-pmhivkYXkymswFfbXsANmBAewXx86UBfmagP+w0wkK06kLsLlTK5oQmsURPivzMkIBQiYq2cjamcZExIwlFQIg== core-util-is@1.0.2: version "1.0.2" @@ -6654,17 +6761,6 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmiconfig@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" - integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - cosmiconfig@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd" @@ -7357,11 +7453,16 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: +dom-accessibility-api@^0.5.9: version "0.5.16" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -7504,6 +7605,11 @@ electron-to-chromium@^1.4.535: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz#14db6660a88f66ce095ea2657abe5653bc7f42ed" integrity sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw== +electron-to-chromium@^1.4.601: + version "1.4.615" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.615.tgz#b1c41839962d2e4e63dca05519da9040e34848c2" + integrity sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng== + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -7836,10 +7942,10 @@ esbuild-plugin-alias@^0.2.1: resolved "https://registry.yarnpkg.com/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz#45a86cb941e20e7c2bc68a2bea53562172494fcb" integrity sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ== -esbuild-register@^3.4.0: - version "3.4.2" - resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-3.4.2.tgz#1e39ee0a77e8f320a9790e68c64c3559620b9175" - integrity sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q== +esbuild-register@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-3.5.0.tgz#449613fb29ab94325c722f560f800dd946dc8ea8" + integrity sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A== dependencies: debug "^4.3.4" @@ -7990,10 +8096,10 @@ eslint-plugin-react@^7.31.8: semver "^6.3.1" string.prototype.matchall "^4.0.8" -eslint-plugin-storybook@0.6.14: - version "0.6.14" - resolved "https://registry.yarnpkg.com/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.14.tgz#dfc2b58700e45eb4a13c172dda2973c2e033cd71" - integrity sha512-IeYigPur/MvESNDo43Z+Z5UvlcEVnt0dDZmnw1odi9X2Th1R3bpGyOZsHXb9bp1pFecOpRUuoMG5xdID2TwwOg== +eslint-plugin-storybook@0.6.15: + version "0.6.15" + resolved "https://registry.yarnpkg.com/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.15.tgz#8a091605b0a90974ec8e62d8112db6c4bd3a6faa" + integrity sha512-lAGqVAJGob47Griu29KXYowI4G7KwMoJDOkEip8ujikuDLxU+oWJ1l0WL6F2oDO4QiyUFXvtDkEkISMOPzo+7w== dependencies: "@storybook/csf" "^0.0.1" "@typescript-eslint/utils" "^5.45.0" @@ -8033,15 +8139,15 @@ eslint-visitor-keys@^3.4.3: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@8.52.0: - version "8.52.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.52.0.tgz#d0cd4a1fac06427a61ef9242b9353f36ea7062fc" - integrity sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg== +eslint@8.53.0: + version "8.53.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.53.0.tgz#14f2c8244298fcae1f46945459577413ba2697ce" + integrity sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.52.0" + "@eslint/eslintrc" "^2.1.3" + "@eslint/js" "8.53.0" "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -8216,19 +8322,19 @@ events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" - integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== +execa@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== dependencies: cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^4.3.0" + get-stream "^8.0.1" + human-signals "^5.0.0" is-stream "^3.0.0" merge-stream "^2.0.0" npm-run-path "^5.1.0" onetime "^6.0.0" - signal-exit "^3.0.7" + signal-exit "^4.1.0" strip-final-newline "^3.0.0" execa@^3.3.0: @@ -8282,18 +8388,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" - integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== - dependencies: - "@jest/expect-utils" "^29.5.0" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - -expect@^29.7.0: +expect@^29.0.0, expect@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== @@ -8418,7 +8513,7 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.3.0: +fast-glob@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -8621,9 +8716,9 @@ flow-parser@0.*: integrity sha512-Ftqkqisn4MA8u+1I7KGYz35y/RtLsRETsK4qrH6KkDUjxnC4mgq3CcXbckHpGyfTErqMyVhJnlJ56feEn9Cn7A== follow-redirects@^1.0.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== for-each@^0.3.3: version "0.3.3" @@ -8874,11 +8969,16 @@ get-stream@^5.0.0, get-stream@^5.1.0: dependencies: pump "^3.0.0" -get-stream@^6.0.0, get-stream@^6.0.1: +get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -9178,6 +9278,13 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +"hashish@>=0.0.2 <0.1": + version "0.0.4" + resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" + integrity sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA== + dependencies: + traverse ">=0.2.4" + hasown@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" @@ -9420,10 +9527,10 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -human-signals@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" - integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== humanize-duration@^3.25.0: version "3.29.0" @@ -9810,7 +9917,7 @@ is-module@^1.0.0: resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== -is-nan@^1.2.1: +is-nan@^1.2.1, is-nan@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== @@ -10266,7 +10373,7 @@ jest-environment-node@^29.7.0: jest-mock "^29.7.0" jest-util "^29.7.0" -jest-get-type@^29.4.3, jest-get-type@^29.6.3: +jest-get-type@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== @@ -10317,7 +10424,7 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-matcher-utils@^29.5.0, jest-matcher-utils@^29.7.0: +jest-matcher-utils@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== @@ -10327,7 +10434,7 @@ jest-matcher-utils@^29.5.0, jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-message-util@^29.5.0, jest-message-util@^29.7.0: +jest-message-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== @@ -10472,7 +10579,7 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" -jest-util@^29.0.0, jest-util@^29.5.0, jest-util@^29.7.0: +jest-util@^29.0.0, jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== @@ -10561,6 +10668,11 @@ jest@29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" +jiti@^1.18.2: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + js-md4@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/js-md4/-/js-md4-0.3.2.tgz#cd3b3dc045b0c404556c81ddb5756c23e59d7cf5" @@ -10596,20 +10708,21 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== -jscodeshift@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.14.0.tgz#7542e6715d6d2e8bde0b4e883f0ccea358b46881" - integrity sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA== - dependencies: - "@babel/core" "^7.13.16" - "@babel/parser" "^7.13.16" - "@babel/plugin-proposal-class-properties" "^7.13.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8" - "@babel/plugin-proposal-optional-chaining" "^7.13.12" - "@babel/plugin-transform-modules-commonjs" "^7.13.8" - "@babel/preset-flow" "^7.13.13" - "@babel/preset-typescript" "^7.13.0" - "@babel/register" "^7.13.16" +jscodeshift@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.15.1.tgz#6c7a9572acdfa4f54098e958f71a05716a4e546b" + integrity sha512-hIJfxUy8Rt4HkJn/zZPU9ChKfKZM1342waJ1QC2e2YsPcWhM+3BJ4dcfQCzArTrk1jJeNLB341H+qOcEHRxJZg== + dependencies: + "@babel/core" "^7.23.0" + "@babel/parser" "^7.23.0" + "@babel/plugin-transform-class-properties" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.23.0" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" + "@babel/plugin-transform-optional-chaining" "^7.23.0" + "@babel/plugin-transform-private-methods" "^7.22.5" + "@babel/preset-flow" "^7.22.15" + "@babel/preset-typescript" "^7.23.0" + "@babel/register" "^7.22.15" babel-core "^7.0.0-bridge.0" chalk "^4.1.2" flow-parser "0.*" @@ -10617,7 +10730,7 @@ jscodeshift@^0.14.0: micromatch "^4.0.4" neo-async "^2.5.0" node-dir "^0.1.17" - recast "^0.21.0" + recast "^0.23.3" temp "^0.8.4" write-file-atomic "^2.3.0" @@ -10843,21 +10956,21 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -lint-staged@^13.0.3: - version "13.3.0" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.3.0.tgz#7965d72a8d6a6c932f85e9c13ccf3596782d28a5" - integrity sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ== +lint-staged@^15.0.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.1.0.tgz#c0f8e4d96ac3c09beac5c76d08524d6000c207b4" + integrity sha512-ZPKXWHVlL7uwVpy8OZ7YQjYDAuO5X4kMh0XgZvPNxLcCCngd0PO5jKQyy3+s4TL2EnHoIXIzP1422f/l3nZKMw== dependencies: chalk "5.3.0" - commander "11.0.0" + commander "11.1.0" debug "4.3.4" - execa "7.2.0" + execa "8.0.1" lilconfig "2.1.0" - listr2 "6.6.1" + listr2 "7.0.2" micromatch "4.0.5" pidtree "0.6.0" string-argv "0.3.2" - yaml "2.3.1" + yaml "2.3.4" linux-platform-info@^0.0.3: version "0.0.3" @@ -10866,10 +10979,10 @@ linux-platform-info@^0.0.3: dependencies: os-family "^1.0.0" -listr2@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-6.6.1.tgz#08b2329e7e8ba6298481464937099f4a2cd7f95d" - integrity sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg== +listr2@7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-7.0.2.tgz#3aa3e1549dfaf3c57ab5eeaba754da3b87f33063" + integrity sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g== dependencies: cli-truncate "^3.1.0" colorette "^2.0.20" @@ -11560,6 +11673,11 @@ node-releases@^2.0.13, node-releases@^2.0.8: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -12304,16 +12422,14 @@ postcss-load-config@^3.0.0: lilconfig "^2.0.5" yaml "^1.10.2" -postcss-loader@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.3.0.tgz#2c4de9657cd4f07af5ab42bd60a673004da1b8cc" - integrity sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q== +postcss-loader@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.3.tgz#6da03e71a918ef49df1bb4be4c80401df8e249dd" + integrity sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA== dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.4" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - semver "^7.3.4" + cosmiconfig "^8.2.0" + jiti "^1.18.2" + semver "^7.3.8" postcss-media-query-parser@^0.2.3: version "0.2.3" @@ -12729,7 +12845,12 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@2.8.8, prettier@^2.7.1, prettier@^2.8.0: +prettier@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848" + integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw== + +prettier@^2.7.1, prettier@^2.8.0: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -12751,16 +12872,7 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^29.0.0: - version "29.6.3" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7" - integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -pretty-format@^29.7.0: +pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== @@ -12809,7 +12921,7 @@ prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -13019,11 +13131,6 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" -react-inspector@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.2.tgz#aa3028803550cb6dbd7344816d5c80bf39d07e9d" - integrity sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ== - react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -13058,13 +13165,6 @@ react-remove-scroll@2.5.5: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" -react-resize-detector@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-7.1.2.tgz#8ef975dd8c3d56f9a5160ac382ef7136dcd2d86c" - integrity sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw== - dependencies: - lodash "^4.17.21" - react-style-singleton@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" @@ -13165,20 +13265,21 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -recast@^0.21.0: - version "0.21.5" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.21.5.tgz#e8cd22bb51bcd6130e54f87955d33a2b2e57b495" - integrity sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg== +recast@^0.23.1: + version "0.23.2" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.2.tgz#d3dda3e8f0a3366860d7508c00e34a338ac52b41" + integrity sha512-Qv6cPfVZyMOtPszK6PgW70pUgm7gPlFitAPf0Q69rlOA0zLw2XdDcNmPbVGYicFGT9O8I7TZ/0ryJD+6COvIPw== dependencies: - ast-types "0.15.2" + assert "^2.0.0" + ast-types "^0.16.1" esprima "~4.0.0" source-map "~0.6.1" tslib "^2.0.1" -recast@^0.23.1: - version "0.23.2" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.2.tgz#d3dda3e8f0a3366860d7508c00e34a338ac52b41" - integrity sha512-Qv6cPfVZyMOtPszK6PgW70pUgm7gPlFitAPf0Q69rlOA0zLw2XdDcNmPbVGYicFGT9O8I7TZ/0ryJD+6COvIPw== +recast@^0.23.3: + version "0.23.4" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.4.tgz#ca1bac7bfd3011ea5a28dfecb5df678559fb1ddf" + integrity sha512-qtEDqIZGVcSZCHniWwZWbRy79Dc6Wp3kT/UmDA2RJKBPg7+7k51aQBZirHmUGn5uvHf2rg8DkjizrN26k61ATw== dependencies: assert "^2.0.0" ast-types "^0.16.1" @@ -13238,13 +13339,6 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.9, regenerator-runtime@^ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== -regenerator-transform@^0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" - integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== - dependencies: - "@babel/runtime" "^7.8.4" - regenerator-transform@^0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" @@ -13314,6 +13408,13 @@ remark-slug@^6.0.0: mdast-util-to-string "^1.0.0" unist-util-visit "^2.0.0" +remove@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/remove/-/remove-0.1.5.tgz#095ffd827d65c9f41ad97d33e416a75811079955" + integrity sha512-AJMA9oWvJzdTjwIGwSQZsjGQiRx73YTmiOWmfCp1fpLa/D4n7jKcpoA+CZiVLJqKcEKUuh1Suq80c5wF+L/qVQ== + dependencies: + seq ">= 0.3.5" + renderkid@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" @@ -13828,6 +13929,14 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +"seq@>= 0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/seq/-/seq-0.3.5.tgz#ae02af3a424793d8ccbf212d69174e0c54dffe38" + integrity sha512-sisY2Ln1fj43KBkRtXkesnRHYNdswIkIibvNe/0UKm2GZxjMbqmccpiatoKr/k2qX5VKiLU8xm+tz/74LAho4g== + dependencies: + chainsaw ">=0.0.7 <0.1" + hashish ">=0.0.2 <0.1" + serialize-javascript@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" @@ -13947,7 +14056,7 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: +signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== @@ -14221,12 +14330,12 @@ store2@^2.14.2: resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068" integrity sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w== -storybook@^7.4.6: - version "7.4.6" - resolved "https://registry.yarnpkg.com/storybook/-/storybook-7.4.6.tgz#f688649af6c2cd1329dd120d8f61a930f76262d0" - integrity sha512-YkFSpnR47j5zz7yElA+2axLjXN7K7TxDGJRHHlqXmG5iQ0PXzmjrj2RxMDKFz4Ybp/QjEUoJ4rx//ESEY0Nb5A== +storybook@^7.6.6: + version "7.6.6" + resolved "https://registry.yarnpkg.com/storybook/-/storybook-7.6.6.tgz#df0b7cd268adc6178686179ef020b1b12563d1e3" + integrity sha512-PmJxpjGdLvDOHaRzqLOvcJ3ALQPaNeW6D5Lv7rPPVbuO24wdDzd/75dPRP7gJKYcGE0NnDZ6cLQq3UlCfbkIBA== dependencies: - "@storybook/cli" "7.4.6" + "@storybook/cli" "7.6.6" stream-shift@^1.0.0: version "1.0.1" @@ -15085,6 +15194,16 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +traverse@>=0.2.4: + version "0.6.8" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.8.tgz#5e5e0c41878b57e4b73ad2f3d1e36a715ea4ab15" + integrity sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA== + +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -15366,6 +15485,11 @@ underscore@~1.12.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -15531,7 +15655,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util@^0.12.0, util@^0.12.4: +util@^0.12.0, util@^0.12.4, util@^0.12.5: version "0.12.5" resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== @@ -15606,10 +15730,10 @@ vite-plugin-stylelint@^4.3.0: "@rollup/pluginutils" "^5.0.2" chokidar "^3.5.3" -vite@4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26" - integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw== +vite@4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.1.tgz#3370986e1ed5dbabbf35a6c2e1fb1e18555b968a" + integrity sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA== dependencies: esbuild "^0.18.10" postcss "^8.4.27" @@ -16074,12 +16198,12 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" - integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== +yaml@2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" + integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== -yaml@^1.10.0, yaml@^1.10.2: +yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== @@ -16132,6 +16256,11 @@ yargs@^17.3.1, yargs@^17.5.1, yargs@^17.7.1, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" +yarn@^1.22.21: + version "1.22.21" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.21.tgz#1959a18351b811cdeedbd484a8f86c3cc3bbaf72" + integrity sha512-ynXaJsADJ9JiZ84zU25XkPGOvVMmZ5b7tmTSpKURYwgELdjucAOydqIOrOfTxVYcNXe91xvLZwcRh68SR3liCg== + yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"