diff --git a/scripts/check-text-newlines-browser.mjs b/scripts/check-text-newlines-browser.mjs index dc6b6f2..b0aeb5e 100644 --- a/scripts/check-text-newlines-browser.mjs +++ b/scripts/check-text-newlines-browser.mjs @@ -28,12 +28,16 @@ try { server = await createServer({ root, configFile: false, server: { host: '127.0.0.1', port: 0, strictPort: true }, - resolve: { alias: [ - { find: /^(?:.*\/)?primitive-adapter\.ts$/, replacement: resolve(root, 'src/client/standalone/primitive-adapter.tsx') }, - { find: /^react$/, replacement: resolve(root, 'node_modules/react/index.js') }, - { find: /^react\/jsx-runtime$/, replacement: resolve(root, 'node_modules/react/jsx-runtime.js') }, - { find: /^react-dom\/client$/, replacement: resolve(root, 'node_modules/react-dom/client.js') }, - ] }, + resolve: { + dedupe: ['react', 'react-dom'], + alias: [ + { find: /^(?:.*\/)?primitive-adapter\.ts$/, replacement: resolve(root, 'src/client/standalone/primitive-adapter.tsx') }, + ], + }, + optimizeDeps: { + include: ['react', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'react-dom', 'react-dom/client'], + force: true, + }, }) await server.listen() const address = server.httpServer.address() @@ -49,8 +53,21 @@ try { await writeFile(join(out, 'page.html'), stdout) await writeFile(join(out, 'browser.log'), stderr) const raw = /
([^<]*)<\/pre>/.exec(stdout)?.[1]
-  if (raw === undefined || raw === 'pending') throw new Error('Browser fixture did not finish; inspect page.html and browser.log')
-  const results = JSON.parse(raw.replaceAll('"', '"').replaceAll(''', "'").replaceAll('<', '<').replaceAll('>', '>').replaceAll('&', '&'))
+  const browserErrors = /
([\s\S]*?)<\/pre>/.exec(stdout)?.[1]
+  /** 解码 Chromium dump-dom 输出的 HTML 文本。 */
+  const decodeBrowserText = value => value
+    .replaceAll('"', '"')
+    .replaceAll(''', "'")
+    .replaceAll('<', '<')
+    .replaceAll('>', '>')
+    .replaceAll('&', '&')
+  const errorSummary = browserErrors === undefined ? '' : decodeBrowserText(browserErrors).trim().slice(0, 2000)
+  if (raw === undefined || raw === 'pending') {
+    const detail = errorSummary === '' ? 'inspect page.html and browser.log' : `Browser errors:\n${errorSummary}`
+    throw new Error(`Browser fixture did not finish; ${detail}`)
+  }
+  if (errorSummary !== '') throw new Error(`Browser reported client errors:\n${errorSummary}`)
+  const results = JSON.parse(decodeBrowserText(raw))
   await writeFile(join(out, 'results.json'), JSON.stringify(results, null, 2) + '\n')
   if (results.length !== 118) throw new Error(`Expected 118 initial/repeated-control layout checks, got ${results.length}`)
   const failed = results.filter(result => !result.pass)
diff --git a/tests/browser/text-newlines.html b/tests/browser/text-newlines.html
index d6327fc..43962d6 100644
--- a/tests/browser/text-newlines.html
+++ b/tests/browser/text-newlines.html
@@ -1 +1,21 @@
-Issues 249 and 280 browser regressions

Browser layout regressions

pending
+Issues 249 and 280 browser regressions

Browser layout regressions

pending

diff --git a/tests/browser/text-newlines.tsx b/tests/browser/text-newlines.tsx
index 06b98e2..6c683dc 100644
--- a/tests/browser/text-newlines.tsx
+++ b/tests/browser/text-newlines.tsx
@@ -1,5 +1,6 @@
 /** Synthetic real-renderer regression for #249. No host or model service. */
 import { createRoot } from 'react-dom/client'
+import { flushSync } from 'react-dom'
 import { GenuiBlock } from '../../src/client/GenuiBlock.tsx'
 import css from '../../src/client/GenuiBlock.module.css'
 import { STANDALONE_THEME_CSS } from '../../src/client/artifact/standalone-theme.ts'
@@ -86,16 +87,24 @@ cases.push({
   selector: '', expected: '', whiteSpace: '', detailWhitespace: true,
 })
 
-createRoot(document.getElementById('root')!).render(
-  
- {cases.map((item, index) => ( -
-
{item.name}
- -
- ))} -
, -) +const root = createRoot(document.getElementById('root')!) +flushSync(() => { + root.render( +
+ {cases.map((item, index) => ( +
+
{item.name}
+ +
+ ))} +
, + ) +}) + +const mountedFixtures = document.querySelectorAll('#fixtures > .case').length +if (mountedFixtures !== cases.length) { + throw new Error(`React fixture mount incomplete: expected ${cases.length}, got ${mountedFixtures}`) +} /** Character ranges measure actual painted line boxes, including rich text. */ function measure(phase: string) {