Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
164 lines
6.7 KiB
TypeScript
164 lines
6.7 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
|
|
import {
|
|
buildScriptsSidePanelRows,
|
|
collectScriptsSidePanelPackagePaths,
|
|
} from "./ScriptsSidePanel.tsx";
|
|
import type { Snippet } from "../types";
|
|
|
|
const snippet = (overrides: Partial<Snippet>): Snippet => ({
|
|
id: overrides.id ?? "snippet",
|
|
label: overrides.label ?? "Snippet",
|
|
command: overrides.command ?? "echo ok",
|
|
package: overrides.package ?? "",
|
|
order: overrides.order,
|
|
});
|
|
|
|
const source = readFileSync(new URL("./ScriptsSidePanel.tsx", import.meta.url), "utf8");
|
|
|
|
test("scripts side panel rows keep manual snippet order inside a package", () => {
|
|
const rows = buildScriptsSidePanelRows({
|
|
snippets: [
|
|
snippet({ id: "alpha", label: "Alpha", package: "ops", order: 3000 }),
|
|
snippet({ id: "zulu", label: "Zulu", package: "ops", order: 1000 }),
|
|
snippet({ id: "beta", label: "Beta", package: "ops", order: 2000 }),
|
|
],
|
|
packages: ["ops"],
|
|
expandedPaths: new Set(["ops"]),
|
|
});
|
|
|
|
assert.deepEqual(
|
|
rows.filter((row) => row.type === "snippet").map((row) => row.id),
|
|
["zulu", "beta", "alpha"],
|
|
);
|
|
});
|
|
|
|
test("scripts side panel active tabs pair the accent background with its foreground", () => {
|
|
assert.equal(source.match(/bg-accent text-accent-foreground/g)?.length, 2);
|
|
});
|
|
|
|
test("scripts side panel offers run-on-all-tabs for every snippet, not only automation scripts", () => {
|
|
// Guard against re-introducing the isScriptSnippet gate on workspace run actions.
|
|
assert.doesNotMatch(
|
|
source,
|
|
/onRunParallel=\{isScriptSnippet\(item\.row\.snippet\) && onRunScriptOnWorkspace/,
|
|
);
|
|
assert.match(source, /onRunParallel=\{onRunScriptOnWorkspace/);
|
|
// Sequential remains script-only (mode is meaningful only for automation runs).
|
|
assert.match(source, /onRunSequential=\{isScript && onRunScriptOnWorkspace/);
|
|
assert.match(source, /scripts\.actions\.runOnAllTabs/);
|
|
assert.match(source, /snippets\.action\.newPackage/);
|
|
assert.match(source, /openPackageDialog/);
|
|
});
|
|
|
|
test("scripts side panel exposes create actions as inline toolbar icons", () => {
|
|
assert.match(source, /snippets\.action\.newSnippet/);
|
|
assert.match(source, /snippets\.action\.newPackage/);
|
|
assert.match(source, /snippets\.action\.newScript/);
|
|
assert.match(source, /openPackageDialog/);
|
|
assert.match(source, /handleAddScript/);
|
|
assert.doesNotMatch(source, /ChevronDown/);
|
|
assert.doesNotMatch(source, /DropdownTrigger/);
|
|
});
|
|
|
|
test("collectScriptsSidePanelPackagePaths includes implied ancestors", () => {
|
|
assert.deepEqual(
|
|
collectScriptsSidePanelPackagePaths(["ops/linux"], [
|
|
snippet({ id: "a", package: "ops/linux/disk" }),
|
|
snippet({ id: "b", package: "" }),
|
|
]).sort(),
|
|
["ops", "ops/linux", "ops/linux/disk"],
|
|
);
|
|
});
|
|
|
|
test("scripts side panel search is icon-toggled and expands below the toolbar", () => {
|
|
assert.match(source, /searchExpanded/);
|
|
assert.match(source, /setSearchExpanded/);
|
|
assert.match(source, /max-h-0 opacity-0/);
|
|
assert.match(source, /snippets\.searchPlaceholder/);
|
|
});
|
|
|
|
test("scripts side panel shows delete in a selection bar only when snippets are selected", () => {
|
|
assert.match(source, /selectedSnippetIds\.size > 0/);
|
|
assert.match(source, /snippets\.selection\.selected[\s\S]*?deleteSelectedSnippets/);
|
|
assert.match(source, /snippets\.selection\.deleteSelected/);
|
|
// Trash must not sit between multi-select and add on the permanent icon row.
|
|
assert.doesNotMatch(
|
|
source,
|
|
/snippets\.action\.selectSnippets[\s\S]*?Trash2[\s\S]*?snippets\.action\.newSnippet/,
|
|
);
|
|
});
|
|
|
|
test("scripts side panel toolbar exposes expand and collapse actions", () => {
|
|
assert.match(source, /vault\.tree\.expandAll/);
|
|
assert.match(source, /vault\.tree\.collapseAll/);
|
|
assert.match(source, /expandAllGroups/);
|
|
assert.match(source, /collapseAllGroups/);
|
|
assert.match(source, /isMultiSelectMode/);
|
|
});
|
|
|
|
test("scripts side panel confirms bulk delete and routes it through the shared delete event", () => {
|
|
// Mass-delete must confirm first (SnippetsManager parity) and must not bypass
|
|
// AppSideEffects via onSnippetsChange filtering — that path skips host binding cleanup.
|
|
assert.match(source, /VaultDeleteConfirmDialog/);
|
|
assert.match(source, /snippets\.selection\.deleteConfirmTitle/);
|
|
assert.match(source, /snippets\.selection\.deleteConfirmDesc/);
|
|
assert.match(source, /detail:\s*\{\s*ids\s*\}/);
|
|
assert.doesNotMatch(
|
|
source,
|
|
/onSnippetsChange\(snippets\.filter\(\(snippet\) => !selectedSnippetIds\.has/,
|
|
);
|
|
});
|
|
|
|
test("scripts side panel defers bulk-delete confirm when a parent owns the dialog", () => {
|
|
// Compact TerminalToolbar nests this panel in a Popover; the portalled confirm
|
|
// must be owned outside that tree so focus cannot unmount the prompt.
|
|
assert.match(source, /onBulkDeleteRequest\?:/);
|
|
assert.match(source, /if\s*\(\s*onBulkDeleteRequest\s*\)\s*\{/);
|
|
assert.match(source, /onBulkDeleteRequest\(ids\)/);
|
|
assert.match(source, /!onBulkDeleteRequest/);
|
|
});
|
|
|
|
test("scripts side panel clears pending bulk delete when the panel hides", () => {
|
|
// Returning null while isVisible is false unmounts the confirm dialog; drop
|
|
// pending deletes so a later re-show does not resurrect a half-dismissed prompt.
|
|
assert.match(
|
|
source,
|
|
/if\s*\(\s*isVisible\s*\)\s*return/,
|
|
);
|
|
assert.match(source, /setIsPackageDialogOpen\(\s*false\s*\)/);
|
|
assert.match(source, /setRenamingPackagePath\(\s*''\s*\)/);
|
|
assert.match(source, /setNewPackageName\(\s*''\s*\)/);
|
|
});
|
|
|
|
test("scripts side panel package dialog traps focus and exposes dialog close contract", () => {
|
|
assert.match(source, /packageDialogRef/);
|
|
assert.match(source, /data-dialog-close="true"/);
|
|
assert.match(source, /isPackageDialogOpen/);
|
|
});
|
|
|
|
test("scripts side panel package rows wrap in a rename and delete context menu", () => {
|
|
assert.match(source, /const PackageRow = memo/);
|
|
assert.match(
|
|
source,
|
|
/data-pkg-path=\{row\.path\}[\s\S]*?<ContextMenu>[\s\S]*?<ContextMenuTrigger asChild>[\s\S]*?\{rowButton\}/,
|
|
);
|
|
assert.match(source, /common\.rename/);
|
|
assert.match(source, /vault\.deleteConfirm\.packageDesc/);
|
|
assert.match(source, /deleteSnippetPackage/);
|
|
assert.match(source, /renameSnippetPackage/);
|
|
assert.match(source, /SNIPPET_PACKAGE_PATH_CHANGE_EVENT/);
|
|
assert.match(source, /detail: \{ from: renamingPackagePath, to: result\.newPath \}/);
|
|
assert.match(source, /detail: \{ from: path, to: null \}/);
|
|
assert.match(source, /openRenamePackageDialog/);
|
|
assert.match(source, /requestDeletePackage/);
|
|
});
|
|
|
|
test("scripts side panel does not start a drag from a non-primary pointer", () => {
|
|
assert.match(source, /from '\.\/ui\/primaryOnlyDrag'/);
|
|
assert.match(source, /isNonPrimaryPointer/);
|
|
assert.match(source, /primaryOnlyDragHandlers/);
|
|
});
|