diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php index 25d2bbc692efdf..6b20b2dba8376e 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php @@ -170,3 +170,29 @@
+ +
+ +
+ + +
+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js index 0fab8b1862c608..9437edced74a44 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js @@ -88,3 +88,21 @@ const { actions } = store( 'directive-context-navigate', { }, }, } ); + +store( 'directive-context-watch', { + actions: { + increment: () => { + const ctx = getContext(); + ctx.counter = ctx.counter + 1; + }, + }, + callbacks: { + countChanges: () => { + const ctx = getContext(); + // Subscribe to changes in counter. + // eslint-disable-next-line no-unused-expressions + ctx.counter; + ctx.changes = ctx.changes + 1; + }, + }, +}); diff --git a/packages/interactivity/src/directives.js b/packages/interactivity/src/directives.js index 67ad50a4595807..d04c1a1f74d3ed 100644 --- a/packages/interactivity/src/directives.js +++ b/packages/interactivity/src/directives.js @@ -39,13 +39,12 @@ const descriptor = Reflect.getOwnPropertyDescriptor; const proxifyContext = ( current, inherited = {} ) => new Proxy( current, { get: ( target, k ) => { - // Subscribe to the inherited and current props. - const inheritedProp = inherited[ k ]; + // Always subscribe to prop changes in the current context. const currentProp = target[ k ]; // Return the inherited prop when missing in target. if ( ! ( k in target ) && k in inherited ) { - return inheritedProp; + return inherited[ k ]; } // Proxify plain objects that are not listed in `ignore`. @@ -54,11 +53,15 @@ const proxifyContext = ( current, inherited = {} ) => ! contextAssignedObjects.get( target )?.has( k ) && isPlainObject( peek( target, k ) ) ) { - return proxifyContext( currentProp, inheritedProp ); + return proxifyContext( currentProp, inherited[ k ] ); } - // For other cases, return the value from target. - return currentProp; + /* + * For other cases, return the value from target, also subscribing + * to changes in the parent context when the current prop is + * not defined. + */ + return k in target ? currentProp : inherited[ k ]; }, set: ( target, k, value ) => { const obj = diff --git a/test/e2e/specs/interactivity/directive-context.spec.ts b/test/e2e/specs/interactivity/directive-context.spec.ts index 3a566e7c2d9619..85341774c2af4e 100644 --- a/test/e2e/specs/interactivity/directive-context.spec.ts +++ b/test/e2e/specs/interactivity/directive-context.spec.ts @@ -313,4 +313,27 @@ test.describe( 'data-wp-context', () => { await select2.click(); await expect( selected ).toHaveText( 'Text 2' ); } ); + + test( 'should not subscribe to parent context props if those also exist in child', async ( { + page, + } ) => { + const counterParent = page.getByTestId( 'counter parent' ); + const counterChild = page.getByTestId( 'counter child' ); + const changes = page.getByTestId( 'counter changes' ); + + await expect( counterParent ).toHaveText( '0' ); + await expect( counterChild ).toHaveText( '0' ); + // The first render counts, so the changes counter starts at 1. + await expect( changes ).toHaveText( '1' ); + + await counterParent.click(); + await expect( counterParent ).toHaveText( '1' ); + await expect( counterChild ).toHaveText( '0' ); + await expect( changes ).toHaveText( '1' ); + + await counterChild.click(); + await expect( counterParent ).toHaveText( '1' ); + await expect( counterChild ).toHaveText( '1' ); + await expect( changes ).toHaveText( '2' ); + } ); } );