'use strict'; jest.mock( '../../../../modules/ext.checkUser.tempAccountsOnboarding/components/icons.json', () => ( { cdxIconNext: '', cdxIconPrevious: '' } ), { virtual: true } ); // Mock the @wikimedia/codex useComputedDirection method to return a fake value. // We cannot easily set up the DOM to support this method as it would require // modifying the DOM created by Vue for the test. const useComputedDirectionMock = jest.fn(); useComputedDirectionMock.mockReturnValue( { value: 'ltr' } ); jest.mock( '@wikimedia/codex', () => { const originalModule = jest.requireActual( '@wikimedia/codex' ); return Object.assign( { __esModule: true }, originalModule, { useComputedDirection: useComputedDirectionMock } ); } ); const TempAccountsOnboardingDialog = require( '../../../../modules/ext.checkUser.tempAccountsOnboarding/components/TempAccountsOnboardingDialog.vue' ), utils = require( '@vue/test-utils' ), { nextTick } = require( 'vue' ), { mockApiSaveOption, waitFor } = require( '../../utils.js' ); const renderComponent = ( slots, props ) => utils.mount( TempAccountsOnboardingDialog, { props: Object.assign( {}, { steps: [] }, props ), slots: Object.assign( {}, slots ) } ); /** * Simulates a swipe to the right * * @param {*} contentElement The element containing the step content * @return {Promise} */ async function swipeToRight( contentElement ) { // Start the touch await contentElement.trigger( 'touchstart', { touches: [ { clientX: 100, clientY: 20 } ] } ); // Then finish the touch simulating a move to the right. await contentElement.trigger( 'touchmove', { touches: [ { clientX: 150, clientY: 15 } ] } ); } /** * Simulates a swipe to the left * * @param {*} contentElement The element containing the step content * @return {Promise} */ async function swipeToLeft( contentElement ) { // Start the touch await contentElement.trigger( 'touchstart', { touches: [ { clientX: 100, clientY: 10 } ] } ); // Then finish the touch simulating a move to the left. await contentElement.trigger( 'touchmove', { touches: [ { clientX: 50, clientY: 20 } ] } ); } /** * Simulates a swipe upwards * * @param {*} contentElement The element containing the step content * @return {Promise} */ async function swipeUp( contentElement ) { // Start the touch await contentElement.trigger( 'touchstart', { touches: [ { clientX: 20, clientY: 100 } ] } ); // Then finish the touch simulating a swipe up await contentElement.trigger( 'touchmove', { touches: [ { clientX: 10, clientY: 10 } ] } ); } describe( 'Temporary Accounts dialog component', () => { beforeEach( () => { jest.spyOn( mw.language, 'convertNumber' ).mockImplementation( ( number ) => number ); } ); afterEach( () => { jest.restoreAllMocks(); } ); it( 'Renders correctly for a total of one step', () => { const wrapper = renderComponent( { step1: '
Test content for step 1
' }, { steps: [ { name: 'step1' } ] } ); expect( wrapper.exists() ).toEqual( true ); // Check the dialog element exists. const dialog = wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog' ); expect( dialog.exists() ).toEqual( true ); // Check that the header and footer elements exist. const header = dialog.find( '.ext-checkuser-temp-account-onboarding-dialog__header' ); expect( header.exists() ).toEqual( true ); const footer = dialog.find( '.ext-checkuser-temp-account-onboarding-dialog__footer' ); expect( footer.exists() ).toEqual( true ); // Check that the header element contains the "Skip all" button, the title, // and the stepper component. const skipAllButton = header.find( '.ext-checkuser-temp-account-onboarding-dialog__header__top__button' ); expect( skipAllButton.text() ).toEqual( '(checkuser-temporary-accounts-onboarding-dialog-skip-all)' ); const title = header.find( '.ext-checkuser-temp-account-onboarding-dialog__header__top__title' ); expect( title.text() ).toEqual( '(checkuser-temporary-accounts-onboarding-dialog-title)' ); const stepperComponent = header.find( '.ext-checkuser-temp-account-onboarding-dialog__header__stepper' ); expect( stepperComponent.exists() ).toEqual( true ); // Check that the footer contains the "Close" button only const closeButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--next' ); expect( closeButton.text() ).toEqual( '(checkuser-temporary-accounts-onboarding-dialog-close-label)' ); const previousButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--prev' ); expect( previousButton.exists() ).toEqual( false ); // Expect that the content exists in the dialog that is defined via a slot const contentElement = wrapper.find( '.test-class' ); expect( contentElement.exists() ).toEqual( true ); expect( contentElement.text() ).toEqual( 'Test content for step 1' ); } ); it( 'Moves forward a step when next button clicked', async () => { const wrapper = renderComponent( { step1: '
Test content for step 1
', step2: '
Test content for step 2
' }, { steps: [ { name: 'step1' }, { name: 'step2' } ] } ); expect( wrapper.exists() ).toEqual( true ); // Check the footer has a "Next" button only. const footer = wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog__footer' ); const nextButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--next' ); expect( nextButton.exists() ).toEqual( true ); expect( nextButton.attributes() ).toHaveProperty( 'aria-label', '(checkuser-temporary-accounts-onboarding-dialog-next-label)' ); // Check that initially the step 1 content is displayed expect( wrapper.find( '.step1' ).exists() ).toEqual( true ); expect( wrapper.find( '.step2' ).exists() ).toEqual( false ); // Click the next button and wait for the DOM to be updated. await nextButton.trigger( 'click' ); await waitFor( () => !wrapper.find( '.step1' ).exists() ); // Expect that the previous and close buttons are now shown instead of next. const closeButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--next' ); expect( closeButton.exists() ).toEqual( true ); expect( closeButton.text() ).toEqual( '(checkuser-temporary-accounts-onboarding-dialog-close-label)' ); const previousButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--prev' ); expect( previousButton.exists() ).toEqual( true ); expect( previousButton.attributes() ).toHaveProperty( 'aria-label', '(checkuser-temporary-accounts-onboarding-dialog-previous-label)' ); // Expect that the second step is now shown expect( wrapper.find( '.step1' ).exists() ).toEqual( false ); expect( wrapper.find( '.step2' ).exists() ).toEqual( true ); } ); it( 'Moves back a step when previous button clicked', async () => { const wrapper = renderComponent( { step1: '
Test content for step 1
', step2: '
Test content for step 2
' }, { steps: [ { name: 'step1' }, { name: 'step2' } ] } ); expect( wrapper.exists() ).toEqual( true ); // Click the next button to move to the second step and wait for the DOM to be updated. const footer = wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog__footer' ); const nextButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--next' ); expect( nextButton.exists() ).toEqual( true ); await nextButton.trigger( 'click' ); await waitFor( () => !wrapper.find( '.step1' ).exists() ); // Verify that the next button click worked. expect( wrapper.find( '.step1' ).exists() ).toEqual( false ); expect( wrapper.find( '.step2' ).exists() ).toEqual( true ); // Click the previous button to back to the first step and wait for the DOM to be updated. const previousButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--prev' ); expect( previousButton.exists() ).toEqual( true ); await previousButton.trigger( 'click' ); await waitFor( () => !wrapper.find( '.step2' ).exists() ); // Verify that we are now back on the first step after clicking the previous button expect( wrapper.find( '.step1' ).exists() ).toEqual( true ); expect( wrapper.find( '.step2' ).exists() ).toEqual( false ); } ); it( 'Prevents moving a step if canMoveToAnotherStep returns false', async () => { const canMoveToAnotherStep = jest.fn(); canMoveToAnotherStep.mockReturnValue( false ); const fakeRefThatDisallowsStepMove = { value: { canMoveToAnotherStep: canMoveToAnotherStep } }; const wrapper = renderComponent( { step1: '
Test content for step 1
', step2: '
Test content for step 2
' }, { steps: [ { name: 'step1', ref: fakeRefThatDisallowsStepMove }, { name: 'step2' } ] } ); expect( wrapper.exists() ).toEqual( true ); // Click the next button to move to the second step. const footer = wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog__footer' ); const nextButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--next' ); expect( nextButton.exists() ).toEqual( true ); await nextButton.trigger( 'click' ); // Verify that the next button click was prevented expect( canMoveToAnotherStep ).toHaveBeenCalled(); expect( wrapper.find( '.step1' ).exists() ).toEqual( true ); expect( wrapper.find( '.step2' ).exists() ).toEqual( false ); // Check that the previous button does not exist, as we did not move a step const previousButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--prev' ); expect( previousButton.exists() ).toEqual( false ); } ); it( 'Dialog should move steps when user swipes left and right', async () => { const wrapper = renderComponent( { step1: '
Test content for step 1
', step2: '
Test content for step 2
' }, { steps: [ { name: 'step1' }, { name: 'step2' } ] } ); expect( wrapper.exists() ).toEqual( true ); const contentElement = wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog-content' ); // Swipe to the left to move to the next step await swipeToLeft( contentElement ); await waitFor( () => !contentElement.find( '.step1' ).exists() ); // Verify that the swipe worked expect( contentElement.find( '.step1' ).exists() ).toEqual( false ); expect( contentElement.find( '.step2' ).exists() ).toEqual( true ); // Swipe to the right to move to the previous step await swipeToRight( contentElement ); await waitFor( () => !contentElement.find( '.step2' ).exists() ); // Verify that we are now back on the first step after swiping expect( contentElement.find( '.step1' ).exists() ).toEqual( true ); expect( contentElement.find( '.step2' ).exists() ).toEqual( false ); // Swipe upwards which should have no effect (T385043) await swipeUp( contentElement ); // Verify that we have not moved step by swiping upwards. expect( contentElement.find( '.step1' ).exists() ).toEqual( true ); expect( contentElement.find( '.step2' ).exists() ).toEqual( false ); } ); it( 'Dialog should move steps when user swipes left and right when in RTL', async () => { useComputedDirectionMock.mockReturnValue( { value: 'rtl' } ); const wrapper = renderComponent( { step1: '
Test content for step 1
', step2: '
Test content for step 2
' }, { steps: [ { name: 'step1' }, { name: 'step2' } ] } ); expect( wrapper.exists() ).toEqual( true ); const contentElement = wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog-content' ); // Swipe to the right to move to the next step await swipeToRight( contentElement ); await waitFor( () => !contentElement.find( '.step1' ).exists() ); // Verify that the swipe worked expect( contentElement.find( '.step1' ).exists() ).toEqual( false ); expect( contentElement.find( '.step2' ).exists() ).toEqual( true ); // Swipe to the left to move to the previous step await swipeToLeft( contentElement ); await waitFor( () => !contentElement.find( '.step2' ).exists() ); // Verify that we are now back on the first step after swiping expect( contentElement.find( '.step1' ).exists() ).toEqual( true ); expect( contentElement.find( '.step2' ).exists() ).toEqual( false ); } ); it( 'Closes dialog if "Skip all" pressed and marks dialog as seen', async () => { const mockSaveOption = mockApiSaveOption( true ); const wrapper = renderComponent( { step1: '
Test content for step 1
', step2: '
Test content for step 2
' }, { steps: [ { name: 'step1' }, { name: 'step2' } ] } ); // Wait for the next tick as CdxDialog has some async code to finish first. await nextTick(); expect( wrapper.exists() ).toEqual( true ); // Click the "Skip all" button and wait for the open property to be updated. const dialog = wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog' ); const header = dialog.find( '.ext-checkuser-temp-account-onboarding-dialog__header' ); const skipAllButton = header.find( '.ext-checkuser-temp-account-onboarding-dialog__header__top__button' ); expect( skipAllButton.exists() ).toEqual( true ); await skipAllButton.trigger( 'click' ); // Expect the dialog has been closed, and the preference has been set to // indicate that the dialog been seen. expect( wrapper.vm.dialogOpen ).toEqual( false ); expect( mockSaveOption ).toHaveBeenCalledWith( 'checkuser-temporary-accounts-onboarding-dialog-seen', 1, { global: 'create' } ); } ); it( 'Closes dialog if "Close" button pressed and marks dialog as seen', async () => { const mockSaveOption = mockApiSaveOption( true ); const wrapper = renderComponent( { step1: '
Test content for step 1
' }, { steps: [ { name: 'step1' } ] } ); // Wait for the next tick as CdxDialog has some async code to finish first. await nextTick(); expect( wrapper.exists() ).toEqual( true ); // Click the "Close" button and wait for the open property to be updated. const dialog = wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog' ); const footer = dialog.find( '.ext-checkuser-temp-account-onboarding-dialog__footer' ); const closeButton = footer.find( '.ext-checkuser-temp-account-onboarding-dialog__footer__navigation--next' ); expect( closeButton.text() ).toEqual( '(checkuser-temporary-accounts-onboarding-dialog-close-label)' ); await closeButton.trigger( 'click' ); // Expect the dialog has been closed, and the preference has been set to // indicate that the dialog been seen. expect( wrapper.vm.dialogOpen ).toEqual( false ); expect( mockSaveOption ).toHaveBeenCalledWith( 'checkuser-temporary-accounts-onboarding-dialog-seen', 1, { global: 'create' } ); } ); it( 'Prevents first close if shouldWarnBeforeClosingDialog returns false', async () => { const mockSaveOption = mockApiSaveOption( true ); // Mock that the current step returns true from shouldWarnBeforeClosingDialog() const shouldWarnBeforeClosingDialog = jest.fn(); shouldWarnBeforeClosingDialog.mockReturnValue( true ); const fakeRefThatDisallowsDialogClose = { value: { shouldWarnBeforeClosingDialog: shouldWarnBeforeClosingDialog } }; const wrapper = renderComponent( { step1: '
Test content for step 1
' }, { steps: [ { name: 'step1', ref: fakeRefThatDisallowsDialogClose } ] } ); // Wait for the next tick as CdxDialog has some async code to finish first. await nextTick(); expect( wrapper.exists() ).toEqual( true ); // Wait for the next tick as CdxDialog has some async code to finish first. await nextTick(); // Click the "Skip all" button const dialog = wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog' ); const skipAllButton = dialog.find( '.ext-checkuser-temp-account-onboarding-dialog__header__top__button' ); expect( skipAllButton.text() ).toEqual( '(checkuser-temporary-accounts-onboarding-dialog-skip-all)' ); await skipAllButton.trigger( 'click' ); // Verify that the dialog is still open expect( shouldWarnBeforeClosingDialog ).toHaveBeenCalled(); expect( dialog.exists() ).toEqual( true ); expect( dialog.find( '.step1' ).exists() ).toEqual( true ); expect( wrapper.vm.dialogOpen ).toEqual( true ); // Press "Skip all" again and verify that the dialog now closes await skipAllButton.trigger( 'click' ); expect( wrapper.vm.dialogOpen ).toEqual( false ); // Verify that the close caused the dialog seen preference to have been set. expect( mockSaveOption ).toHaveBeenCalledWith( 'checkuser-temporary-accounts-onboarding-dialog-seen', 1, { global: 'create' } ); } ); it( 'Closes dialog if Escape key is pressed', async () => { const wrapper = renderComponent( { step1: '
Test content for step 1
' }, { steps: [ { name: 'step1' } ] } ); // Wait for the next tick as CdxDialog has some async code to finish first. await nextTick(); expect( wrapper.exists() ).toEqual( true ); // Simulate an Escape keypress and expect that this closes the dialog await wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog' ).trigger( 'keyup.escape' ); expect( wrapper.vm.dialogOpen ).toEqual( false ); } ); it( 'Prevents first Escape key if shouldWarnBeforeClosingDialog returns false', async () => { // Mock that the current step returns true from shouldWarnBeforeClosingDialog() const shouldWarnBeforeClosingDialog = jest.fn(); shouldWarnBeforeClosingDialog.mockReturnValue( true ); const fakeRefThatDisallowsDialogClose = { value: { shouldWarnBeforeClosingDialog: shouldWarnBeforeClosingDialog } }; const wrapper = renderComponent( { step1: '
Test content for step 1
' }, { steps: [ { name: 'step1', ref: fakeRefThatDisallowsDialogClose } ] } ); // Wait for the next tick as CdxDialog has some async code to finish first. await nextTick(); expect( wrapper.exists() ).toEqual( true ); // Simulate an Escape keypress and expect that this first press does nothing await wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog' ) .trigger( 'keyup.escape' ); expect( shouldWarnBeforeClosingDialog ).toHaveBeenCalled(); expect( wrapper.vm.dialogOpen ).toEqual( true ); // Press Escape again and expect that this time it hides the dialog await wrapper.find( '.ext-checkuser-temp-account-onboarding-dialog' ) .trigger( 'keyup.escape' ); expect( wrapper.vm.dialogOpen ).toEqual( false ); } ); } );