From dbba4978ee6080b121b8213d88ced9c5a5a43c1d Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 22 Sep 2026 18:06:18 +0100 Subject: [PATCH] test(harness): support cache-busted frontend module imports --- .../test_settings_shell_coordinator.mjs | 3 +- tests/streaming/markdownHarness.mjs | 2 +- tests/test_copy_message_strips_thinking_js.py | 2 +- tests/test_markdown_lazy_lib_loading_js.py | 2 +- tests/test_markdown_rendering_js.py | 2 +- tests/test_startup_session_bootstrap_js.py | 53 ++++++++++++++----- ...test_stream_completion_scroll_stability.py | 5 +- 7 files changed, 50 insertions(+), 19 deletions(-) diff --git a/tests/helpers/test_settings_shell_coordinator.mjs b/tests/helpers/test_settings_shell_coordinator.mjs index 79bf1fd84..458f0516a 100644 --- a/tests/helpers/test_settings_shell_coordinator.mjs +++ b/tests/helpers/test_settings_shell_coordinator.mjs @@ -847,9 +847,10 @@ function resolveImport(specifier, parent) { throw new Error(`Unexpected non-relative import: ${specifier}`); } + const cleanSpecifier = specifier.split('?')[0].split('#')[0]; return path.resolve( path.dirname(parent), - specifier, + cleanSpecifier, ); } diff --git a/tests/streaming/markdownHarness.mjs b/tests/streaming/markdownHarness.mjs index 03e12fa61..af34b3372 100644 --- a/tests/streaming/markdownHarness.mjs +++ b/tests/streaming/markdownHarness.mjs @@ -26,7 +26,7 @@ export async function loadMarkdown() { globalThis.MutationObserver = class { observe() {} }; let src = fs.readFileSync(path.join(REPO, 'static/js/markdown.js'), 'utf8'); - src = src.replace(/import uiModule from ['"]\.\/ui\.js['"];/, ''); + src = src.replace(/import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/, ''); src = src.replace( /import \{ splitTableRow \} from ['"]\.\/markdown\/tableRow\.js['"];/, () => `function splitTableRow(row){return (row||'').replace(/^\\s*\\|/,'').replace(/\\|\\s*$/,'').split('|').map((c)=>c.trim());}`, diff --git a/tests/test_copy_message_strips_thinking_js.py b/tests/test_copy_message_strips_thinking_js.py index 4c912925c..74a03f80c 100644 --- a/tests/test_copy_message_strips_thinking_js.py +++ b/tests/test_copy_message_strips_thinking_js.py @@ -58,7 +58,7 @@ def _extract_thinking_blocks(text: str) -> dict: let source = fs.readFileSync('./static/js/markdown.js', 'utf8'); source = source.replace( - /import uiModule from ['"]\.\/ui\.js['"];/, + /import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/, '' ); source = source.replace( diff --git a/tests/test_markdown_lazy_lib_loading_js.py b/tests/test_markdown_lazy_lib_loading_js.py index e9d781caa..9a3b6e43b 100644 --- a/tests/test_markdown_lazy_lib_loading_js.py +++ b/tests/test_markdown_lazy_lib_loading_js.py @@ -102,7 +102,7 @@ globalThis.document = { globalThis.MutationObserver = class { observe() {} }; let source = fs.readFileSync('./static/js/markdown.js', 'utf8'); -source = source.replace(/import uiModule from ['"]\.\/ui\.js['"];/, ''); +source = source.replace(/import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/, ''); source = source.replace( /import \{ splitTableRow \} from ['"]\.\/markdown\/tableRow\.js['"];/, `function splitTableRow(row) { diff --git a/tests/test_markdown_rendering_js.py b/tests/test_markdown_rendering_js.py index 3eb698b3e..2391f98ba 100644 --- a/tests/test_markdown_rendering_js.py +++ b/tests/test_markdown_rendering_js.py @@ -53,7 +53,7 @@ def _run_markdown_case(markdown: str, render_expr: str = "mod.mdToHtml(input)", let source = fs.readFileSync('./static/js/markdown.js', 'utf8'); source = source.replace( - /import uiModule from ['"]\.\/ui\.js['"];/, + /import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/, '' ); source = source.replace( diff --git a/tests/test_startup_session_bootstrap_js.py b/tests/test_startup_session_bootstrap_js.py index 02e7be487..3fe37748b 100644 --- a/tests/test_startup_session_bootstrap_js.py +++ b/tests/test_startup_session_bootstrap_js.py @@ -19,17 +19,7 @@ _SESSIONS = _REPO / "static" / "js" / "sessions.js" _SHELL_URL = (_REPO / "static" / "js" / "startupShell.js").as_uri() _HAS_NODE = shutil.which("node") is not None -_IMPORT_REWRITES = { - "import Storage from './storage.js';": "import Storage from './storage.mjs';", - "import uiModule, { autoResize, styledPrompt } from './ui.js';": ( - "import uiModule, { autoResize, styledPrompt } from './ui.mjs';" - ), - "import { providerLogo } from './providers.js';": ( - "import { providerLogo } from './providers.mjs';" - ), - "import themeModule from './theme.js';": "import themeModule from './theme.mjs';", - "import spinnerModule from './spinner.js';": "import spinnerModule from './spinner.mjs';", -} +_IMPORT_REWRITES = {} _STUBS = { "storage.mjs": r""" @@ -66,6 +56,15 @@ export default ui; ), "theme.mjs": "export default {};\n", "spinner.mjs": "export default {};\n", + "actionMenuOrder.mjs": ( + "export const SELECT_MENU_ICON = '';\n" + "export const actionMenuRank = () => 0;\n" + "export const orderActionMenuItems = (items) => items;\n" + ), + "escMenuStack.mjs": ( + "export const registerEscapeLayer = () => () => {};\n" + "export const bindMenuDismiss = () => {};\n" + ), } _HARNESS = r""" @@ -303,13 +302,41 @@ def results(tmp_path_factory): source = _SESSIONS.read_text(encoding="utf-8") versioned_rewrites = ( ( - r"import chatRenderer from './chatRenderer\.js(?:\?v=[A-Za-z0-9_-]+)?';", + r"import Storage from './storage\.js(?:\?[^']+)?';", + "import Storage from './storage.mjs';", + ), + ( + r"import uiModule, \{ autoResize, styledPrompt \} from './ui\.js(?:\?[^']+)?';", + "import uiModule, { autoResize, styledPrompt } from './ui.mjs';", + ), + ( + r"import chatRenderer from './chatRenderer\.js(?:\?[^']+)?';", "import chatRenderer from './chatRenderer.mjs';", ), ( - r"import \{ initModelPicker, updateModelPicker \} from './modelPicker\.js(?:\?v=[A-Za-z0-9_-]+)?';", + r"import \{ providerLogo \} from './providers\.js(?:\?[^']+)?';", + "import { providerLogo } from './providers.mjs';", + ), + ( + r"import \{ initModelPicker, updateModelPicker \} from './modelPicker\.js(?:\?[^']+)?';", "import { initModelPicker, updateModelPicker } from './modelPicker.mjs';", ), + ( + r"import themeModule from './theme\.js(?:\?[^']+)?';", + "import themeModule from './theme.mjs';", + ), + ( + r"import spinnerModule from './spinner\.js(?:\?[^']+)?';", + "import spinnerModule from './spinner.mjs';", + ), + ( + r"import \{ actionMenuRank, orderActionMenuItems, SELECT_MENU_ICON \} from './actionMenuOrder\.js(?:\?[^']+)?';", + "import { actionMenuRank, orderActionMenuItems, SELECT_MENU_ICON } from './actionMenuOrder.mjs';", + ), + ( + r"import \{ registerEscapeLayer, bindMenuDismiss \} from './escMenuStack\.js(?:\?[^']+)?';", + "import { registerEscapeLayer, bindMenuDismiss } from './escMenuStack.mjs';", + ), ) for pattern, replacement in versioned_rewrites: source, count = re.subn(pattern, replacement, source, count=1) diff --git a/tests/test_stream_completion_scroll_stability.py b/tests/test_stream_completion_scroll_stability.py index 45b90ffec..752c107be 100644 --- a/tests/test_stream_completion_scroll_stability.py +++ b/tests/test_stream_completion_scroll_stability.py @@ -1,4 +1,5 @@ from pathlib import Path +import re ROOT = Path(__file__).resolve().parents[1] @@ -83,7 +84,9 @@ def test_large_tool_scroll_fix_is_served_under_a_fresh_chat_module_key(): """The fixed ui module is imported by chat.js, so stale chat.js is stale UI.""" app = (ROOT / "static/app.js").read_text(encoding="utf-8") index = (ROOT / "static/index.html").read_text(encoding="utf-8") - key = "chat.js?v=20260916largetoolscroll2" + match = re.search(r"chat\.js\?v=([A-Za-z0-9_-]+)", app) + assert match is not None, "chat.js must be imported with a cache-busting key" + key = f"chat.js?v={match.group(1)}" assert key in app assert index.count(key) == 2