diff --git a/.github/workflows/cmux-next.yml b/.github/workflows/cmux-next.yml index 87d5f29f0b14..c1105ecf83df 100644 --- a/.github/workflows/cmux-next.yml +++ b/.github/workflows/cmux-next.yml @@ -105,8 +105,6 @@ on: - tests/test_cmux_next_checks_workflow.py - tests/test_cmux_next_generated_catch_up.py - .github/workflows/cmux-next-generated-catch-up.yml - - tests/test_cmux_next_regenerate_bundles_workflow.py - - .github/workflows/cmux-next-regenerate-bundles.yml - tests/test_next_batch.py - .github/workflows/cmux-next-batch.yml - tests/test_cmux_next_tree_notify.py @@ -205,8 +203,6 @@ on: - tests/test_cmux_next_checks_workflow.py - tests/test_cmux_next_generated_catch_up.py - .github/workflows/cmux-next-generated-catch-up.yml - - tests/test_cmux_next_regenerate_bundles_workflow.py - - .github/workflows/cmux-next-regenerate-bundles.yml - tests/test_next_batch.py - .github/workflows/cmux-next-batch.yml - tests/test_cmux_next_tree_notify.py @@ -757,14 +753,13 @@ jobs: python3 tests/test_cmux_next_route.py python3 tests/test_cmux_next_route_replay.py - # The companion workflows' structure: generated-files catch-up, the bundle - # regeneration dispatch and the next-batch landing queue. + # The companion workflows' structure: generated-files catch-up and the + # next-batch landing queue. - name: Companion workflows id: companion-workflows continue-on-error: true run: | python3 tests/test_cmux_next_generated_catch_up.py - python3 tests/test_cmux_next_regenerate_bundles_workflow.py python3 tests/test_next_batch.py # A pull request that changes the router replays the last 30 merged PRs' changed files diff --git a/tests/test-execution.toml b/tests/test-execution.toml index a074c4494b49..1f3debaaa86f 100644 --- a/tests/test-execution.toml +++ b/tests/test-execution.toml @@ -996,10 +996,6 @@ lane = "linux-guard" path = "tests/test_cmux_next_route.py" lane = "linux-guard" -[[test]] -path = "tests/test_cmux_next_regenerate_bundles_workflow.py" -lane = "linux-guard" - [[test]] path = "tests/test_gh_merge_green.py" lane = "linux-guard" diff --git a/tests/test_build_web_bundles_from.sh b/tests/test_build_web_bundles_from.sh index 91b7e97422cc..4061bd043158 100755 --- a/tests/test_build_web_bundles_from.sh +++ b/tests/test_build_web_bundles_from.sh @@ -3,39 +3,52 @@ # source key (cmux-next.yml's Linux web-bundles job caches them by that key) and stamps them, # so a Mac job that restores them skips the build. A tree missing a bundle is refused and the # checkout is left alone. +# +# The bundles are build output, not committed (41b907dce7b), so the test runs a copy of the +# script in a scratch repository with a synthetic prebuilt tree. It never touches this +# checkout's generated bundles or its .web-bundles.key. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -SCRIPT="$ROOT/scripts/cmux-next/build-web-bundles.sh" WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT -cd "$ROOT" + +REPO="$WORK/repo" +mkdir -p "$REPO/scripts/cmux-next" +cp "$ROOT/scripts/cmux-next/build-web-bundles.sh" "$ROOT/scripts/cmux-next/web-bundle-key.py" "$REPO/scripts/cmux-next/" +SCRIPT="$REPO/scripts/cmux-next/build-web-bundles.sh" +git -C "$REPO" init -q PANE="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/Resources/agent-pane" PAGES="Packages/macOS/CmuxNext/Sources/CmuxNextPages/Resources/pages" ACTIVITY="Packages/macOS/CmuxNext/Sources/CmuxNextAgentActivity/Resources/agent-activity" PALETTE="Packages/macOS/CmuxNext/Sources/CmuxNextPalette/Resources/palette-ranker.js" APP="Resources/markdown-viewer/webviews-app" +INSPECTOR="Native/OptChat/optchat-chief/inspector" + +# The palette ranker lands in a Resources directory that holds tracked files in a checkout. +mkdir -p "$REPO/$(dirname "$PALETTE")" -# A prebuilt tree: the committed bundles stand in for the cached build of this source key. -for path in "$PANE" "$PAGES" "$ACTIVITY" "$APP"; do - mkdir -p "$WORK/out/$(dirname "$path")" - cp -R "$path" "$WORK/out/$path" +# A prebuilt tree for this source key, as the Linux web-bundles job caches it. +for path in "$PANE" "$PAGES" "$ACTIVITY" "$APP" "$INSPECTOR"; do + mkdir -p "$WORK/out/$path" + printf '%s\n' "$path" > "$WORK/out/$path/index.html" done mkdir -p "$WORK/out/$(dirname "$PALETTE")" -cp "$PALETTE" "$WORK/out/$PALETTE" +printf 'export const rank = () => 0;\n' > "$WORK/out/$PALETTE" -rm -f .web-bundles.key "$SCRIPT" --from "$WORK/out" > "$WORK/log" 2>&1 || { cat "$WORK/log"; echo "FAIL: --from must install a complete prebuilt tree"; exit 1; } "$SCRIPT" --verify > /dev/null 2>&1 || { echo "FAIL: after --from the bundles must be current without a build"; exit 1; } -git diff --quiet -- "$PANE" "$PAGES" "$ACTIVITY" "$PALETTE" "$APP" \ - || { echo "FAIL: --from changed the committed bundles"; exit 1; } +for path in "$PANE" "$PAGES" "$ACTIVITY" "$APP" "$INSPECTOR"; do + diff -r "$WORK/out/$path" "$REPO/$path" > /dev/null || { echo "FAIL: --from did not install $path as built"; exit 1; } +done +cmp -s "$WORK/out/$PALETTE" "$REPO/$PALETTE" || { echo "FAIL: --from did not install $PALETTE as built"; exit 1; } rm -rf "$WORK/out/$PAGES" -rm -f .web-bundles.key +rm -f "$REPO/.web-bundles.key" if "$SCRIPT" --from "$WORK/out" > "$WORK/log" 2>&1; then echo "FAIL: --from must refuse a tree that is missing a bundle"; exit 1 fi -[ -d "$PAGES" ] || { echo "FAIL: a refused --from must leave the checkout's bundles alone"; exit 1; } -[ ! -f .web-bundles.key ] || { echo "FAIL: a refused --from must not stamp the bundles current"; exit 1; } +[ -f "$REPO/$PAGES/index.html" ] || { echo "FAIL: a refused --from must leave the checkout's bundles alone"; exit 1; } +[ ! -f "$REPO/.web-bundles.key" ] || { echo "FAIL: a refused --from must not stamp the bundles current"; exit 1; } echo "PASS: build-web-bundles.sh --from installs and stamps a prebuilt tree, and refuses an incomplete one" diff --git a/tests/test_cmux_next_checks_workflow.py b/tests/test_cmux_next_checks_workflow.py index 864382dbe003..490dcf853c64 100755 --- a/tests/test_cmux_next_checks_workflow.py +++ b/tests/test_cmux_next_checks_workflow.py @@ -144,6 +144,20 @@ def test_rust_ratchet_is_its_own_step(self): self.assertEqual(sum("--only rust" in run for run in godfile_runs), 1, godfile_runs) +class ReferencedFilesExist(unittest.TestCase): + """Every test a checks step runs, and every test or workflow a path filter names, exists. + The bundle removal (ce0b9e76a9b) deleted tests/test_cmux_next_regenerate_bundles_workflow.py + and its workflow, but the Companion workflows step still ran the test, so every pull + request's checks went red (run 37646119996).""" + + def test_named_tests_and_workflows_exist(self): + text = WORKFLOW.read_text(encoding="utf-8") + named = set(re.findall(r"(?:python3|bash) (tests/[A-Za-z0-9_./-]+\.(?:py|sh))", text)) + named |= set(re.findall(r"^\s+- ((?:tests|\.github/workflows)/[A-Za-z0-9_./-]+\.(?:py|sh|yml))$", text, re.M)) + missing = sorted(path for path in named if not (ROOT / path).exists()) + self.assertEqual(missing, []) + + class GodfileScopes(unittest.TestCase): """`--only swift` and `--only rust` each check their half, at unchanged budgets.""" diff --git a/tests/test_cmux_next_route.py b/tests/test_cmux_next_route.py index c7764335ee2f..15342b4c0d00 100644 --- a/tests/test_cmux_next_route.py +++ b/tests/test_cmux_next_route.py @@ -109,8 +109,14 @@ def test_cmux_tui_runs_the_daemon_tier_and_the_scheme_compile(self): self.assertEqual(result["swift_targets"], "") def test_a_file_a_test_reads_selects_that_test(self): + # CmuxNextSettingsTests reads the schema. Since the bundles are build output + # (ce0b9e76a9b) the schema is also a web-bundle input, so the targets that ship + # a bundle, and the app that embeds them, are selected with it, and nothing else. result = tiers(["schemas/settings/settings-schema.json"]) - self.assertEqual(result["swift_targets"], "CmuxNextSettingsTests") + self.assertEqual(result["swift_targets"].split(), sorted([ + "CmuxNextSettingsTests", "CmuxNextAppTests", + "CmuxNextAgentPaneTests", "CmuxNextPagesTests", "CmuxNextAgentActivityTests", "CmuxNextPaletteTests", + ])) def test_a_local_package_selects_the_targets_that_use_it(self): result = tiers(["Packages/Shared/CmuxHomeCore/Sources/CmuxHomeCore/Thread.swift"]) diff --git a/webviews/src/agent-session/acpmux/ModelPicker.tsx b/webviews/src/agent-session/acpmux/ModelPicker.tsx index 07038c970377..398607f476ea 100644 --- a/webviews/src/agent-session/acpmux/ModelPicker.tsx +++ b/webviews/src/agent-session/acpmux/ModelPicker.tsx @@ -498,7 +498,7 @@ export function ModelPicker(props: ModelPickerProps) { {entry.name} {folderNote(entry) && {folderNote(entry)}} {entry.ids.includes(harness ?? "") && } - + , ])} diff --git a/webviews/src/agent-session/acpmux/composerPickers.test.tsx b/webviews/src/agent-session/acpmux/composerPickers.test.tsx index d27a7cebf0f0..6c7bd1d2c992 100644 --- a/webviews/src/agent-session/acpmux/composerPickers.test.tsx +++ b/webviews/src/agent-session/acpmux/composerPickers.test.tsx @@ -239,12 +239,16 @@ describe("acpmux composer pickers", () => { // The highlight opens on the current mode, the last one. await key(mode, "ArrowDown"); expect( - [...doc.querySelectorAll("[role=menuitemradio]")].find((row) => row.textContent?.includes("Full access")), + [...doc.querySelectorAll("[role=menuitemradio]")].find((row) => + row.textContent?.includes("Full access"), + ), ).toBeTruthy(); // A live update drops that option while it is highlighted. await render(snapshot({ modes: { ...full, availableModes: [full.availableModes[0]!] } })); expect( - [...doc.querySelectorAll("[role=menuitemradio]")].find((row) => row.textContent?.includes("Ask for approval")), + [...doc.querySelectorAll("[role=menuitemradio]")].find((row) => + row.textContent?.includes("Ask for approval"), + ), ).toBeTruthy(); await key(mode, " "); expect(mode.getAttribute("aria-expanded")).toBe("true"); diff --git a/webviews/test/gallery-coverage.allowlist.json b/webviews/test/gallery-coverage.allowlist.json index 94aa6f461c47..60aaa32a640b 100644 --- a/webviews/test/gallery-coverage.allowlist.json +++ b/webviews/test/gallery-coverage.allowlist.json @@ -12,6 +12,7 @@ "agent-session/acpmux/ComposerPickers.tsx#ChevronIcon", "agent-session/acpmux/ComposerPickers.tsx#ChevronRightIcon", "agent-session/acpmux/ComposerPickers.tsx#ContextRing", + "agent-session/acpmux/ComposerPickers.tsx#LockIcon", "agent-session/acpmux/ComposerPickers.tsx#MicIcon", "agent-session/acpmux/ComposerPickers.tsx#PaperclipIcon", "agent-session/acpmux/ComposerPickers.tsx#Picker", @@ -33,12 +34,12 @@ "agent-session/acpmux/Keycap.tsx#Keycap", "agent-session/acpmux/MenuLevel.tsx#MenuLevel", "agent-session/acpmux/MenuRow.tsx#MenuRow", - "agent-session/acpmux/ModelPicker.tsx#ModelPicker", "agent-session/acpmux/ModelPickerCascade.tsx#ModelPickerCascade", "agent-session/acpmux/ModelPickerDrill.tsx#ModelPickerDrill", "agent-session/acpmux/ModelPickerShell.tsx#ModelPickerShell", "agent-session/acpmux/NewTabPage.tsx#KindIcon", "agent-session/acpmux/NewTabPage.tsx#NewTabPage", + "agent-session/acpmux/ProjectBadge.tsx#ProjectBadge", "agent-session/acpmux/QuickKeyHints.tsx#QuickKeyHints", "agent-session/acpmux/QuickSurface.tsx#QuickSurface", "agent-session/acpmux/SessionSidebar.tsx#SessionSidebar", @@ -114,6 +115,7 @@ "agent-session/acpmux/icons/Icon.tsx#Icon", "agent-session/acpmux/icons/IconsContext.tsx#IconsProvider", "agent-session/acpmux/permissions/Panel.tsx#PermissionPanel", + "agent-session/acpmux/question/QuestionCard.tsx#QuestionCard", "agent-session/acpmux/shell/MoveRow.tsx#MoveRow", "agent-session/acpmux/shell/ShellRow.tsx#ShellActionsContext", "agent-session/acpmux/shell/ShellRow.tsx#ShellRow", @@ -134,6 +136,7 @@ "pages/markdown/linkOverlays.tsx#LinkCardView", "pages/markdown/linkOverlays.tsx#LinkOverlayHost", "pages/markdown/linkOverlays.tsx#LinkPopoverView", + "pages/settings/editors/ChatRootsEditor.tsx#ChatRootsEditor", "pages/settings/editors/OrderedChoicesEditor.tsx#OrderedChoicesEditor", "pages/shared/PageMenu.tsx#PageMenu", "ui/Breadcrumbs.tsx#Breadcrumbs", @@ -154,6 +157,12 @@ "ui/Menu.tsx#MenuRadioItem", "ui/Menu.tsx#MenuSeparator", "ui/Menu.tsx#Submenu", + "ui/MinimapTick.tsx#MinimapTick", + "ui/PickerPrimitives.tsx#PickerButton", + "ui/PickerPrimitives.tsx#PickerComboboxInput", + "ui/PickerPrimitives.tsx#PickerDialog", + "ui/PickerPrimitives.tsx#PickerOption", + "ui/PickerPrimitives.tsx#PickerOptionList", "ui/Popover.tsx#Popover", "ui/Select.tsx#Select", "ui/Sheet.tsx#Sheet", diff --git a/webviews/test/palette-ranker.test.ts b/webviews/test/palette-ranker.test.ts index f2ecd2415456..4e61140242c8 100644 --- a/webviews/test/palette-ranker.test.ts +++ b/webviews/test/palette-ranker.test.ts @@ -1,4 +1,7 @@ import { describe, expect, test } from "bun:test"; +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { rankPalette, rankPaletteEmpty, type PaletteFrecency, type PaletteRankEntry } from "../src/palette/ranker"; const now = 800_000_000; @@ -153,12 +156,16 @@ describe("shared palette ranker", () => { expect(result[0]?.rows[1]?.highlights).toEqual([]); }); - test("the checked-in JavaScriptCore bridge exposes the same ranker", async () => { - const bundle = await Bun.file( - "../Packages/macOS/CmuxNext/Sources/CmuxNextPalette/Resources/palette-ranker.js", - ).text(); + test("the built JavaScriptCore bridge exposes the same ranker", async () => { + // The bridge is build output (scripts/cmux-next/build-web-bundles.sh), not a committed file: + // build it into a scratch directory, as the app build does into the package resources. + const out = mkdtempSync(join(tmpdir(), "palette-ranker-")); + const built = Bun.spawnSync(["sh", "../scripts/cmux-next/build-palette-ranker.sh", "--out", out]); + expect(built.exitCode).toBe(0); + const bundlePath = join(out, "palette-ranker.js"); + const bundle = await Bun.file(bundlePath).text(); expect(bundle.length).toBeGreaterThan(0); - await import("../../Packages/macOS/CmuxNext/Sources/CmuxNextPalette/Resources/palette-ranker.js"); + await import(bundlePath); const bridge = (globalThis as typeof globalThis & { __cmuxPaletteRank?: (request: string) => string }) .__cmuxPaletteRank; expect(typeof bridge).toBe("function");