From 0d461fa6e72ab3a4b5b7621fd46f87e6562bde50 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Mon, 10 Jun 2024 23:05:20 +0800 Subject: [PATCH] Add unit tests for some tracks in product editor (#48245) * Add tests for ProductPage * Add tests for ProductVariationPage * Add test for product_tab_click event * Add test for product_editor_feedback_bar_turnoff_editor_click event * Add test for product_dropdown_option_click event * Add test for product_add_view track * Changelogs * Changelog * Lint * Add product_edit_view test and falsey test * Fix test name --- ...cks-unit-tests-improvements-product-editor | 4 + packages/js/internal-js-tests/jest-preset.js | 3 + ...cks-unit-tests-improvements-product-editor | 4 + .../feedback-bar/test/feedback-bar.test.tsx | 44 ++++++++++ .../src/components/tabs/test/tabs.spec.tsx | 34 ++++++++ .../test/delete-variation-menu-item.test.tsx | 58 ++++++++++++++ .../test/product-block-editor-fills.test.tsx | 49 ++++++++++++ .../products/test/product-page.test.tsx | 80 +++++++++++++++++++ .../product-tracking/test/global.d.ts | 4 + .../test/product-edit.test.tsx | 36 +++++++++ .../test/product-new.test.tsx | 33 ++++++++ ...cks-unit-tests-improvements-product-editor | 4 + 12 files changed, 353 insertions(+) create mode 100644 packages/js/internal-js-tests/changelog/add-tracks-unit-tests-improvements-product-editor create mode 100644 packages/js/product-editor/changelog/add-tracks-unit-tests-improvements-product-editor create mode 100644 packages/js/product-editor/src/components/feedback-bar/test/feedback-bar.test.tsx create mode 100644 plugins/woocommerce-admin/client/products/fills/more-menu-items/test/delete-variation-menu-item.test.tsx create mode 100644 plugins/woocommerce-admin/client/products/fills/test/product-block-editor-fills.test.tsx create mode 100644 plugins/woocommerce-admin/client/products/test/product-page.test.tsx create mode 100644 plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/global.d.ts create mode 100644 plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/product-edit.test.tsx create mode 100644 plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/product-new.test.tsx create mode 100644 plugins/woocommerce/changelog/add-tracks-unit-tests-improvements-product-editor diff --git a/packages/js/internal-js-tests/changelog/add-tracks-unit-tests-improvements-product-editor b/packages/js/internal-js-tests/changelog/add-tracks-unit-tests-improvements-product-editor new file mode 100644 index 00000000000..587546f4efb --- /dev/null +++ b/packages/js/internal-js-tests/changelog/add-tracks-unit-tests-improvements-product-editor @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Add tests for some product editor tracks diff --git a/packages/js/internal-js-tests/jest-preset.js b/packages/js/internal-js-tests/jest-preset.js index ef55ec5f8d0..451e1373986 100644 --- a/packages/js/internal-js-tests/jest-preset.js +++ b/packages/js/internal-js-tests/jest-preset.js @@ -51,6 +51,9 @@ module.exports = { '**/test/*.[jt]s?(x)', '**/?(*.)test.[jt]s?(x)', ], + testPathIgnorePatterns: [ + '\\.d\\.ts$', // This regex pattern matches any file that ends with .d.ts + ], // The keys for the transformed modules contains the name of the packages that should be transformed. transformIgnorePatterns: [ 'node_modules/(?!(?:\\.pnpm|' + Object.keys( transformModules ).join( '|' ) + ')/)', diff --git a/packages/js/product-editor/changelog/add-tracks-unit-tests-improvements-product-editor b/packages/js/product-editor/changelog/add-tracks-unit-tests-improvements-product-editor new file mode 100644 index 00000000000..587546f4efb --- /dev/null +++ b/packages/js/product-editor/changelog/add-tracks-unit-tests-improvements-product-editor @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Add tests for some product editor tracks diff --git a/packages/js/product-editor/src/components/feedback-bar/test/feedback-bar.test.tsx b/packages/js/product-editor/src/components/feedback-bar/test/feedback-bar.test.tsx new file mode 100644 index 00000000000..2c1adbb74ce --- /dev/null +++ b/packages/js/product-editor/src/components/feedback-bar/test/feedback-bar.test.tsx @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { createElement } from '@wordpress/element'; +import { recordEvent } from '@woocommerce/tracks'; + +/** + * Internal dependencies + */ +import { FeedbackBar } from '../feedback-bar'; +import { useFeedbackBar } from '../../../hooks/use-feedback-bar'; + +jest.mock( '../../../hooks/use-feedback-bar', () => ( { + ...jest.requireActual( '../../../hooks/use-feedback-bar' ), + useFeedbackBar: jest.fn(), +} ) ); + +jest.mock( '@woocommerce/tracks', () => ( { + ...jest.requireActual( '@woocommerce/tracks' ), + recordEvent: jest.fn(), +} ) ); + +describe( 'FeedbackBar', () => { + beforeEach( () => { + jest.clearAllMocks(); + } ); + it( 'should trigger product_editor_feedback_bar_turnoff_editor_click event when clicking turn off editor', () => { + ( useFeedbackBar as jest.Mock ).mockImplementation( () => ( { + hideFeedbackBar: () => {}, + shouldShowFeedbackBar: true, + } ) ); + render( ); + + act( () => { + fireEvent.click( screen.getByText( 'turn it off' ) ); + } ); + expect( recordEvent ).toBeCalledWith( + 'product_editor_feedback_bar_turnoff_editor_click', + { product_type: 'testing' } + ); + } ); +} ); diff --git a/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx b/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx index ca034907e20..89f768e2ff2 100644 --- a/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx +++ b/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx @@ -6,6 +6,8 @@ import { render, fireEvent, screen } from '@testing-library/react'; import { getQuery, navigateTo } from '@woocommerce/navigation'; import { SlotFillProvider } from '@wordpress/components'; import { useState, createElement } from '@wordpress/element'; +import { recordEvent } from '@woocommerce/tracks'; +import { select } from '@wordpress/data'; /** * Internal dependencies @@ -15,6 +17,7 @@ import { TabBlockEdit as Tab, TabBlockAttributes, } from '../../../blocks/generic/tab/edit'; +import { TRACKS_SOURCE } from '../../../constants'; jest.mock( '@woocommerce/block-templates', () => ( { ...jest.requireActual( '@woocommerce/block-templates' ), @@ -27,6 +30,19 @@ jest.mock( '@woocommerce/navigation', () => ( { getQuery: jest.fn().mockReturnValue( {} ), } ) ); +jest.mock( '@woocommerce/tracks', () => ( { + ...jest.requireActual( '@woocommerce/tracks' ), + recordEvent: jest.fn(), +} ) ); + +jest.mock( '@wordpress/data', () => { + const originalModule = jest.requireActual( '@wordpress/data' ); + return { + ...originalModule, + select: jest.fn( ( ...args ) => originalModule.select( ...args ) ), + }; +} ); + const blockProps = { setAttributes: () => {}, className: '', @@ -109,6 +125,7 @@ function MockTabs( { onChange = jest.fn() } ) { describe( 'Tabs', () => { beforeEach( () => { + jest.clearAllMocks(); ( getQuery as jest.Mock ).mockReturnValue( { tab: null, } ); @@ -224,4 +241,21 @@ describe( 'Tabs', () => { expect( panel1.classList ).not.toContain( 'is-selected' ); expect( panel2.classList ).toContain( 'is-selected' ); } ); + + it( 'should trigger wcadmin_product_tab_click track event when tab is clicked', async () => { + ( select as jest.Mock ).mockImplementation( () => ( { + getEditedEntityRecord: () => ( { + type: 'simple', + } ), + } ) ); + render( ); + + const button = screen.getByText( 'Test button 2' ); + fireEvent.click( button ); + expect( recordEvent ).toBeCalledWith( 'product_tab_click', { + product_tab: 'test2', + product_type: 'simple', + source: TRACKS_SOURCE, + } ); + } ); } ); diff --git a/plugins/woocommerce-admin/client/products/fills/more-menu-items/test/delete-variation-menu-item.test.tsx b/plugins/woocommerce-admin/client/products/fills/more-menu-items/test/delete-variation-menu-item.test.tsx new file mode 100644 index 00000000000..e8a69e378d3 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/more-menu-items/test/delete-variation-menu-item.test.tsx @@ -0,0 +1,58 @@ +/** + * External dependencies + */ +import { render, fireEvent } from '@testing-library/react'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { recordEvent } from '@woocommerce/tracks'; +import { useParams } from 'react-router-dom'; + +/** + * Internal dependencies + */ +import { DeleteVariationMenuItem } from '../delete-variation-menu-item'; + +jest.mock( '@wordpress/data', () => ( { + ...jest.requireActual( '@wordpress/data' ), + useDispatch: jest.fn(), + useSelect: jest.fn(), +} ) ); +jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) ); + +jest.mock( 'react-router-dom', () => ( { useParams: jest.fn() } ) ); + +jest.mock( '@wordpress/core-data', () => ( { + useEntityId: jest.fn().mockReturnValue( 'variation_1' ), + useEntityProp: jest + .fn() + .mockImplementation( ( _1, _2, propType ) => [ propType ] ), +} ) ); + +describe( 'DeleteVariationMenuItem', () => { + beforeEach( () => { + jest.clearAllMocks(); + } ); + it( 'should trigger product_dropdown_option_click track event when clicking the menu', async () => { + ( useDispatch as jest.Mock ).mockReturnValue( { + deleteProductVariation: () => {}, + } ); + ( useSelect as jest.Mock ).mockReturnValue( { + type: 'simple', + status: 'publish', + } ); + ( useParams as jest.Mock ).mockReturnValue( { productId: 1 } ); + const { getByText } = render( + {} } /> + ); + fireEvent.click( getByText( 'Delete variation' ) ); + + expect( recordEvent ).toHaveBeenCalledWith( + 'product_dropdown_option_click', + { + product_id: 1, + product_status: 'status', + selected_option: 'delete_variation', + variation_id: 'variation_1', + } + ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/products/fills/test/product-block-editor-fills.test.tsx b/plugins/woocommerce-admin/client/products/fills/test/product-block-editor-fills.test.tsx new file mode 100644 index 00000000000..0158721252b --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/test/product-block-editor-fills.test.tsx @@ -0,0 +1,49 @@ +/** + * External dependencies + */ +import { render, fireEvent } from '@testing-library/react'; +import { useSelect } from '@wordpress/data'; +import { recordEvent } from '@woocommerce/tracks'; + +/** + * Internal dependencies + */ +import { MoreMenuFill } from '../product-block-editor-fills'; + +jest.mock( '@wordpress/data', () => ( { + ...jest.requireActual( '@wordpress/data' ), + useSelect: jest.fn(), +} ) ); +jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) ); +jest.mock( '../more-menu-items', () => ( { + ...jest.requireActual( '../more-menu-items' ), + ClassicEditorMenuItem: jest.fn().mockImplementation( () =>
), + FeedbackMenuItem: jest.fn().mockImplementation( ( { onClick } ) => ( +
+ +
+ ) ), +} ) ); + +describe( 'MoreMenuFill', () => { + beforeEach( () => { + jest.clearAllMocks(); + } ); + it( 'should trigger product_dropdown_option_click track event when clicking the menu', async () => { + ( useSelect as jest.Mock ).mockReturnValue( { + type: 'simple', + status: 'publish', + } ); + const { getByText } = render( {} } /> ); + fireEvent.click( getByText( 'Feedback button' ) ); + + expect( recordEvent ).toHaveBeenCalledWith( + 'product_dropdown_option_click', + { + product_status: 'publish', + product_type: 'simple', + selected_option: 'feedback', + } + ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/products/test/product-page.test.tsx b/plugins/woocommerce-admin/client/products/test/product-page.test.tsx new file mode 100644 index 00000000000..d9e87de706b --- /dev/null +++ b/plugins/woocommerce-admin/client/products/test/product-page.test.tsx @@ -0,0 +1,80 @@ +/** + * External dependencies + */ +import { render } from '@testing-library/react'; +import { recordEvent } from '@woocommerce/tracks'; +import { TRACKS_SOURCE } from '@woocommerce/product-editor'; +import { useParams } from 'react-router-dom'; + +/** + * Internal dependencies + */ +import ProductPage from '../product-page'; +import ProductVariationPage from '../product-variation-page'; + +jest.mock( '@woocommerce/tracks', () => ( { + recordEvent: jest.fn(), +} ) ); +jest.mock( 'react-router-dom', () => ( { useParams: jest.fn() } ) ); + +// Mocks to prevent crashes. +jest.mock( '@wordpress/api-fetch', () => ( { + apiFetch: jest.fn(), +} ) ); +jest.mock( '@wordpress/core-data', () => ( { + apiFetch: jest.fn(), +} ) ); +jest.mock( '../hooks/use-product-entity-record', () => ( { + useProductEntityRecord: jest.fn(), +} ) ); +jest.mock( '../hooks/use-product-variation-entity-record', () => ( { + useProductVariationEntityRecord: jest.fn(), +} ) ); +jest.mock( '@woocommerce/product-editor', () => ( { + ...jest.requireActual( '@woocommerce/product-editor' ), + productEditorHeaderApiFetchMiddleware: jest.fn(), + productApiFetchMiddleware: jest.fn(), + __experimentalInitBlocks: jest.fn().mockImplementation( () => () => {} ), +} ) ); + +describe( 'ProductPage', () => { + beforeEach( () => { + jest.clearAllMocks(); + } ); + it( 'should trigger product_add_view on render without product_id defined', () => { + ( useParams as jest.Mock ).mockReturnValue( { productId: null } ); + render( ); + expect( recordEvent ).toBeCalledWith( 'product_add_view', { + source: TRACKS_SOURCE, + } ); + } ); + it( 'should trigger product_edit_view on render with product_id defined', () => { + ( useParams as jest.Mock ).mockReturnValue( { productId: 1 } ); + render( ); + expect( recordEvent ).toBeCalledWith( 'product_edit_view', { + source: TRACKS_SOURCE, + product_id: 1, + } ); + } ); +} ); + +describe( 'ProductVariationPage', () => { + beforeEach( () => { + jest.clearAllMocks(); + } ); + it( 'should trigger product_add_view track event on render without product_id defined', () => { + ( useParams as jest.Mock ).mockReturnValue( { productId: null } ); + render( ); + expect( recordEvent ).toBeCalledWith( 'product_add_view', { + source: TRACKS_SOURCE, + } ); + } ); + it( 'should trigger product_edit_view track event on render with product_id defined', () => { + ( useParams as jest.Mock ).mockReturnValue( { productId: 1 } ); + render( ); + expect( recordEvent ).toBeCalledWith( 'product_edit_view', { + source: TRACKS_SOURCE, + product_id: 1, + } ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/global.d.ts b/plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/global.d.ts new file mode 100644 index 00000000000..79210a3d85f --- /dev/null +++ b/plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/global.d.ts @@ -0,0 +1,4 @@ +declare const productScreen: { name: string }; +declare const global: typeof globalThis & { + productScreen: typeof productScreen; +}; diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/product-edit.test.tsx b/plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/product-edit.test.tsx new file mode 100644 index 00000000000..4231a08f29f --- /dev/null +++ b/plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/product-edit.test.tsx @@ -0,0 +1,36 @@ +/** + * @jest-environment node + */ + +/** + * External dependencies + */ +import { recordEvent } from '@woocommerce/tracks'; + +jest.mock( '@woocommerce/tracks', () => ( { + recordEvent: jest.fn(), +} ) ); +jest.mock( '../shared', () => ( { + addExitPageListener: jest.fn().mockImplementation( () => {} ), + initProductScreenTracks: jest.fn().mockImplementation( () => {} ), + getProductData: jest.fn().mockImplementation( () => ( { product_id: 1 } ) ), +} ) ); + +describe( 'Product Screen Tracking', () => { + beforeEach( () => { + jest.clearAllMocks(); + } ); + it( 'should trigger product_edit_view event when productScreen.name is "edit"', () => { + global.productScreen = { name: 'edit' }; + require( '../product-edit' ); + expect( recordEvent ).toHaveBeenCalledWith( 'product_edit_view', { + product_id: 1, + } ); + } ); + + it( 'should not trigger product_edit_view event when productScreen.name is not "edit"', () => { + global.productScreen = { name: '' }; + require( '../product-edit' ); + expect( recordEvent ).not.toHaveBeenCalled(); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/product-new.test.tsx b/plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/product-new.test.tsx new file mode 100644 index 00000000000..e20d647fe3e --- /dev/null +++ b/plugins/woocommerce-admin/client/wp-admin-scripts/product-tracking/test/product-new.test.tsx @@ -0,0 +1,33 @@ +/** + * @jest-environment node + */ + +/** + * External dependencies + */ +import { recordEvent } from '@woocommerce/tracks'; + +jest.mock( '@woocommerce/tracks', () => ( { + recordEvent: jest.fn(), +} ) ); +jest.mock( '../shared', () => ( { + addExitPageListener: jest.fn().mockImplementation( () => {} ), + initProductScreenTracks: jest.fn().mockImplementation( () => {} ), +} ) ); + +describe( 'Product Screen Tracking', () => { + beforeEach( () => { + jest.clearAllMocks(); + } ); + it( 'should trigger product_add_view event when productScreen.name is "new"', () => { + global.productScreen = { name: 'new' }; + require( '../product-new' ); + expect( recordEvent ).toHaveBeenCalledWith( 'product_add_view' ); + } ); + + it( 'should not trigger product_add_view event when productScreen.name is not "new"', () => { + global.productScreen = { name: '' }; + require( '../product-new' ); + expect( recordEvent ).not.toHaveBeenCalled(); + } ); +} ); diff --git a/plugins/woocommerce/changelog/add-tracks-unit-tests-improvements-product-editor b/plugins/woocommerce/changelog/add-tracks-unit-tests-improvements-product-editor new file mode 100644 index 00000000000..e25aecd09a2 --- /dev/null +++ b/plugins/woocommerce/changelog/add-tracks-unit-tests-improvements-product-editor @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Add tests for some product editor tracks \ No newline at end of file