From fc960139a422c384f72208afe6eb5c583fd029c5 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Thu, 4 Jul 2024 12:03:36 +0200 Subject: [PATCH 1/4] Add interactive block "test/get-element" for E2E interactivity tests --- .../interactive-blocks/get-element/block.json | 15 +++++++ .../interactive-blocks/get-element/render.php | 28 ++++++++++++ .../get-element/view.asset.php | 1 + .../interactive-blocks/get-element/view.js | 31 +++++++++++++ .../specs/interactivity/get-element.spec.ts | 43 +++++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 packages/e2e-tests/plugins/interactive-blocks/get-element/block.json create mode 100644 packages/e2e-tests/plugins/interactive-blocks/get-element/render.php create mode 100644 packages/e2e-tests/plugins/interactive-blocks/get-element/view.asset.php create mode 100644 packages/e2e-tests/plugins/interactive-blocks/get-element/view.js create mode 100644 test/e2e/specs/interactivity/get-element.spec.ts diff --git a/packages/e2e-tests/plugins/interactive-blocks/get-element/block.json b/packages/e2e-tests/plugins/interactive-blocks/get-element/block.json new file mode 100644 index 00000000000000..95fd5ddcb21144 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/get-element/block.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/get-element", + "title": "E2E Interactivity tests - getElement", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewScriptModule": "file:./view.js", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/get-element/render.php b/packages/e2e-tests/plugins/interactive-blocks/get-element/render.php new file mode 100644 index 00000000000000..5c07d653100655 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/get-element/render.php @@ -0,0 +1,28 @@ + + +
+
+ + +
diff --git a/packages/e2e-tests/plugins/interactive-blocks/get-element/view.asset.php b/packages/e2e-tests/plugins/interactive-blocks/get-element/view.asset.php new file mode 100644 index 00000000000000..db23afdf657a19 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/get-element/view.asset.php @@ -0,0 +1 @@ + array( '@wordpress/interactivity' ) ); diff --git a/packages/e2e-tests/plugins/interactive-blocks/get-element/view.js b/packages/e2e-tests/plugins/interactive-blocks/get-element/view.js new file mode 100644 index 00000000000000..b4e9ebaa78c49e --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/get-element/view.js @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +import { store, getElement } from '@wordpress/interactivity'; + +const { state } = store( 'test/get-element', { + state: { + prefix: '+', + dataSomeValue: 'Initial value', + get someValue() { + const { attributes } = getElement(); + return attributes[ 'data-some-value' ]; // Should this be reactive? + }, + }, + actions: { + mutateDOM() { + state.prefix = '++'; + const el = document.querySelector( + '[data-testid="read from attributes]' + ); + el.setAttribute( 'data-some-value', 'New DOM value' ); + }, + mutateProp() { + const { attributes } = getElement(); + + attributes[ 'data-some-value' ] = state.dataSomeValue; // Does this make sense? + + state.dataSomeValue = 'New prop value'; + }, + }, +} ); diff --git a/test/e2e/specs/interactivity/get-element.spec.ts b/test/e2e/specs/interactivity/get-element.spec.ts new file mode 100644 index 00000000000000..f1ce7d5579fcba --- /dev/null +++ b/test/e2e/specs/interactivity/get-element.spec.ts @@ -0,0 +1,43 @@ +/** + * Internal dependencies + */ +import { test, expect } from './fixtures'; + +test.describe( 'store', () => { + test.beforeAll( async ( { interactivityUtils: utils } ) => { + await utils.activatePlugins(); + await utils.addPostWithBlock( 'test/get-element' ); + } ); + + test.beforeEach( async ( { interactivityUtils: utils, page } ) => { + await page.goto( utils.getLink( 'test/get-element' ) ); + } ); + + test.afterAll( async ( { interactivityUtils: utils } ) => { + await utils.deactivatePlugins(); + await utils.deleteAllPosts(); + } ); + + test( 'initial attributes can be accessed', async ( { page } ) => { + const el = page.getByTestId( 'read from attributes' ); + await expect( el ).toHaveText( '+Initial value' ); + } ); + + test( 'mutated attributes via DOM manipulation can be accessed', async ( { + page, + } ) => { + const button = page.getByTestId( 'mutate DOM' ); + await button.click(); + const el = page.getByTestId( 'read from attributes' ); + await expect( el ).toHaveText( '++New DOM value' ); + } ); + + test( 'mutated attributes via data-wp-bind can be accessed', async ( { + page, + } ) => { + const button = page.getByTestId( 'mutate prop' ); + await button.click(); + const el = page.getByTestId( 'read from attributes' ); + await expect( el ).toHaveText( 'New prop value' ); + } ); +} ); From 40fe77ee54fc10c452d2f3972cb5f0bde657825d Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 10 Jul 2024 14:17:39 +0100 Subject: [PATCH 2/4] chore: Fix typo in data-testid attribute value --- .../e2e-tests/plugins/interactive-blocks/get-element/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-tests/plugins/interactive-blocks/get-element/view.js b/packages/e2e-tests/plugins/interactive-blocks/get-element/view.js index b4e9ebaa78c49e..d47ec4501c167d 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/get-element/view.js +++ b/packages/e2e-tests/plugins/interactive-blocks/get-element/view.js @@ -16,7 +16,7 @@ const { state } = store( 'test/get-element', { mutateDOM() { state.prefix = '++'; const el = document.querySelector( - '[data-testid="read from attributes]' + '[data-testid="read from attributes"]' ); el.setAttribute( 'data-some-value', 'New DOM value' ); }, From 82c1bc209ddd955025e2866ca449f6a269f4ecf7 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 10 Jul 2024 14:18:39 +0100 Subject: [PATCH 3/4] Remove prefix from e2e test assertions --- test/e2e/specs/interactivity/get-element.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/specs/interactivity/get-element.spec.ts b/test/e2e/specs/interactivity/get-element.spec.ts index f1ce7d5579fcba..7d277f6f740f9d 100644 --- a/test/e2e/specs/interactivity/get-element.spec.ts +++ b/test/e2e/specs/interactivity/get-element.spec.ts @@ -20,7 +20,7 @@ test.describe( 'store', () => { test( 'initial attributes can be accessed', async ( { page } ) => { const el = page.getByTestId( 'read from attributes' ); - await expect( el ).toHaveText( '+Initial value' ); + await expect( el ).toHaveText( 'Initial value' ); } ); test( 'mutated attributes via DOM manipulation can be accessed', async ( { @@ -29,7 +29,7 @@ test.describe( 'store', () => { const button = page.getByTestId( 'mutate DOM' ); await button.click(); const el = page.getByTestId( 'read from attributes' ); - await expect( el ).toHaveText( '++New DOM value' ); + await expect( el ).toHaveText( 'New DOM value' ); } ); test( 'mutated attributes via data-wp-bind can be accessed', async ( { From be82b9aafc9adbe30c07765ad1540c1c9fcd3e88 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Tue, 27 Aug 2024 13:54:56 +0200 Subject: [PATCH 4/4] Replace `deepImmutable` with `Object.freeze` --- packages/interactivity/src/hooks.tsx | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/packages/interactivity/src/hooks.tsx b/packages/interactivity/src/hooks.tsx index 9af6fb00d6aba5..4a16bfde6d4dec 100644 --- a/packages/interactivity/src/hooks.tsx +++ b/packages/interactivity/src/hooks.tsx @@ -101,30 +101,6 @@ interface DirectivesProps { // Main context. const context = createContext< any >( {} ); -// Wrap the element props to prevent modifications. -const immutableMap = new WeakMap(); -const immutableError = () => { - throw new Error( - 'Please use `data-wp-bind` to modify the attributes of an element.' - ); -}; -const immutableHandlers: ProxyHandler< object > = { - get( target, key, receiver ) { - const value = Reflect.get( target, key, receiver ); - return !! value && typeof value === 'object' - ? deepImmutable( value ) - : value; - }, - set: immutableError, - deleteProperty: immutableError, -}; -const deepImmutable = < T extends object = {} >( target: T ): T => { - if ( ! immutableMap.has( target ) ) { - immutableMap.set( target, new Proxy( target, immutableHandlers ) ); - } - return immutableMap.get( target ); -}; - // Store stacks for the current scope and the default namespaces and export APIs // to interact with them. const scopeStack: Scope[] = []; @@ -158,7 +134,7 @@ export const getElement = () => { const { ref, attributes } = getScope(); return Object.freeze( { ref: ref.current, - attributes: deepImmutable( attributes ), + attributes: Object.freeze( { ...attributes } ), } ); };