diff --git a/mobile/scripts/build-terminal-document-fixture.mjs b/mobile/scripts/build-terminal-document-fixture.mjs new file mode 100644 index 00000000000..f4c65a09c49 --- /dev/null +++ b/mobile/scripts/build-terminal-document-fixture.mjs @@ -0,0 +1,91 @@ +import { writeFile } from 'node:fs/promises' +import path from 'node:path' +import * as esbuild from 'esbuild' + +/** + * Writes the committed copy of the terminal WebView document that + * `terminal-document-identity.test.ts` diffs against. + * + * The document is a build artifact: fourteen source slices joined in a pinned order, with the + * generated xterm engine spliced into two of them. `terminal-webview-payload-hash.test.ts` already + * says *whether* it moved; what it cannot say is *where*, and a refactor whose whole claim is that + * the document did not move needs the diff, not the digest. + * + * The two generated engine strings are stored as placeholders rather than inline. They are already + * pinned by the hash test, they are regenerated by postinstall from whatever xterm version the + * lockfile holds, and inlining them would put 612 KiB of vendored bytes in the fixture and turn + * every xterm bump into an unreadable diff of the file that is supposed to isolate hand-written + * changes. + * + * Regenerating this fixture is a review event: it is only correct when the emitted document was + * meant to change, and the diff is the evidence for that. Run `node scripts/build-terminal-document-fixture.mjs` + * from `mobile/`. + */ +const mobileRoot = path.resolve(import.meta.dirname, '..') +const entry = path.join(mobileRoot, 'src', 'terminal', 'terminal-webview-html.ts') +const enginePath = path.join(mobileRoot, 'src', 'terminal', 'terminal-webview-engine.generated.ts') + +export const TERMINAL_DOCUMENT_FIXTURE_PATH = path.join( + mobileRoot, + 'src', + 'terminal', + 'terminal-document-golden.txt' +) + +/** Chosen so the document cannot contain one by accident; asserted below and in the test. */ +export const ENGINE_JS_PLACEHOLDER = '__ORCA_TERMINAL_ENGINE_JS__' +export const ENGINE_CSS_PLACEHOLDER = '__ORCA_TERMINAL_ENGINE_CSS__' + +async function loadModule(entryPoint) { + const result = await esbuild.build({ + entryPoints: [entryPoint], + bundle: true, + format: 'esm', + platform: 'node', + write: false, + logLevel: 'silent' + }) + const code = result.outputFiles[0].text + return import(`data:text/javascript;base64,${Buffer.from(code, 'utf8').toString('base64')}`) +} + +/** + * The document with both generated sections replaced by their placeholders. + * + * Exported so the test builds the same text the script writes, rather than restating the + * substitution and agreeing with a fixture that was written wrong. + */ +export function terminalDocumentFixture(document, engineJs, engineCss) { + for (const placeholder of [ENGINE_JS_PLACEHOLDER, ENGINE_CSS_PLACEHOLDER]) { + if (document.includes(placeholder)) { + throw new Error(`the document already contains ${placeholder}`) + } + } + for (const [name, value] of [ + ['XTERM_ENGINE_JS', engineJs], + ['XTERM_ENGINE_CSS', engineCss] + ]) { + if (document.split(value).length !== 2) { + throw new Error(`${name} does not appear exactly once in the document`) + } + } + return document + .replace(engineJs, ENGINE_JS_PLACEHOLDER) + .replace(engineCss, ENGINE_CSS_PLACEHOLDER) +} + +async function main() { + const [{ XTERM_HTML }, { XTERM_ENGINE_JS, XTERM_ENGINE_CSS }] = await Promise.all([ + loadModule(entry), + loadModule(enginePath) + ]) + const fixture = terminalDocumentFixture(XTERM_HTML, XTERM_ENGINE_JS, XTERM_ENGINE_CSS) + await writeFile(TERMINAL_DOCUMENT_FIXTURE_PATH, fixture) + console.log( + `[build-terminal-document-fixture] ${Buffer.byteLength(fixture, 'utf8')} bytes (document ${Buffer.byteLength(XTERM_HTML, 'utf8')})` + ) +} + +if (import.meta.filename === process.argv[1]) { + await main() +} diff --git a/mobile/src/terminal/terminal-document-golden.txt b/mobile/src/terminal/terminal-document-golden.txt new file mode 100644 index 00000000000..12a6d3e1fe0 --- /dev/null +++ b/mobile/src/terminal/terminal-document-golden.txt @@ -0,0 +1,2920 @@ + + + + + + + + + + +
+
+
+
+
+
+
+
+ + +
+
+ + + + \ No newline at end of file diff --git a/mobile/src/terminal/terminal-document-identity.test.ts b/mobile/src/terminal/terminal-document-identity.test.ts new file mode 100644 index 00000000000..0f4bf152bd7 --- /dev/null +++ b/mobile/src/terminal/terminal-document-identity.test.ts @@ -0,0 +1,54 @@ +import { readFileSync } from 'node:fs' +import { describe, expect, it } from 'vitest' +import { + ENGINE_CSS_PLACEHOLDER, + ENGINE_JS_PLACEHOLDER, + TERMINAL_DOCUMENT_FIXTURE_PATH, + terminalDocumentFixture +} from '../../scripts/build-terminal-document-fixture.mjs' +import { XTERM_ENGINE_CSS, XTERM_ENGINE_JS } from './terminal-webview-engine.generated' +import { XTERM_HTML } from './terminal-webview-html' + +/** + * The emitted WebView document, byte for byte, against a committed copy of itself. + * + * `terminal-webview-payload-hash.test.ts` pins the same bytes as a digest, which answers whether + * the document moved. This answers where: the whole document is one assertion, so a slice that + * gained a character, lost an indent or changed order arrives as a diff of the line rather than as + * two hexadecimal strings. Both are kept — the digest also covers the generated engine, which this + * fixture deliberately does not. + * + * C7.1 moves the document's hand-written script into modules the web page can import, and a + * generator rebuilds the document from them. This is the instrument that says the native screen + * kept the document it had. Regenerate the fixture with + * `node scripts/build-terminal-document-fixture.mjs` only when the emitted document was meant to + * change; the diff in that commit is the evidence, and reviewing it is the point. + */ +const fixture = readFileSync(TERMINAL_DOCUMENT_FIXTURE_PATH, 'utf8') + +describe('the terminal WebView document', () => { + it('is byte for byte the document the fixture holds', () => { + // Rebuilt through the script's own substitution rather than a second copy of it: a fixture + // written by a different rule than the one that reads it agrees with itself and with nothing. + expect(terminalDocumentFixture(XTERM_HTML, XTERM_ENGINE_JS, XTERM_ENGINE_CSS)).toBe(fixture) + }) + + it('holds the generated engine as placeholders, so an xterm bump is not a diff here', () => { + // Without this the fixture could lose a placeholder — inlining the engine, or dropping the + // section entirely — and the assertion above would still pass against whatever it became. + for (const placeholder of [ENGINE_JS_PLACEHOLDER, ENGINE_CSS_PLACEHOLDER]) { + expect(fixture.split(placeholder)).toHaveLength(2) + } + expect(fixture).not.toContain(XTERM_ENGINE_JS) + expect(fixture).not.toContain(XTERM_ENGINE_CSS) + }) + + it('is the whole document once the engine is put back', () => { + // The placeholder round trip, which is what makes the first case a claim about the document + // and not only about the hand-written part of it. + const restored = fixture + .replace(ENGINE_JS_PLACEHOLDER, () => XTERM_ENGINE_JS) + .replace(ENGINE_CSS_PLACEHOLDER, () => XTERM_ENGINE_CSS) + expect(restored).toBe(XTERM_HTML) + }) +})