Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: Move block fixtures to e2e-tests package #13658

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
"dev": "npm run build:packages && concurrently \"wp-scripts start\" \"npm run dev:packages\"",
"dev:packages": "node ./bin/packages/watch.js",
"docs:build": "node docs/tool",
"fixtures:clean": "rimraf \"test/integration/full-content/fixtures/*.+(json|serialized.html)\"",
"fixtures:clean": "rimraf \"packages/e2e-tests/fixtures/blocks/*.+(json|serialized.html)\"",
"fixtures:server-registered": "docker-compose run -w /var/www/html/wp-content/plugins/gutenberg --rm wordpress ./bin/get-server-blocks.php > test/integration/full-content/server-registered.json",
"fixtures:generate": "npm run fixtures:server-registered && cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit",
"fixtures:regenerate": "npm run fixtures:clean && npm run fixtures:generate",
Expand Down
12 changes: 12 additions & 0 deletions packages/e2e-tests/fixtures/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export {
blockNameToFixtureBasename,
getAvailableBlockFixturesBasenames,
getBlockFixtureHTML,
getBlockFixtureJSON,
getBlockFixtureParsedJSON,
getBlockFixtureSerializedHTML,
writeBlockFixtureHTML,
writeBlockFixtureJSON,
writeBlockFixtureParsedJSON,
writeBlockFixtureSerializedHTML,
} from './utils';
92 changes: 92 additions & 0 deletions packages/e2e-tests/fixtures/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* External dependencies
*/
import fs from 'fs';
import path from 'path';
import { uniq } from 'lodash';

const FIXTURES_DIR = path.join( __dirname, 'blocks' );

function readFixtureFile( fixturesDir, filename ) {
try {
return fs.readFileSync(
path.join( fixturesDir, filename ),
'utf8'
);
} catch ( err ) {
return null;
}
}

function writeFixtureFile( fixturesDir, filename, content ) {
fs.writeFileSync(
path.join( fixturesDir, filename ),
content
);
}

export function blockNameToFixtureBasename( blockName ) {
return blockName.replace( /\//g, '__' );
}

export function getAvailableBlockFixturesBasenames() {
// We expect 4 different types of files for each fixture:
// - fixture.html : original content
// - fixture.parsed.json : parser output
// - fixture.json : blocks structure
// - fixture.serialized.html : re-serialized content
// Get the "base" name for each fixture first.
return uniq(
fs.readdirSync( FIXTURES_DIR )
.filter( ( f ) => /(\.html|\.json)$/.test( f ) )
.map( ( f ) => f.replace( /\..+$/, '' ) )
);
}

export function getBlockFixtureHTML( basename ) {
const filename = `${ basename }.html`;
return {
filename,
file: readFixtureFile( FIXTURES_DIR, filename ),
};
}

export function getBlockFixtureJSON( basename ) {
const filename = `${ basename }.json`;
return {
filename,
file: readFixtureFile( FIXTURES_DIR, filename ),
};
}

export function getBlockFixtureParsedJSON( basename ) {
const filename = `${ basename }.parsed.json`;
return {
filename,
file: readFixtureFile( FIXTURES_DIR, filename ),
};
}

export function getBlockFixtureSerializedHTML( basename ) {
const filename = `${ basename }.serialized.html`;
return {
filename,
file: readFixtureFile( FIXTURES_DIR, filename ),
};
}

export function writeBlockFixtureHTML( basename, fixture ) {
writeFixtureFile( FIXTURES_DIR, `${ basename }.html`, fixture );
}

export function writeBlockFixtureJSON( basename, fixture ) {
writeFixtureFile( FIXTURES_DIR, `${ basename }.json`, fixture );
}

export function writeBlockFixtureParsedJSON( basename, fixture ) {
writeFixtureFile( FIXTURES_DIR, `${ basename }.parsed.json`, fixture );
}

export function writeBlockFixtureSerializedHTML( basename, fixture ) {
writeFixtureFile( FIXTURES_DIR, `${ basename }.serialized.html`, fixture );
}
192 changes: 95 additions & 97 deletions test/integration/full-content/full-content.spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/**
* External dependencies
*/
import fs from 'fs';
import path from 'path';
import { uniq, startsWith, get } from 'lodash';
import { startsWith, get } from 'lodash';
import { format } from 'util';

/**
Expand All @@ -17,38 +15,19 @@ import {
} from '@wordpress/blocks';
import { parse as grammarParse } from '@wordpress/block-serialization-default-parser';
import { registerCoreBlocks } from '@wordpress/block-library';

const fixturesDir = path.join( __dirname, 'fixtures' );

// We expect 4 different types of files for each fixture:
// - fixture.html : original content
// - fixture.parsed.json : parser output
// - fixture.json : blocks structure
// - fixture.serialized.html : re-serialized content
// Get the "base" name for each fixture first.
const fileBasenames = uniq(
fs.readdirSync( fixturesDir )
.filter( ( f ) => /(\.html|\.json)$/.test( f ) )
.map( ( f ) => f.replace( /\..+$/, '' ) )
);

function readFixtureFile( filename ) {
try {
return fs.readFileSync(
path.join( fixturesDir, filename ),
'utf8'
);
} catch ( err ) {
return null;
}
}

function writeFixtureFile( filename, content ) {
fs.writeFileSync(
path.join( fixturesDir, filename ),
content
);
}
import { //eslint-disable-line no-restricted-syntax
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it was a legitimate use-case to disable the rule here. If it's part of the package's public interface, it should be exposed from the root. We shouldn't ever need to path-traverse a module to import.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed in general, however as explained I did it on purpose to avoid exposing those methods at the moment. We will have to maintain them forever so let's test them with our own e2e tests first.

I also left this note:

I think that we should simplify our setup by replacing all the tests in test/integration/full-content/full-content.spec.js with a corresponding set of e2e tests which represent a real-life usage rather than something that produces so much hassle with running fixture cleanup and generation.

If we get rid of those integration tests we will also won't need to have those methods exposed as part of the package's public interface. There is another issue which you raised in #13583 and I think noticed in one of the similar PRs which adds a different new block. It seems like those tests are sometimes broken and don't generate the correct output which makes them only confusing. It's very hard to validate them if you don't know exactly how they should behave.

blockNameToFixtureBasename,
getAvailableBlockFixturesBasenames,
getBlockFixtureHTML,
getBlockFixtureJSON,
getBlockFixtureParsedJSON,
getBlockFixtureSerializedHTML,
writeBlockFixtureParsedJSON,
writeBlockFixtureJSON,
writeBlockFixtureSerializedHTML,
} from '@wordpress/e2e-tests/fixtures';

const blockBasenames = getAvailableBlockFixturesBasenames();

function normalizeParsedBlocks( blocks ) {
return blocks.map( ( block, index ) => {
Expand All @@ -75,31 +54,37 @@ describe( 'full post content fixture', () => {
registerCoreBlocks();
} );

fileBasenames.forEach( ( f ) => {
it( f, () => {
const content = readFixtureFile( f + '.html' );
if ( content === null ) {
blockBasenames.forEach( ( basename ) => {
it( basename, () => {
const {
filename: htmlFixtureFileName,
file: htmlFixtureContent,
} = getBlockFixtureHTML( basename );
if ( htmlFixtureContent === null ) {
throw new Error(
'Missing fixture file: ' + f + '.html'
`Missing fixture file: ${ htmlFixtureFileName }`
);
}

const parserOutputActual = grammarParse( content );
let parserOutputExpectedString = readFixtureFile( f + '.parsed.json' );

if ( ! parserOutputExpectedString ) {
if ( process.env.GENERATE_MISSING_FIXTURES ) {
parserOutputExpectedString = JSON.stringify(
parserOutputActual,
null,
4
) + '\n';
writeFixtureFile( f + '.parsed.json', parserOutputExpectedString );
} else {
throw new Error(
'Missing fixture file: ' + f + '.parsed.json'
);
}
const {
filename: parsedJSONFixtureFileName,
file: parsedJSONFixtureContent,
} = getBlockFixtureParsedJSON( basename );
const parserOutputActual = grammarParse( htmlFixtureContent );
let parserOutputExpectedString;
if ( parsedJSONFixtureContent ) {
parserOutputExpectedString = parsedJSONFixtureContent;
} else if ( process.env.GENERATE_MISSING_FIXTURES ) {
parserOutputExpectedString = JSON.stringify(
parserOutputActual,
null,
4
) + '\n';
writeBlockFixtureParsedJSON( basename, parserOutputExpectedString );
} else {
throw new Error(
`Missing fixture file: ${ parsedJSONFixtureFileName }`
);
}

const parserOutputExpected = JSON.parse( parserOutputExpectedString );
Expand All @@ -109,18 +94,18 @@ describe( 'full post content fixture', () => {
).toEqual( parserOutputExpected );
} catch ( err ) {
throw new Error( format(
"File '%s.parsed.json' does not match expected value:\n\n%s",
f,
"File '%s' does not match expected value:\n\n%s",
parsedJSONFixtureFileName,
err.message
) );
}

const blocksActual = parse( content );
const blocksActual = parse( htmlFixtureContent );

// Block validation may log errors during deprecation migration,
// unless explicitly handled from a valid block via isEligible.
// Match on filename for deprecated blocks fixtures to allow.
const isDeprecated = /__deprecated([-_]|$)/.test( f );
// Match on basename for deprecated blocks fixtures to allow.
const isDeprecated = /__deprecated([-_]|$)/.test( basename );
if ( isDeprecated ) {
/* eslint-disable no-console */
console.warn.mockReset();
Expand All @@ -129,21 +114,26 @@ describe( 'full post content fixture', () => {
}

const blocksActualNormalized = normalizeParsedBlocks( blocksActual );
let blocksExpectedString = readFixtureFile( f + '.json' );

if ( ! blocksExpectedString ) {
if ( process.env.GENERATE_MISSING_FIXTURES ) {
blocksExpectedString = JSON.stringify(
blocksActualNormalized,
null,
4
) + '\n';
writeFixtureFile( f + '.json', blocksExpectedString );
} else {
throw new Error(
'Missing fixture file: ' + f + '.json'
);
}
const {
filename: jsonFixtureFileName,
file: jsonFixtureContent,
} = getBlockFixtureJSON( basename );

let blocksExpectedString;

if ( jsonFixtureContent ) {
blocksExpectedString = jsonFixtureContent;
} else if ( process.env.GENERATE_MISSING_FIXTURES ) {
blocksExpectedString = JSON.stringify(
blocksActualNormalized,
null,
4
) + '\n';
writeBlockFixtureJSON( basename, blocksExpectedString );
} else {
throw new Error(
`Missing fixture file: ${ jsonFixtureFileName }`
);
}

const blocksExpected = JSON.parse( blocksExpectedString );
Expand All @@ -153,34 +143,38 @@ describe( 'full post content fixture', () => {
).toEqual( blocksExpected );
} catch ( err ) {
throw new Error( format(
"File '%s.json' does not match expected value:\n\n%s",
f,
"File '%s' does not match expected value:\n\n%s",
jsonFixtureFileName,
err.message
) );
}

// `serialize` doesn't have a trailing newline, but the fixture
// files should.
const serializedActual = serialize( blocksActual ) + '\n';
let serializedExpected = readFixtureFile( f + '.serialized.html' );

if ( ! serializedExpected ) {
if ( process.env.GENERATE_MISSING_FIXTURES ) {
serializedExpected = serializedActual;
writeFixtureFile( f + '.serialized.html', serializedExpected );
} else {
throw new Error(
'Missing fixture file: ' + f + '.serialized.html'
);
}
const {
filename: serializedHTMLFileName,
file: serializedHTMLFixtureContent,
} = getBlockFixtureSerializedHTML( basename );

let serializedExpected;
if ( serializedHTMLFixtureContent ) {
serializedExpected = serializedHTMLFixtureContent;
} else if ( process.env.GENERATE_MISSING_FIXTURES ) {
serializedExpected = serializedActual;
writeBlockFixtureSerializedHTML( basename, serializedExpected );
} else {
throw new Error(
`Missing fixture file: ${ serializedHTMLFileName }`
);
}

try {
expect( serializedActual ).toEqual( serializedExpected );
} catch ( err ) {
throw new Error( format(
"File '%s.serialized.html' does not match expected value:\n\n%s",
f,
"File '%s' does not match expected value:\n\n%s",
serializedHTMLFileName,
err.message
) );
}
Expand All @@ -197,25 +191,29 @@ describe( 'full post content fixture', () => {
// The `core/template` is not worth testing here because it's never saved, it's covered better in e2e tests.
.filter( ( name ) => name.indexOf( 'core-embed' ) !== 0 && name !== 'core/template' )
.forEach( ( name ) => {
const nameToFilename = name.replace( /\//g, '__' );
const foundFixtures = fileBasenames
const nameToFilename = blockNameToFixtureBasename( name );
const foundFixtures = blockBasenames
.filter( ( basename ) => (
basename === nameToFilename ||
startsWith( basename, nameToFilename + '__' )
) )
.map( ( basename ) => {
// The file that contains the input HTML for this test.
const inputFilename = basename + '.html';
const {
filename: htmlFixtureFileName,
} = getBlockFixtureHTML( basename );
const {
file: jsonFixtureContent,
} = getBlockFixtureJSON( basename );
// The parser output for this test. For missing files,
// JSON.parse( null ) === null.
const parserOutput = JSON.parse(
readFixtureFile( basename + '.json' )
jsonFixtureContent,
);
// The name of the first block that this fixture file
// contains (if any).
const firstBlock = get( parserOutput, [ '0', 'name' ], null );
return {
filename: inputFilename,
filename: htmlFixtureFileName,
parserOutput,
firstBlock,
};
Expand Down