diff --git a/plugins/woocommerce-blocks/tests/js/jest.config.json b/plugins/woocommerce-blocks/tests/js/jest.config.json new file mode 100644 index 00000000000..c91bf754a0d --- /dev/null +++ b/plugins/woocommerce-blocks/tests/js/jest.config.json @@ -0,0 +1,59 @@ +{ + "rootDir": "../../", + "collectCoverageFrom": [ + "assets/js/**/*.js", + "!**/node_modules/**", + "!**/vendor/**", + "!**/test/**" + ], + "moduleDirectories": [ "node_modules" ], + "moduleNameMapper": { + "@woocommerce/atomic-blocks": "assets/js/atomic/blocks", + "@woocommerce/atomic-utils": "assets/js/atomic/utils", + "@woocommerce/icons": "assets/js/icons", + "@woocommerce/settings": "assets/js/settings/shared", + "@woocommerce/blocks/(.*)$": "assets/js/blocks/$1", + "@woocommerce/block-settings": "assets/js/settings/blocks", + "@woocommerce/editor-components(.*)$": "assets/js/editor-components/$1", + "@woocommerce/blocks-registry": "assets/js/blocks-registry", + "@woocommerce/blocks-checkout": "packages/checkout", + "@woocommerce/blocks-components": "packages/components", + "@woocommerce/price-format": "packages/prices", + "@woocommerce/block-hocs(.*)$": "assets/js/hocs/$1", + "@woocommerce/base-components(.*)$": "assets/js/base/components/$1", + "@woocommerce/base-context(.*)$": "assets/js/base/context/$1", + "@woocommerce/base-hocs(.*)$": "assets/js/base/hocs/$1", + "@woocommerce/base-hooks(.*)$": "assets/js/base/hooks/$1", + "@woocommerce/base-utils(.*)$": "assets/js/base/utils", + "@woocommerce/block-data": "assets/js/data", + "@woocommerce/resource-previews": "assets/js/previews", + "@woocommerce/shared-context": "assets/js/shared/context", + "@woocommerce/shared-hocs": "assets/js/shared/hocs", + "@woocommerce/blocks-test-utils": "tests/utils", + "@woocommerce/types": "assets/js/types", + "@woocommerce/utils": "assets/js/utils", + "@woocommerce/interactivity": "assets/js/interactivity", + "^react$": "/node_modules/react", + "^react-dom$": "/node_modules/react-dom" + }, + "setupFiles": [ + "@wordpress/jest-preset-default/scripts/setup-globals.js", + "/tests/js/setup-globals.js", + "/tests/js/setup-fetch.js" + ], + "setupFilesAfterEnv": [ + "/tests/js/setup-after-env.ts" + ], + "testPathIgnorePatterns": [ + "/tests/", + "/node_modules/", + "/vendor/" + ], + "transformIgnorePatterns": [ "node_modules/?!(simple-html-tokenizer|is-plain-obj|is-plain-object|memize)" ], + "testEnvironment": "jsdom", + "preset": "@wordpress/jest-preset-default", + "transform": { + "^.+\\.(js|ts|tsx)$": "/tests/js/jestPreprocess.js" + }, + "verbose": true +} diff --git a/plugins/woocommerce-blocks/tests/js/jestPreprocess.js b/plugins/woocommerce-blocks/tests/js/jestPreprocess.js new file mode 100644 index 00000000000..9baa877eae3 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/js/jestPreprocess.js @@ -0,0 +1,10 @@ +const babelOptions = { + presets: [ '@babel/preset-typescript', '@wordpress/babel-preset-default' ], + plugins: [ + 'explicit-exports-references', + '@babel/plugin-proposal-class-properties', + ], +}; + +module.exports = + require( 'babel-jest' ).default.createTransformer( babelOptions ); diff --git a/plugins/woocommerce-blocks/tests/js/setup-after-env.ts b/plugins/woocommerce-blocks/tests/js/setup-after-env.ts new file mode 100644 index 00000000000..4988776289c --- /dev/null +++ b/plugins/woocommerce-blocks/tests/js/setup-after-env.ts @@ -0,0 +1,5 @@ +/** + * External dependencies + */ +import '@testing-library/jest-dom'; +import '@wordpress/jest-console'; diff --git a/plugins/woocommerce-blocks/tests/js/setup-fetch.js b/plugins/woocommerce-blocks/tests/js/setup-fetch.js new file mode 100644 index 00000000000..eedaca4db51 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/js/setup-fetch.js @@ -0,0 +1,2 @@ +// This ensures you can use `window.fetch()` in your Jest tests. +require( 'jest-fetch-mock' ).enableMocks(); diff --git a/plugins/woocommerce-blocks/tests/js/setup-globals.js b/plugins/woocommerce-blocks/tests/js/setup-globals.js new file mode 100644 index 00000000000..198bcbc24a0 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/js/setup-globals.js @@ -0,0 +1,262 @@ +// Set up `wp.*` aliases. Doing this because any tests importing wp stuff will likely run into this. +global.wp = {}; +require( '@wordpress/data' ); +// wcSettings is required by @woocommerce/* packages +global.wcSettings = { + adminUrl: 'https://vagrant.local/wp/wp-admin/', + addressFormats: { + default: + '{name}\n{company}\n{address_1}\n{address_2}\n{city}\n{state}\n{postcode}\n{country}', + JP: '{postcode}\n{state} {city} {address_1}\n{address_2}\n{company}\n{last_name} {first_name}\n{country}', + CA: '{company}\n{name}\n{address_1}\n{address_2}\n{city} {state_code} {postcode}\n{country}', + }, + shippingMethodsExist: true, + currency: { + code: 'USD', + precision: 2, + symbol: '$', + }, + currentUserIsAdmin: false, + date: { + dow: 0, + }, + hasFilterableProducts: true, + orderStatuses: { + pending: 'Pending payment', + processing: 'Processing', + 'on-hold': 'On hold', + completed: 'Completed', + cancelled: 'Cancelled', + refunded: 'Refunded', + failed: 'Failed', + }, + placeholderImgSrc: 'placeholder.jpg', + productCount: 101, + locale: { + siteLocale: 'en_US', + userLocale: 'en_US', + weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], + }, + countries: { + AT: 'Austria', + CA: 'Canada', + GB: 'United Kingdom (UK)', + }, + countryData: { + AT: { + states: {}, + allowBilling: true, + allowShipping: true, + locale: { + postcode: { priority: 65 }, + state: { required: false, hidden: true }, + }, + format: '{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}', + }, + CA: { + states: { + ON: 'Ontario', + }, + allowBilling: true, + allowShipping: true, + locale: { + postcode: { label: 'Postal code' }, + state: { label: 'Province' }, + }, + format: '{company}\n{name}\n{address_1}\n{address_2}\n{city} {state_code} {postcode}\n{country}', + }, + JP: { + allowBilling: true, + allowShipping: true, + states: { + JP28: 'Hyogo', + }, + locale: { + last_name: { priority: 10 }, + first_name: { priority: 20 }, + postcode: { + priority: 65, + }, + state: { + label: 'Prefecture', + priority: 66, + }, + city: { priority: 67 }, + address_1: { priority: 68 }, + address_2: { priority: 69 }, + }, + format: '{postcode}\n{state} {city} {address_1}\n{address_2}\n{company}\n{last_name} {first_name}\n{country}', + }, + GB: { + states: {}, + allowBilling: true, + allowShipping: true, + locale: { + postcode: { label: 'Postcode' }, + state: { label: 'County', required: false }, + }, + }, + }, + storePages: { + myaccount: { + id: 0, + title: '', + permalink: '', + }, + shop: { + id: 0, + title: '', + permalink: '', + }, + cart: { + id: 0, + title: '', + permalink: '', + }, + checkout: { + id: 0, + title: '', + permalink: 'https://local/checkout/', + }, + privacy: { + id: 0, + title: '', + permalink: '', + }, + terms: { + id: 0, + title: '', + permalink: '', + }, + }, + attributes: [ + { + attribute_id: '1', + attribute_name: 'color', + attribute_label: 'Color', + attribute_type: 'select', + attribute_orderby: 'menu_order', + attribute_public: 0, + }, + { + attribute_id: '2', + attribute_name: 'size', + attribute_label: 'Size', + attribute_type: 'select', + attribute_orderby: 'menu_order', + attribute_public: 0, + }, + ], + defaultFields: { + first_name: { + label: 'First name', + optionalLabel: 'First name (optional)', + autocomplete: 'given-name', + autocapitalize: 'sentences', + required: true, + hidden: false, + index: 10, + }, + last_name: { + label: 'Last name', + optionalLabel: 'Last name (optional)', + autocomplete: 'family-name', + autocapitalize: 'sentences', + required: true, + hidden: false, + index: 20, + }, + company: { + label: 'Company', + optionalLabel: 'Company (optional)', + autocomplete: 'organization', + autocapitalize: 'sentences', + required: false, + hidden: false, + index: 30, + }, + address_1: { + label: 'Address', + optionalLabel: 'Address (optional)', + autocomplete: 'address-line1', + autocapitalize: 'sentences', + required: true, + hidden: false, + index: 40, + }, + address_2: { + label: 'Apartment, suite, etc.', + optionalLabel: 'Apartment, suite, etc. (optional)', + autocomplete: 'address-line2', + autocapitalize: 'sentences', + required: false, + hidden: false, + index: 50, + }, + country: { + label: 'Country/Region', + optionalLabel: 'Country/Region (optional)', + autocomplete: 'country', + required: true, + hidden: false, + index: 60, + }, + city: { + label: 'City', + optionalLabel: 'City (optional)', + autocomplete: 'address-level2', + autocapitalize: 'sentences', + required: true, + hidden: false, + index: 70, + }, + state: { + label: 'State/County', + optionalLabel: 'State/County (optional)', + autocomplete: 'address-level1', + autocapitalize: 'sentences', + required: true, + hidden: false, + index: 80, + }, + postcode: { + label: 'Postal code', + optionalLabel: 'Postal code (optional)', + autocomplete: 'postal-code', + autocapitalize: 'characters', + required: true, + hidden: false, + index: 90, + }, + phone: { + label: 'Phone', + optionalLabel: 'Phone (optional)', + autocomplete: 'tel', + type: 'tel', + required: true, + hidden: false, + index: 100, + }, + }, + checkoutData: { + order_id: 100, + status: 'checkout-draft', + order_key: 'wc_order_mykey', + order_number: '100', + customer_id: 1, + }, +}; + +global.jQuery = () => ( { + on: () => void null, + off: () => void null, +} ); + +global.IntersectionObserver = function () { + return { + observe: () => void null, + unobserve: () => void null, + }; +}; + +global.__webpack_public_path__ = ''; diff --git a/plugins/woocommerce-blocks/tests/utils/compatibility-notice.js b/plugins/woocommerce-blocks/tests/utils/compatibility-notice.js new file mode 100644 index 00000000000..033fc85a7d7 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/utils/compatibility-notice.js @@ -0,0 +1,16 @@ +export async function preventCompatibilityNotice() { + await page.evaluate( () => { + window.localStorage.setItem( + 'wc-blocks_dismissed_compatibility_notices', + '["checkout"]' + ); + } ); +} + +export async function reactivateCompatibilityNotice() { + await page.evaluate( () => { + window.localStorage.removeItem( + 'wc-blocks_dismissed_compatibility_notices' + ); + } ); +} diff --git a/plugins/woocommerce-blocks/tests/utils/find-by-text.ts b/plugins/woocommerce-blocks/tests/utils/find-by-text.ts new file mode 100644 index 00000000000..4709dbf06a1 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/utils/find-by-text.ts @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +import type { MatcherFunction } from '@testing-library/react'; + +/** + * This function will match text over several elements, the standard matcher + * will only find strings if they are within the same element. + */ +export const textContentMatcher = ( text: string ): MatcherFunction => { + return ( _content, node ) => { + const hasText = ( _node ) => _node.textContent === text; + const nodeHasText = hasText( node ); + const childrenDontHaveText = Array.from( node?.children || [] ).every( + ( child ) => ! hasText( child ) + ); + return nodeHasText && childrenDontHaveText; + }; +}; + +/** + * This will check if the text is present an the container, it can be within + * multiple elements, for example: + *
+ * Text + * is + * present + *
+ */ +export const textContentMatcherAcrossSiblings = ( + text: string +): MatcherFunction => { + return ( _content, node ): boolean => { + /* + If the element in question is not the first child, then skip, as we + will have already run this check for its siblings (when we ran it on the + first child). + */ + const siblings = + node?.parentElement?.children[ 0 ] === node + ? node?.parentElement?.children + : []; + let siblingText = ''; + + // Get the text of all siblings and put it into a single string. + if ( siblings?.length > 0 ) { + siblingText = Array.from( siblings ) + .map( ( child ) => child.textContent ) + .filter( Boolean ) + .join( ' ' ) + .trim(); + } + return siblingText !== '' && siblingText === text; + }; +}; diff --git a/plugins/woocommerce/changelog/47516-fix-restore-js-unit b/plugins/woocommerce/changelog/47516-fix-restore-js-unit new file mode 100644 index 00000000000..82d97157970 --- /dev/null +++ b/plugins/woocommerce/changelog/47516-fix-restore-js-unit @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Blocks: Fix JS unit tests \ No newline at end of file