Skip to content

Commit

Permalink
Add event listener removal tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cbravobernal committed Jan 18, 2024
1 parent 790587e commit 99e2ed8
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
gutenberg_enqueue_module( 'directive-on-document-view' );
?>

<div data-wp-interactive='{ "namespace": "directive-on-document" }'>
<div data-wp-on-document--keydown="callbacks.keydownHandler">
<p data-wp-text="state.counter" data-testid="counter">0</p>
<div data-wp-interactive='{ "namespace": "directive-on-document" }' data-wp-context='{"isVisible":true}'>
<button data-wp-on--click="actions.visibilityHandler" data-testid="visibility">Switch visibility</button>
<div data-wp-show-mock="context.isVisible">
<div data-wp-on-document--keydown="callbacks.keydownHandler">
<p data-wp-text="state.counter" data-testid="counter">0</p>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
/**
* WordPress dependencies
*/
import { store } from '@wordpress/interactivity';
import { store, directive, getContext } from '@wordpress/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': showMock }, element, evaluate } ) => {
const entry = showMock.find( ( { suffix } ) => suffix === 'default' );
if ( ! evaluate( entry ) ) {
return null;
}
return element;
}
);

const { state } = store( 'directive-on-document', {
state: {
Expand All @@ -12,4 +25,10 @@ const { state } = store( 'directive-on-document', {
state.counter += 1;
},
},
actions: {
visibilityHandler: () => {
const context = getContext();
context.isVisible = ! context.isVisible;
},
}
} );
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
gutenberg_enqueue_module( 'directive-on-window-view' );
?>

<div data-wp-interactive='{ "namespace": "directive-on-window" }'>
<div data-wp-on-window--resize="callbacks.resizeHandler">
<p data-wp-text="state.counter" data-testid="counter">0</p>
<div data-wp-interactive='{ "namespace": "directive-on-window" }' data-wp-context='{"isVisible":true}'>
<button data-wp-on--click="actions.visibilityHandler" data-testid="visibility">Switch visibility</button>
<div data-wp-show-mock="context.isVisible">
<div data-wp-on-window--resize="callbacks.resizeHandler">
<p data-wp-text="state.counter" data-testid="counter">0</p>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
/**
* WordPress dependencies
*/
import { store } from '@wordpress/interactivity';
import { store, directive, getContext } from '@wordpress/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': showMock }, element, evaluate } ) => {
const entry = showMock.find( ( { suffix } ) => suffix === 'default' );
if ( ! evaluate( entry ) ) {
return null;
}
return element;
}
);

const { state } = store( 'directive-on-window', {
state: {
Expand All @@ -12,4 +25,10 @@ const { state } = store( 'directive-on-window', {
state.counter += 1;
},
},
actions: {
visibilityHandler: () => {
const context = getContext();
context.isVisible = ! context.isVisible;
},
}
} );
111 changes: 59 additions & 52 deletions packages/interactivity/src/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { deepSignal, peek } from 'deepsignal';
* Internal dependencies
*/
import { createPortal } from './portals';
import { useWatch, useInit, useEffect } from './utils';
import { useWatch, useInit } from './utils';
import { directive } from './hooks';

const isObject = ( item ) =>
Expand All @@ -28,6 +28,64 @@ const mergeDeepSignals = ( target, source, overwrite ) => {
}
};

const newRule =
/(?:([\u0080-\uFFFF\w-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|(}\s*)/g;
const ruleClean = /\/\*[^]*?\*\/| +/g;
const ruleNewline = /\n+/g;
const empty = ' ';

/**
* Convert a css style string into a object.
*
* Made by Cristian Bote (@cristianbote) for Goober.
* https://unpkg.com/browse/goober@2.1.13/src/core/astish.js
*
* @param {string} val CSS string.
* @return {Object} CSS object.
*/
const cssStringToObject = ( val ) => {
const tree = [ {} ];
let block, left;

while ( ( block = newRule.exec( val.replace( ruleClean, '' ) ) ) ) {
if ( block[ 4 ] ) {
tree.shift();
} else if ( block[ 3 ] ) {
left = block[ 3 ].replace( ruleNewline, empty ).trim();
tree.unshift( ( tree[ 0 ][ left ] = tree[ 0 ][ left ] || {} ) );
} else {
tree[ 0 ][ block[ 1 ] ] = block[ 2 ]
.replace( ruleNewline, empty )
.trim();
}
}

return tree[ 0 ];
};

/**
* Creates a directive that adds an event listener to the global window or
* document object.
*
* @param {string} type 'window' or 'document'
* @return {void}
*/
const getGlobalEventDirective =
( type ) =>
( { directives, evaluate } ) => {
directives[ `on-${ type }` ]
.filter( ( { suffix } ) => suffix !== 'default' )
.forEach( ( entry ) => {
useInit( () => {
const cb = ( event ) => evaluate( entry, event );
const globalVar = type === 'window' ? window : document;
globalVar.addEventListener( entry.suffix, cb );
return () =>
globalVar.removeEventListener( entry.suffix, cb );
}, [] );
} );
};

export default () => {
// data-wp-context
directive(
Expand Down Expand Up @@ -87,22 +145,6 @@ export default () => {
} );
} );

const getGlobalEventDirective =
( type ) =>
( { directives, evaluate } ) => {
directives[ `on-${ type }` ]
.filter( ( { suffix } ) => suffix !== 'default' )
.forEach( ( entry ) => {
useEffect( () => {
const cb = ( event ) => evaluate( entry, event );
const globalVar = type === 'window' ? window : document;
globalVar.addEventListener( entry.suffix, cb );
return () =>
globalVar.removeEventListener( entry.suffix, cb );
}, [] );
} );
};

// data-wp-on-window--[event]
directive( 'on-window', getGlobalEventDirective( 'window' ) );
// data-wp-on-document--[event]
Expand Down Expand Up @@ -145,41 +187,6 @@ export default () => {
}
);

const newRule =
/(?:([\u0080-\uFFFF\w-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|(}\s*)/g;
const ruleClean = /\/\*[^]*?\*\/| +/g;
const ruleNewline = /\n+/g;
const empty = ' ';

/**
* Convert a css style string into a object.
*
* Made by Cristian Bote (@cristianbote) for Goober.
* https://unpkg.com/browse/goober@2.1.13/src/core/astish.js
*
* @param {string} val CSS string.
* @return {Object} CSS object.
*/
const cssStringToObject = ( val ) => {
const tree = [ {} ];
let block, left;

while ( ( block = newRule.exec( val.replace( ruleClean, '' ) ) ) ) {
if ( block[ 4 ] ) {
tree.shift();
} else if ( block[ 3 ] ) {
left = block[ 3 ].replace( ruleNewline, empty ).trim();
tree.unshift( ( tree[ 0 ][ left ] = tree[ 0 ][ left ] || {} ) );
} else {
tree[ 0 ][ block[ 1 ] ] = block[ 2 ]
.replace( ruleNewline, empty )
.trim();
}
}

return tree[ 0 ];
};

// data-wp-style--[style-key]
directive( 'style', ( { directives: { style }, element, evaluate } ) => {
style
Expand Down
16 changes: 16 additions & 0 deletions test/e2e/specs/interactivity/directive-on-document.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,20 @@ test.describe( 'data-wp-on-document', () => {
await page.keyboard.press( 'ArrowDown' );
await expect( counter ).toHaveText( '1' );
} );
test( 'the event listener is removed when the element is removed', async ( {
page,
} ) => {
const counter = page.getByTestId( 'counter' );
const visibilityButton = page.getByTestId( 'visibility' );
await expect( counter ).toHaveText( '0' );
await page.keyboard.press( 'ArrowDown' );
await expect( counter ).toHaveText( '1' );
// Remove the element.
await visibilityButton.click();
// This keyboard press should not increase the counter.
await page.keyboard.press( 'ArrowDown' );
// Add the element back.
await visibilityButton.click();
await expect( counter ).toHaveText( '1' );
} );
} );
15 changes: 15 additions & 0 deletions test/e2e/specs/interactivity/directive-on-window.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,19 @@ test.describe( 'data-wp-on-window', () => {
const counter = page.getByTestId( 'counter' );
await expect( counter ).toHaveText( '1' );
} );
test( 'the event listener is removed when the element is removed', async ( {
page,
} ) => {
const counter = page.getByTestId( 'counter' );
const visibilityButton = page.getByTestId( 'visibility' );
await page.setViewportSize( { width: 600, height: 600 } );
await expect( counter ).toHaveText( '1' );
// Remove the element.
await visibilityButton.click();
// This resize should not increase the counter.
await page.setViewportSize( { width: 300, height: 600 } );
// Add the element back.
await visibilityButton.click();
await expect( counter ).toHaveText( '1' );
} );
} );

0 comments on commit 99e2ed8

Please sign in to comment.