Track write/edit tool paths per turn, refresh the open preview on tool_complete and after preservePreview loadDir on stream done, without closing preview for unrelated responses or wiping unsaved local edits.
86 lines
3.5 KiB
Python
86 lines
3.5 KiB
Python
"""Regression: workspace file preview must survive background file-tree refresh on chat done."""
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
MESSAGES_JS = (REPO / "static" / "messages.js").read_text(encoding="utf-8")
|
|
WORKSPACE_JS = (REPO / "static" / "workspace.js").read_text(encoding="utf-8")
|
|
|
|
|
|
def _function_block(src: str, name: str) -> str:
|
|
marker = f"function {name}("
|
|
start = src.find(marker)
|
|
assert start != -1, f"{name}() not found"
|
|
params_end = src.find("){", start)
|
|
assert params_end != -1, f"{name}() body not found"
|
|
brace = params_end + 1
|
|
depth = 0
|
|
for idx in range(brace, len(src)):
|
|
ch = src[idx]
|
|
if ch == "{":
|
|
depth += 1
|
|
elif ch == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return src[start : idx + 1]
|
|
raise AssertionError(f"{name}() body did not close")
|
|
|
|
|
|
def _done_block() -> str:
|
|
start = MESSAGES_JS.find("source.addEventListener('done'")
|
|
assert start != -1, "done handler not found in messages.js"
|
|
end = MESSAGES_JS.find("source.addEventListener('stream_end'", start)
|
|
assert end != -1, "stream_end handler not found after done handler"
|
|
return MESSAGES_JS[start:end]
|
|
|
|
|
|
def test_stream_done_refreshes_workspace_without_clearing_preview():
|
|
"""Chat completion should refresh the tree but not exit an open file preview."""
|
|
done_block = _done_block()
|
|
assert "preservePreview:true" in done_block.replace(" ", ""), (
|
|
"The done handler must refresh the workspace file tree without calling the "
|
|
"directory-navigation clearPreview path in loadDir()."
|
|
)
|
|
|
|
|
|
def test_load_dir_supports_preserve_preview_option():
|
|
block = _function_block(WORKSPACE_JS, "loadDir")
|
|
assert "preservePreview" in block, "loadDir() must accept a preservePreview option"
|
|
assert "if(!preservePreview&&typeofclearPreview" in block.replace(" ", ""), (
|
|
"loadDir() should skip clearPreview() when preservePreview is requested"
|
|
)
|
|
assert "awaitrefreshOpenPreviewIfMutated()" in block.replace(" ", ""), (
|
|
"Background refresh must reload the open preview when a mutation tool touched it"
|
|
)
|
|
|
|
|
|
def test_load_dir_still_clears_preview_for_directory_navigation():
|
|
"""#1785: explicit directory navigation must still switch preview back to browse mode."""
|
|
block = _function_block(WORKSPACE_JS, "loadDir")
|
|
assert "clearPreview({keepPanelOpen:true})" in block.replace(" ", ""), (
|
|
"Directory navigation must still clear previews when preservePreview is not set"
|
|
)
|
|
|
|
|
|
def test_turn_mutation_tracking_reloads_open_preview():
|
|
block = _function_block(WORKSPACE_JS, "refreshOpenPreviewIfMutated")
|
|
assert "openFile(_previewCurrentPath" in block.replace(" ", ""), (
|
|
"Mutated open previews must reload through openFile()"
|
|
)
|
|
assert "_previewDirty" in block, "Reload must be skipped while the preview has unsaved edits"
|
|
|
|
|
|
def test_tool_complete_tracks_workspace_mutations_for_preview_reload():
|
|
tool_complete_idx = MESSAGES_JS.find("source.addEventListener('tool_complete'")
|
|
assert tool_complete_idx != -1
|
|
end = MESSAGES_JS.find("source.addEventListener('approval'", tool_complete_idx)
|
|
block = MESSAGES_JS[tool_complete_idx:end]
|
|
assert "noteWorkspaceMutationsFromToolCall" in block
|
|
assert "refreshOpenPreviewIfMutated" in block
|
|
|
|
|
|
def test_stream_start_resets_turn_mutation_tracking():
|
|
block = _function_block(MESSAGES_JS, "attachLiveStream")
|
|
assert "resetTurnWorkspaceMutations" in block
|