From a473122250fc6d0e445e090658ed502411a94e16 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Wed, 9 Aug 2023 19:38:13 +0200 Subject: [PATCH] Interactivity API: Add missing tests for `body`, `init` and `on` directives (#52952) * Add tests for wp-body * Simplify wp-body directive * Add tests for wp-init * Add tests for `wp-on` directive * Rename fakeshow to show-mock in init tests * Remove unnecessary part of `wp-show-mock` * Subscribe to context changes inside `initOne` --------- Co-authored-by: Luis Herranz --- .../directive-body/block.json | 14 ++++ .../directive-body/render.php | 22 ++++++ .../interactive-blocks/directive-body/view.js | 11 +++ .../directive-init/block.json | 14 ++++ .../directive-init/render.php | 42 ++++++++++ .../interactive-blocks/directive-init/view.js | 64 ++++++++++++++++ .../directive-on/block.json | 14 ++++ .../directive-on/render.php | 52 +++++++++++++ .../interactive-blocks/directive-on/view.js | 27 +++++++ packages/interactivity/src/directives.js | 9 +-- .../interactivity/directive-init.spec.ts | 76 +++++++++++++++++++ .../specs/interactivity/directive-on.spec.ts | 54 +++++++++++++ .../interactivity/directives-body.spec.ts | 47 ++++++++++++ 13 files changed, 439 insertions(+), 7 deletions(-) create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-body/block.json create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-body/render.php create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-body/view.js create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-init/block.json create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-init/render.php create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-init/view.js create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-on/block.json create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-on/render.php create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-on/view.js create mode 100644 test/e2e/specs/interactivity/directive-init.spec.ts create mode 100644 test/e2e/specs/interactivity/directive-on.spec.ts create mode 100644 test/e2e/specs/interactivity/directives-body.spec.ts diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-body/block.json b/packages/e2e-tests/plugins/interactive-blocks/directive-body/block.json new file mode 100644 index 00000000000000..61b48396be08ae --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-body/block.json @@ -0,0 +1,14 @@ +{ + "apiVersion": 2, + "name": "test/directive-body", + "title": "E2E Interactivity tests - directive body", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewScript": "directive-body-view", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-body/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-body/render.php new file mode 100644 index 00000000000000..5e24b7d7a3b9b5 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-body/render.php @@ -0,0 +1,22 @@ + +
+
+ +
+ +
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-body/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-body/view.js new file mode 100644 index 00000000000000..f3cbc521f4355b --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-body/view.js @@ -0,0 +1,11 @@ +( ( { wp } ) => { + const { store } = wp.interactivity; + + store( { + actions: { + toggleText: ( { context } ) => { + context.text = context.text === 'text-1' ? 'text-2' : 'text-1'; + }, + }, + } ); +} )( window ); diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-init/block.json b/packages/e2e-tests/plugins/interactive-blocks/directive-init/block.json new file mode 100644 index 00000000000000..a7e195d2e4884a --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-init/block.json @@ -0,0 +1,14 @@ +{ + "apiVersion": 2, + "name": "test/directive-init", + "title": "E2E Interactivity tests - directive init", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewScript": "directive-init-view", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-init/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-init/render.php new file mode 100644 index 00000000000000..76d5b776a68bb3 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-init/render.php @@ -0,0 +1,42 @@ + +
+
+

false

+

0

+ +
+
+

false,false

+

0,0

+
+
+
+ Initially visible +
+ +

+ true +

+
+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-init/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-init/view.js new file mode 100644 index 00000000000000..274809df3a9e5c --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-init/view.js @@ -0,0 +1,64 @@ +( ( { wp } ) => { + const { store, directive, useContext } = wp.interactivity; + + // Mock `data-wp-show` directive to test when things are removed from the + // DOM. Replace with `data-wp-show` when it's ready. + directive( + 'show-mock', + ( { + directives: { + 'show-mock': { default: showMock }, + }, + element, + evaluate, + context, + } ) => { + const contextValue = useContext( context ); + if ( ! evaluate( showMock, { context: contextValue } ) ) { + return null; + } + return element; + } + ); + + + store( { + selector: { + isReady: ({ context: { isReady } }) => { + return isReady + .map(v => v ? 'true': 'false') + .join(','); + }, + calls: ({ context: { calls } }) => { + return calls.join(','); + }, + isMounted: ({ context }) => { + return context.isMounted ? 'true' : 'false'; + }, + }, + actions: { + initOne: ( { context: { isReady, calls } } ) => { + isReady[0] = true; + // Subscribe to changes in that prop. + isReady[0] = isReady[0]; + calls[0]++; + }, + initTwo: ( { context: { isReady, calls } } ) => { + isReady[1] = true; + calls[1]++; + }, + initMount: ( { context } ) => { + context.isMounted = true; + return () => { + context.isMounted = false; + } + }, + reset: ( { context: { isReady } } ) => { + isReady.fill(false); + }, + toggle: ( { context } ) => { + context.isVisible = ! context.isVisible; + }, + }, + } ); +} )( window ); diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-on/block.json b/packages/e2e-tests/plugins/interactive-blocks/directive-on/block.json new file mode 100644 index 00000000000000..b9d8aa5f9ce57d --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-on/block.json @@ -0,0 +1,14 @@ +{ + "apiVersion": 2, + "name": "test/directive-on", + "title": "E2E Interactivity tests - directive on", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewScript": "directive-on-view", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-on/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-on/render.php new file mode 100644 index 00000000000000..9d96c7768a4894 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-on/render.php @@ -0,0 +1,52 @@ + +
+
+

0

+ +
+
+

initial

+ +
+
+

0

+ +
+
+

0

+ +
+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-on/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-on/view.js new file mode 100644 index 00000000000000..c93b4d5ed34e63 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-on/view.js @@ -0,0 +1,27 @@ +( ( { wp } ) => { + const { store } = wp.interactivity; + + store( { + state: { + counter: 0, + text: '' + }, + actions: { + clickHandler: ( { state, event } ) => { + state.counter += 1; + event.target.dispatchEvent( + new CustomEvent( 'customevent', { bubbles: true } ) + ); + }, + inputHandler: ( { state, event } ) => { + state.text = event.target.value; + }, + selectHandler: ( { context, event } ) => { + context.option = event.target.value; + }, + customEventHandler: ({ context }) => { + context.customEvents += 1; + }, + }, + } ); +} )( window ); diff --git a/packages/interactivity/src/directives.js b/packages/interactivity/src/directives.js index a25e83c170e8d0..16789bd9da0522 100644 --- a/packages/interactivity/src/directives.js +++ b/packages/interactivity/src/directives.js @@ -55,13 +55,8 @@ export default () => { ); // data-wp-body - directive( 'body', ( { props: { children }, context: inherited } ) => { - const { Provider } = inherited; - const inheritedValue = useContext( inherited ); - return createPortal( - { children }, - document.body - ); + directive( 'body', ( { props: { children } } ) => { + return createPortal( children, document.body ); } ); // data-wp-effect--[name] diff --git a/test/e2e/specs/interactivity/directive-init.spec.ts b/test/e2e/specs/interactivity/directive-init.spec.ts new file mode 100644 index 00000000000000..aa81ab1ea61db2 --- /dev/null +++ b/test/e2e/specs/interactivity/directive-init.spec.ts @@ -0,0 +1,76 @@ +/** + * Internal dependencies + */ +import { test, expect } from './fixtures'; + +test.describe( 'data-wp-init', () => { + test.beforeAll( async ( { interactivityUtils: utils } ) => { + await utils.activatePlugins(); + await utils.addPostWithBlock( 'test/directive-init' ); + } ); + + test.beforeEach( async ( { interactivityUtils: utils, page } ) => { + await page.goto( utils.getLink( 'test/directive-init' ) ); + } ); + + test.afterAll( async ( { interactivityUtils: utils } ) => { + await utils.deactivatePlugins(); + await utils.deleteAllPosts(); + } ); + + test( 'should run when the block renders', async ( { page } ) => { + const el = page.getByTestId( 'single init' ); + await expect( el.getByTestId( 'isReady' ) ).toHaveText( 'true' ); + await expect( el.getByTestId( 'calls' ) ).toHaveText( '1' ); + } ); + + test( 'should not run again if accessed signals change', async ( { + page, + } ) => { + const el = page.getByTestId( 'single init' ); + await expect( el.getByTestId( 'isReady' ) ).toHaveText( 'true' ); + await el.getByRole( 'button' ).click(); + await expect( el.getByTestId( 'isReady' ) ).toHaveText( 'false' ); + await expect( el.getByTestId( 'calls' ) ).toHaveText( '1' ); + } ); + + test( 'should run multiple inits if defined', async ( { page } ) => { + const el = page.getByTestId( 'multiple inits' ); + await expect( el.getByTestId( 'isReady' ) ).toHaveText( 'true,true' ); + await expect( el.getByTestId( 'calls' ) ).toHaveText( '1,1' ); + } ); + + test( 'should run the init callback when the element is unmounted', async ( { + page, + } ) => { + const container = page.getByTestId( 'init show' ); + const show = container.getByTestId( 'show' ); + const toggle = container.getByTestId( 'toggle' ); + const isMounted = container.getByTestId( 'isMounted' ); + + await expect( show ).toHaveText( 'Initially visible' ); + await expect( isMounted ).toHaveText( 'true' ); + + await toggle.click(); + + await expect( show ).not.toBeVisible(); + await expect( isMounted ).toHaveText( 'false' ); + } ); + + test( 'should run init when the element is mounted', async ( { page } ) => { + const container = page.getByTestId( 'init show' ); + const show = container.getByTestId( 'show' ); + const toggle = container.getByTestId( 'toggle' ); + const isMounted = container.getByTestId( 'isMounted' ); + + await toggle.click(); + + await expect( show ).not.toBeVisible(); + await expect( isMounted ).toHaveText( 'false' ); + + await toggle.click(); + + await expect( show ).toHaveText( 'Initially visible' ); + await expect( isMounted ).toHaveText( 'true' ); + } ); +} ); diff --git a/test/e2e/specs/interactivity/directive-on.spec.ts b/test/e2e/specs/interactivity/directive-on.spec.ts new file mode 100644 index 00000000000000..03dfe64462c567 --- /dev/null +++ b/test/e2e/specs/interactivity/directive-on.spec.ts @@ -0,0 +1,54 @@ +/** + * Internal dependencies + */ +import { test, expect } from './fixtures'; + +test.describe( 'data-wp-on', () => { + test.beforeAll( async ( { interactivityUtils: utils } ) => { + await utils.activatePlugins(); + await utils.addPostWithBlock( 'test/directive-on' ); + } ); + + test.beforeEach( async ( { interactivityUtils: utils, page } ) => { + await page.goto( utils.getLink( 'test/directive-on' ) ); + } ); + + test.afterAll( async ( { interactivityUtils: utils } ) => { + await utils.deactivatePlugins(); + await utils.deleteAllPosts(); + } ); + + test( 'callbacks should run whenever the specified event is dispatched', async ( { + page, + } ) => { + const counter = page.getByTestId( 'counter' ); + await page + .getByTestId( 'button' ) + .click( { clickCount: 3, delay: 100 } ); + await expect( counter ).toHaveText( '3' ); + } ); + + test( 'callbacks should receive the dispatched event', async ( { + page, + } ) => { + const text = page.getByTestId( 'text' ); + await page.getByTestId( 'input' ).fill( 'hello!' ); + await expect( text ).toHaveText( 'hello!' ); + } ); + + test( 'callbacks should be able to access the context', async ( { + page, + } ) => { + const option = page.getByTestId( 'option' ); + await page.getByTestId( 'select' ).selectOption( 'dog' ); + await expect( option ).toHaveText( 'dog' ); + } ); + + test( 'should work with custom events', async ( { page } ) => { + const counter = page.getByTestId( 'custom events counter' ); + await page + .getByTestId( 'custom events button' ) + .click( { clickCount: 3, delay: 100 } ); + await expect( counter ).toHaveText( '3' ); + } ); +} ); diff --git a/test/e2e/specs/interactivity/directives-body.spec.ts b/test/e2e/specs/interactivity/directives-body.spec.ts new file mode 100644 index 00000000000000..be11cfc556b591 --- /dev/null +++ b/test/e2e/specs/interactivity/directives-body.spec.ts @@ -0,0 +1,47 @@ +/** + * Internal dependencies + */ +import { test, expect } from './fixtures'; + +test.describe( 'data-wp-body', () => { + test.beforeAll( async ( { interactivityUtils: utils } ) => { + await utils.activatePlugins(); + await utils.addPostWithBlock( 'test/directive-body' ); + } ); + + test.beforeEach( async ( { interactivityUtils: utils, page } ) => { + await page.goto( utils.getLink( 'test/directive-body' ) ); + } ); + + test.afterAll( async ( { interactivityUtils: utils } ) => { + await utils.deactivatePlugins(); + await utils.deleteAllPosts(); + } ); + + test( "should move the element to the document's body", async ( { + page, + } ) => { + const container = page.getByTestId( 'container' ); + const parentTag = page + .getByTestId( 'element with data-wp-body' ) + .locator( 'xpath=..' ); + + await expect( container ).toBeEmpty(); + await expect( parentTag ).toHaveJSProperty( 'tagName', 'BODY' ); + } ); + + test( 'should make context accessible for inner elements', async ( { + page, + } ) => { + const text = page + .getByTestId( 'element with data-wp-body' ) + .getByTestId( 'text' ); + const toggle = page.getByTestId( 'toggle text' ); + + await expect( text ).toHaveText( 'text-1' ); + await toggle.click(); + await expect( text ).toHaveText( 'text-2' ); + await toggle.click(); + await expect( text ).toHaveText( 'text-1' ); + } ); +} );