Skip to content

Commit

Permalink
Make i18n functions filter their return values (#27966)
Browse files Browse the repository at this point in the history
* Make i18n functions filter their return values

* @wordpress/hooks local dependency

* Support for domain-specific filters as per PHP

* Move responsibility for creating applyFilters to default-i18n

* Add tests

* Flag dependency in tsconfig.json

* package-lock changes

* Coding standards fix

* Introduce i18n-filters test

* Include i18n-filters test in CI task

* More consistent (not perfect) hook attachment

* Test fix

* Nit: type names start with a capital letter

* Add default i18n tests

* Use type annotations rather than explicit conversion

Co-authored-by: Lee Willis <[email protected]>
  • Loading branch information
leewillis77 and leewillis77 authored Jan 28, 2021
1 parent 1a54497 commit 736a005
Show file tree
Hide file tree
Showing 11 changed files with 568 additions and 10 deletions.
2 changes: 1 addition & 1 deletion bin/plugin/commands/performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ async function runPerformanceTests( branches, options ) {
log( '>> Starting the WordPress environment' );
await runShellScript( 'npm run wp-env start', environmentDirectory );

const testSuites = [ 'post-editor', 'site-editor' ];
const testSuites = [ 'post-editor', 'i18n-filters', 'site-editor' ];

/** @type {Record<string,Record<string, WPFormattedPerformanceResults>>} */
let results = {};
Expand Down
4 changes: 2 additions & 2 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ function gutenberg_override_script( $scripts, $handle, $src, $deps = array(), $v
* `WP_Dependencies::set_translations` will fall over on itself if setting
* translations on the `wp-i18n` handle, since it internally adds `wp-i18n`
* as a dependency of itself, exhausting memory. The same applies for the
* polyfill script, which is a dependency _of_ `wp-i18n`.
* polyfill and hooks scripts, which are dependencies _of_ `wp-i18n`.
*
* See: https://core.trac.wordpress.org/ticket/46089
*/
if ( 'wp-i18n' !== $handle && 'wp-polyfill' !== $handle ) {
if ( ! in_array( $handle, array( 'wp-i18n', 'wp-polyfill', 'wp-hooks' ), true ) ) {
$scripts->set_translations( $handle, 'default' );
}
if ( 'wp-i18n' === $handle ) {
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

291 changes: 291 additions & 0 deletions packages/e2e-tests/specs/performance/i18n-filters.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/**
* External dependencies
*/
import { basename, join } from 'path';
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs';

/**
* WordPress dependencies
*/
import {
createNewPost,
saveDraft,
insertBlock,
openGlobalBlockInserter,
closeGlobalBlockInserter,
} from '@wordpress/e2e-test-utils';

function readFile( filePath ) {
return existsSync( filePath )
? readFileSync( filePath, 'utf8' ).trim()
: '';
}

function deleteFile( filePath ) {
if ( existsSync( filePath ) ) {
unlinkSync( filePath );
}
}

function isKeyEvent( item ) {
return (
item.cat === 'devtools.timeline' &&
item.name === 'EventDispatch' &&
item.dur &&
item.args &&
item.args.data
);
}

function isKeyDownEvent( item ) {
return isKeyEvent( item ) && item.args.data.type === 'keydown';
}

function isKeyPressEvent( item ) {
return isKeyEvent( item ) && item.args.data.type === 'keypress';
}

function isKeyUpEvent( item ) {
return isKeyEvent( item ) && item.args.data.type === 'keyup';
}

function isFocusEvent( item ) {
return isKeyEvent( item ) && item.args.data.type === 'focus';
}

function isClickEvent( item ) {
return isKeyEvent( item ) && item.args.data.type === 'click';
}

function isMouseOverEvent( item ) {
return isKeyEvent( item ) && item.args.data.type === 'mouseover';
}

function isMouseOutEvent( item ) {
return isKeyEvent( item ) && item.args.data.type === 'mouseout';
}

function getEventDurationsForType( trace, filterFunction ) {
return trace.traceEvents
.filter( filterFunction )
.map( ( item ) => item.dur / 1000 );
}

function getTypingEventDurations( trace ) {
return [
getEventDurationsForType( trace, isKeyDownEvent ),
getEventDurationsForType( trace, isKeyPressEvent ),
getEventDurationsForType( trace, isKeyUpEvent ),
];
}

function getSelectionEventDurations( trace ) {
return [ getEventDurationsForType( trace, isFocusEvent ) ];
}

function getClickEventDurations( trace ) {
return [ getEventDurationsForType( trace, isClickEvent ) ];
}

function getHoverEventDurations( trace ) {
return [
getEventDurationsForType( trace, isMouseOverEvent ),
getEventDurationsForType( trace, isMouseOutEvent ),
];
}

page.on( 'load', function () {
page.evaluate( () => {
const filters = [
'i18n.gettext',
'i18n.gettext_default',
'i18n.ngettext',
'i18n.ngettext_default',
'i18n.gettext_with_context',
'i18n.gettext_with_context_default',
'i18n.ngettext_with_context',
'i18n.ngettext_with_context_default',
];
filters.forEach( ( filter ) => {
wp.hooks.addFilter(
filter,
'e2e-tests',
( ...args ) => {
return args[ 0 ];
},
90
);
} );
} );
} );

jest.setTimeout( 1000000 );

describe( 'Post Editor Performance (with i18n filters)', () => {
it( 'Loading, typing and selecting blocks', async () => {
const results = {
load: [],
type: [],
focus: [],
inserterOpen: [],
inserterHover: [],
};

const html = readFile(
join( __dirname, '../../assets/large-post.html' )
);

await createNewPost();

await page.evaluate( ( _html ) => {
const { parse } = window.wp.blocks;
const { dispatch } = window.wp.data;
const blocks = parse( _html );

blocks.forEach( ( block ) => {
if ( block.name === 'core/image' ) {
delete block.attributes.id;
delete block.attributes.url;
}
} );

dispatch( 'core/block-editor' ).resetBlocks( blocks );
}, html );
await saveDraft();

let i = 1;

// Measuring loading time
while ( i-- ) {
const startTime = new Date();
await page.reload();
await page.waitForSelector( '.wp-block' );
results.load.push( new Date() - startTime );
}

// Measure time to open inserter
await page.waitForSelector( '.edit-post-layout' );
const traceFile = __dirname + '/trace.json';
let traceResults;
for ( let j = 0; j < 10; j++ ) {
await page.tracing.start( {
path: traceFile,
screenshots: false,
categories: [ 'devtools.timeline' ],
} );
await openGlobalBlockInserter();
await page.tracing.stop();

traceResults = JSON.parse( readFile( traceFile ) );
const [ mouseClickEvents ] = getClickEventDurations( traceResults );
for ( let k = 0; k < mouseClickEvents.length; k++ ) {
results.inserterOpen.push( mouseClickEvents[ k ] );
}
await closeGlobalBlockInserter();
}

// Measure inserter hover performance
const paragraphBlockItem =
'.block-editor-inserter__menu .editor-block-list-item-paragraph';
const headingBlockItem =
'.block-editor-inserter__menu .editor-block-list-item-heading';
await openGlobalBlockInserter();
await page.waitForSelector( paragraphBlockItem );
await page.hover( paragraphBlockItem );
await page.hover( headingBlockItem );
for ( let j = 0; j < 20; j++ ) {
await page.tracing.start( {
path: traceFile,
screenshots: false,
categories: [ 'devtools.timeline' ],
} );
await page.hover( paragraphBlockItem );
await page.hover( headingBlockItem );
await page.tracing.stop();

traceResults = JSON.parse( readFile( traceFile ) );
const [ mouseOverEvents, mouseOutEvents ] = getHoverEventDurations(
traceResults
);
for ( let k = 0; k < mouseOverEvents.length; k++ ) {
results.inserterHover.push(
mouseOverEvents[ k ] + mouseOutEvents[ k ]
);
}
}
await closeGlobalBlockInserter();

// Measuring typing performance
await insertBlock( 'Paragraph' );
i = 200;
await page.tracing.start( {
path: traceFile,
screenshots: false,
categories: [ 'devtools.timeline' ],
} );
while ( i-- ) {
await page.keyboard.type( 'x' );
}

await page.tracing.stop();
traceResults = JSON.parse( readFile( traceFile ) );
const [
keyDownEvents,
keyPressEvents,
keyUpEvents,
] = getTypingEventDurations( traceResults );

if (
keyDownEvents.length === keyPressEvents.length &&
keyPressEvents.length === keyUpEvents.length
) {
for ( let j = 0; j < keyDownEvents.length; j++ ) {
results.type.push(
keyDownEvents[ j ] + keyPressEvents[ j ] + keyUpEvents[ j ]
);
}
}

// Save the draft so we don't get browser dialogs about leaving unsaved page.
await saveDraft();

// Measuring block selection performance
await createNewPost();
await page.evaluate( () => {
const { createBlock } = window.wp.blocks;
const { dispatch } = window.wp.data;
const blocks = window.lodash
.times( 1000 )
.map( () => createBlock( 'core/paragraph' ) );
dispatch( 'core/block-editor' ).resetBlocks( blocks );
} );

const paragraphs = await page.$$( '.wp-block' );

await page.tracing.start( {
path: traceFile,
screenshots: false,
categories: [ 'devtools.timeline' ],
} );
for ( let j = 0; j < 10; j++ ) {
await paragraphs[ j ].click();
}

await page.tracing.stop();

traceResults = JSON.parse( readFile( traceFile ) );
const [ focusEvents ] = getSelectionEventDurations( traceResults );
results.focus = focusEvents;

const resultsFilename = basename( __filename, '.js' ) + '.results.json';

writeFileSync(
join( __dirname, resultsFilename ),
JSON.stringify( results, null, 2 )
);

deleteFile( traceFile );

expect( true ).toBe( true );
} );
} );
1 change: 1 addition & 0 deletions packages/i18n/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ _Parameters_

- _initialData_ `[LocaleData]`: Locale data configuration.
- _initialDomain_ `[string]`: Domain for which configuration applies.
- _hooks_ `[ApplyFiltersInterface]`: Hooks implementation.

_Returns_

Expand Down
1 change: 1 addition & 0 deletions packages/i18n/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@wordpress/hooks": "file:../hooks",
"gettext-parser": "^1.3.1",
"lodash": "^4.17.19",
"memize": "^1.1.0",
Expand Down
Loading

0 comments on commit 736a005

Please sign in to comment.