diff --git a/packages/blocks-engine/src/wp/__tests__/video-grid-layout.test.ts b/packages/blocks-engine/src/wp/__tests__/video-grid-layout.test.ts new file mode 100644 index 000000000..de22dc2f2 --- /dev/null +++ b/packages/blocks-engine/src/wp/__tests__/video-grid-layout.test.ts @@ -0,0 +1,43 @@ +import { expect, it } from 'vitest'; +import { chromium } from 'playwright'; +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; + +const require = createRequire(import.meta.url) as { (id: string): any; resolve(id: string): string }; +const root = fileURLToPath(new URL('../../../../../php-transformer/', import.meta.url)); + +it('retains source text/video grid geometry at 390, 768 and 1440px', async () => { + const source = readFileSync(`${root}/tests/fixtures/text-video-grid.html`, 'utf8'); + const output = JSON.parse(execFileSync('php', ['-r', ` + require 'vendor/autoload.php'; + $r = (new Automattic\\BlocksEngine\\PhpTransformer\\HtmlToBlocks\\HtmlTransformer())->transform(file_get_contents('tests/fixtures/text-video-grid.html'))->toArray(); + echo json_encode(['html' => $r['serialized_blocks'], 'css' => implode("\\n", array_column($r['assets'], 'content'))]); + `], { cwd: root, encoding: 'utf8' })) as { html: string; css: string }; + const coreCss = readFileSync(require.resolve('@wordpress/block-library/build-style/style.css'), 'utf8'); + const browser = await chromium.launch({ headless: true }); + try { + const page = await browser.newPage(); + for (const width of [390, 768, 1440]) { + await page.setViewportSize({ width, height: 1000 }); + const measure = async (html: string) => { + await page.setContent(html); + return page.evaluate(() => ['h2', 'video'].map((selector) => { + const box = document.querySelector(selector)!.getBoundingClientRect(); + return { x: box.x, y: box.y, width: box.width, height: box.height }; + })); + }; + const before = await measure(source); + const after = await measure(`${output.html}`); + console.log(width, { source: before, imported: after }); + for (const [index, box] of after.entries()) { + for (const key of ['x', 'y', 'width', 'height'] as const) { + expect(Math.abs(box[key] - before[index][key]), `${width}px ${index} ${key}`).toBeLessThan(1); + } + } + } + } finally { + await browser.close(); + } +}, 30000); diff --git a/packages/blocks-engine/src/wp/__tests__/video-grid-save-validity.test.ts b/packages/blocks-engine/src/wp/__tests__/video-grid-save-validity.test.ts new file mode 100644 index 000000000..c09a67e86 --- /dev/null +++ b/packages/blocks-engine/src/wp/__tests__/video-grid-save-validity.test.ts @@ -0,0 +1,49 @@ +import { beforeAll, describe, expect, it, vi } from 'vitest'; +import { execFileSync } from 'node:child_process'; +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; +import { setupDomGlobals } from '../dom-globals.js'; + +const require = createRequire(import.meta.url) as { (id: string): any; resolve(id: string): string }; +type Block = { name: string; innerBlocks: Block[]; attributes: Record }; +type Wp = { + parse(markup: string): Block[]; + validateBlock(block: Block): [boolean, unknown[]]; +}; +const transformer = fileURLToPath(new URL('../../../../../php-transformer/', import.meta.url)); + +describe('text/video author grid Gutenberg save validity', () => { + let wp: Wp; + beforeAll(() => { + setupDomGlobals(); + wp = require('@wordpress/blocks') as Wp; + require('@wordpress/block-library').registerCoreBlocks(); + }); + + it('detects the former media-text video attribute mismatch', () => { + const error = vi.spyOn(console, 'error').mockImplementation(() => undefined); + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + try { + const blocks = wp.parse('
'); + expect(wp.validateBlock(blocks[0])[0]).toBe(false); + } finally { + error.mockRestore(); + warn.mockRestore(); + } + }); + + it.each([true, false])('validates the public transformer output (controls=%s)', (controls) => { + const markup = execFileSync('php', ['-r', ` + require 'vendor/autoload.php'; + $html = file_get_contents('tests/fixtures/text-video-grid.html'); + if (${controls ? 'false' : 'true'}) $html = str_replace(' controls', '', $html); + echo (new Automattic\\BlocksEngine\\PhpTransformer\\HtmlToBlocks\\HtmlTransformer())->transform($html)->toArray()['serialized_blocks']; + `], { cwd: transformer, encoding: 'utf8' }); + const flatten = (blocks: Block[]): Block[] => blocks.flatMap((block) => [block, ...flatten(block.innerBlocks)]); + const blocks = flatten(wp.parse(markup)); + expect(blocks.map((block) => wp.validateBlock(block)[0])).toEqual(blocks.map(() => true)); + expect(blocks.some((block) => block.name === 'core/media-text')).toBe(false); + const video = blocks.find((block) => block.name === 'core/video'); + expect(video?.attributes).toMatchObject({ src: 'clip.mp4', poster: 'poster.jpg', preload: 'metadata', controls, loop: true, muted: true, playsInline: true }); + }); +}); diff --git a/php-transformer/composer.json b/php-transformer/composer.json index af3efab57..6ddd21300 100644 --- a/php-transformer/composer.json +++ b/php-transformer/composer.json @@ -477,6 +477,7 @@ "php tests/unit/embed-author-height-overrides-attribute.php", "php tests/unit/media-text-block-factory.php", "php tests/unit/video-element-conversion.php", + "php tests/unit/text-video-grid.php", "php tests/contract/native-video-poster.php", "php tests/contract/native-progress-meter.php", "php tests/contract/small-fixed-size-image-beside-text.php", diff --git a/php-transformer/src/HtmlToBlocks/BlockFactory.php b/php-transformer/src/HtmlToBlocks/BlockFactory.php index b8df1fc9e..b70455c23 100644 --- a/php-transformer/src/HtmlToBlocks/BlockFactory.php +++ b/php-transformer/src/HtmlToBlocks/BlockFactory.php @@ -227,23 +227,6 @@ private function commentAttrs(string $name, array $attrs): array // core's save() never reads it back, so it never belongs in the // serialized comment. unset($attrs['mediaFigureClassName']); - // Internal-only: consumed by mediaTextVideoAttrs() to build the - // video pane's