feat: add multi-session desktop workspace
This commit is contained in:
+69
-370
@@ -2,385 +2,84 @@ import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
test("renders every Pi RPC extension UI method without replying to fire-and-forget requests", async () => {
|
||||
const source = await readFile("ui/src/App.tsx", "utf8");
|
||||
for (const method of [
|
||||
"select",
|
||||
"confirm",
|
||||
"input",
|
||||
"editor",
|
||||
"notify",
|
||||
"setStatus",
|
||||
"setWidget",
|
||||
"setTitle",
|
||||
"set_editor_text",
|
||||
]) {
|
||||
assert.match(source, new RegExp(`case "${method}"`));
|
||||
}
|
||||
assert.match(source, /setPendingExtension\(extension\)/);
|
||||
assert.doesNotMatch(source, /This extension UI is unsupported/);
|
||||
assert.match(source, /function stripAnsi/);
|
||||
assert.match(source, /stripAnsi\(extension\.statusText\)/);
|
||||
assert.match(source, /className="pi-controls"/);
|
||||
assert.match(source, /aria-label="Model and thinking controls"/);
|
||||
assert.match(source, /const uiScaleKey/);
|
||||
assert.match(source, /function clampUiScale/);
|
||||
assert.match(source, /function updateUiScale/);
|
||||
assert.match(source, /localStorage\.setItem\(uiScaleKey/);
|
||||
assert.match(source, /className="scale-settings"/);
|
||||
assert.match(source, /aria-label="Interface scale"/);
|
||||
assert.match(source, /aria-label="Decrease interface scale"/);
|
||||
assert.match(source, /aria-label="Increase interface scale"/);
|
||||
assert.match(source, /type="range"/);
|
||||
assert.match(source, /style=\{\{ zoom: uiScale \}\}/);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/className="pi-controls"[\s\S]*aria-label="Interface scale"/,
|
||||
);
|
||||
assert.doesNotMatch(source, /<details className="pi-controls">/);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/event\.ctrlKey && event\.key\.toLowerCase\(\) === "c"/,
|
||||
);
|
||||
assert.match(source, /getCurrentWindow\(\)\s*\.hide\(\)\s*\.catch/);
|
||||
assert.match(
|
||||
source,
|
||||
/window\.addEventListener\("keydown", handleKeydown, true\)/,
|
||||
);
|
||||
assert.match(source, /useLayoutEffect/);
|
||||
assert.match(source, /transcript\.scrollTop = transcript\.scrollHeight/);
|
||||
assert.match(source, /requestAnimationFrame\(scrollTranscriptToLatest\)/);
|
||||
assert.match(source, /const loadGeneration = useRef\(0\)/);
|
||||
assert.match(
|
||||
source,
|
||||
/const bridgeRetryTimer = useRef<number \| undefined>\(undefined\)/,
|
||||
);
|
||||
assert.match(source, /const bridgeRetryAttempt = useRef\(0\)/);
|
||||
assert.match(source, /function scheduleBridgeReconnect/);
|
||||
assert.match(source, /Bridge unavailable; retrying in/);
|
||||
assert.match(
|
||||
source,
|
||||
/setStatus\(String\(error\)\);\s*scheduleBridgeReconnect\(\);/,
|
||||
);
|
||||
assert.match(source, /bridgeRetryTimer\.current = window\.setTimeout/);
|
||||
assert.match(source, /void refreshAgents\(preferredPath\)/);
|
||||
assert.match(source, /bridgeRetryAttempt\.current = 0/);
|
||||
assert.match(
|
||||
source,
|
||||
/const lastEventSequence = useRef<Record<string, number>>/,
|
||||
);
|
||||
assert.match(source, /const eventReloadInFlight = useRef\(false\)/);
|
||||
assert.match(source, /const queuedEventReload = useRef<string \| undefined>/);
|
||||
assert.match(source, /function reloadAgentAfterEvent\(agentId: string\)/);
|
||||
assert.match(source, /void reloadAgentAfterEvent\(selectedId\)/);
|
||||
assert.match(source, /let disposed = false;/);
|
||||
assert.match(source, /if \(disposed\) stop\(\);/);
|
||||
assert.match(source, /generation !== loadGeneration\.current/);
|
||||
assert.match(source, /current\.phase !== "working"/);
|
||||
assert.match(source, /bridgeEvent\.seq <= \(lastEventSequence\.current/);
|
||||
assert.match(source, /\.toFixed\(1\)/);
|
||||
assert.match(source, /formatTokens\(stats\.tokens\.input\)/);
|
||||
assert.match(source, /formatTokens\(stats\.tokens\.output\)/);
|
||||
assert.match(source, /formatTokens\(cacheTokens\)/);
|
||||
assert.match(source, /function updateWorkProgress/);
|
||||
assert.match(
|
||||
source,
|
||||
/className=\{`work-progress composer-progress \$\{workProgress\.phase\}`\}/,
|
||||
);
|
||||
assert.match(source, /event\.type === "tool"/);
|
||||
assert.match(source, /event\.type === "queue"/);
|
||||
assert.match(source, /composer-progress/);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/<section className=\{`work-progress \$\{workProgress\.phase\}`\}/,
|
||||
);
|
||||
const sources = Promise.all([
|
||||
readFile("ui/src/App.tsx", "utf8"),
|
||||
readFile("ui/src/workspace/reducer.ts", "utf8"),
|
||||
readFile("ui/src/workspace/useWorkspace.ts", "utf8"),
|
||||
readFile("ui/src/workspace/persistence.ts", "utf8"),
|
||||
readFile("ui/src/App.css", "utf8"),
|
||||
]);
|
||||
|
||||
test("wires the desktop to stable runtime workspace APIs", async () => {
|
||||
const [app, reducer, hook] = await sources;
|
||||
for (const operation of [
|
||||
"create_session_runtime",
|
||||
"open_session_runtime",
|
||||
"close_session_runtime",
|
||||
"list_directory_sessions",
|
||||
"get_session_runtime_snapshot",
|
||||
"subscribe_workspace",
|
||||
])
|
||||
assert.match(`${app}\n${hook}`, new RegExp(operation));
|
||||
assert.doesNotMatch(app, /switch_session/);
|
||||
assert.doesNotMatch(app, /new_session/);
|
||||
assert.match(reducer, /WorkspaceState\["runtimesById"\]/);
|
||||
assert.match(reducer, /requestGeneration/);
|
||||
assert.match(reducer, /loadedThroughSeq/);
|
||||
});
|
||||
|
||||
test("exposes accessible directory, session, notification, and extension controls", async () => {
|
||||
const [component, styles] = await Promise.all([
|
||||
readFile("ui/src/App.tsx", "utf8"),
|
||||
readFile("ui/src/App.css", "utf8"),
|
||||
test("keeps presentation persistence allowlisted and agent IDs ephemeral", async () => {
|
||||
const [, , , persistence] = await sources;
|
||||
assert.match(persistence, /load_ui_state/);
|
||||
assert.match(persistence, /save_ui_state/);
|
||||
assert.match(persistence, /directoryOrder/);
|
||||
assert.match(persistence, /drafts/);
|
||||
assert.doesNotMatch(persistence, /agentId/);
|
||||
assert.doesNotMatch(persistence, /messages/);
|
||||
assert.doesNotMatch(persistence, /extensions/);
|
||||
});
|
||||
|
||||
test("renders directory sidebar, accessible session tabs, warnings, and reduced motion", async () => {
|
||||
const [app, , , , css] = await sources;
|
||||
const [sidebar, tabs] = await Promise.all([
|
||||
readFile("ui/src/components/DirectorySidebar.tsx", "utf8"),
|
||||
readFile("ui/src/components/SessionTabs.tsx", "utf8"),
|
||||
]);
|
||||
assert.match(component, /aria-expanded=\{view === "settings"\}/);
|
||||
assert.match(
|
||||
component,
|
||||
/aria-label="Managed directories"\s+className="directory-tab-list"\s+role="tablist"/,
|
||||
);
|
||||
assert.match(component, /role="tab"/);
|
||||
assert.match(
|
||||
component,
|
||||
/aria-selected=\{directory\.agentId === selectedId\}/,
|
||||
);
|
||||
assert.match(component, /aria-controls="directory-workspace"/);
|
||||
assert.match(component, /onKeyDown=\{\(event\) => handleDirectoryTabKeyDown/);
|
||||
assert.match(
|
||||
component,
|
||||
/aria-current=\{session\.isCurrent \? "page" : undefined\}/,
|
||||
);
|
||||
assert.match(component, /role="status" aria-atomic="true"/);
|
||||
assert.match(component, /aria-hidden="true"/);
|
||||
assert.match(
|
||||
component,
|
||||
/role=\{notice\.notifyType === "error" \? "alert" : "status"\}/,
|
||||
);
|
||||
assert.match(component, /role="dialog"/);
|
||||
assert.match(component, /aria-modal="true"/);
|
||||
assert.match(component, /aria-labelledby="extension-title"/);
|
||||
assert.match(component, /const extensionRef = useRef<HTMLElement>\(null\)/);
|
||||
assert.match(component, /function focusableExtensionControls/);
|
||||
assert.match(component, /event\.key !== "Tab"/);
|
||||
assert.match(component, /data-initial-focus/);
|
||||
assert.match(component, /className="extension-backdrop"/);
|
||||
assert.match(component, /htmlFor="extension-value"/);
|
||||
assert.match(component, /Close current session and start a new session/);
|
||||
assert.match(component, /Restart Pi for the selected directory/);
|
||||
assert.match(component, /window\.confirm\(/);
|
||||
assert.match(styles, /button:focus-visible/);
|
||||
assert.match(styles, /\*::before,\n\*::after/);
|
||||
assert.match(styles, /\.sr-only/);
|
||||
assert.match(styles, /\.extension-backdrop \{/);
|
||||
assert.match(styles, /animation-duration: 0\.01ms/);
|
||||
assert.match(sidebar, /aria-label="Directories"/);
|
||||
assert.match(tabs, /role="tablist"/);
|
||||
assert.match(tabs, /role="tab"/);
|
||||
assert.match(tabs, /aria-selected/);
|
||||
assert.match(tabs, /ArrowRight/);
|
||||
assert.match(app, /Multiple sessions are open in the same directory/);
|
||||
assert.match(app, /Many Pi sessions are open/);
|
||||
assert.match(css, /prefers-reduced-motion/);
|
||||
assert.match(css, /grid-template-columns:\s*178px minmax\(0, 1fr\)/);
|
||||
});
|
||||
|
||||
test("makes the window draggable from the non-interactive header", async () => {
|
||||
const [component, styles, capability] = await Promise.all([
|
||||
readFile("ui/src/App.tsx", "utf8"),
|
||||
readFile("ui/src/App.css", "utf8"),
|
||||
readFile("ui/src-tauri/capabilities/default.json", "utf8"),
|
||||
test("preserves native session naming, compaction, composer, and extension responses", async () => {
|
||||
const [app] = await sources;
|
||||
const [conversation, extension] = await Promise.all([
|
||||
readFile("ui/src/components/ConversationWorkspace.tsx", "utf8"),
|
||||
readFile("ui/src/components/ExtensionDialog.tsx", "utf8"),
|
||||
]);
|
||||
assert.match(component, /startDragging\(\)/);
|
||||
assert.match(
|
||||
component,
|
||||
/closest\(\s*"button, input, select, textarea",?\s*\)/,
|
||||
);
|
||||
assert.match(styles, /\.workflow-header \{[\s\S]*cursor: grab/);
|
||||
assert.match(capability, /core:window:allow-start-dragging/);
|
||||
assert.match(app, /set_session_name/);
|
||||
assert.match(app, /compact/);
|
||||
assert.match(app, /submit_prompt/);
|
||||
assert.match(app, /respond_to_extension/);
|
||||
assert.match(conversation, /event\.key === "Enter" && !event\.shiftKey/);
|
||||
assert.match(conversation, /className="todos-pane"/);
|
||||
assert.match(extension, /role="dialog"/);
|
||||
assert.match(extension, /aria-modal="true"/);
|
||||
});
|
||||
|
||||
test("opens local follow-up choices for /model and /resume", async () => {
|
||||
const [component, styles] = await Promise.all([
|
||||
readFile("ui/src/App.tsx", "utf8"),
|
||||
readFile("ui/src/App.css", "utf8"),
|
||||
]);
|
||||
assert.match(component, /const controlCommands: Command\[\]/);
|
||||
assert.match(component, /followUp: "model"/);
|
||||
assert.match(component, /followUp: "resume"/);
|
||||
assert.match(component, /followUp: "thinking"/);
|
||||
assert.match(component, /const nativeCommand = controlCommands\.find/);
|
||||
assert.match(component, /function chooseThinking/);
|
||||
assert.match(component, /function openCommandFollowUp/);
|
||||
assert.match(component, /function chooseModel/);
|
||||
assert.match(component, /className="command-followup"/);
|
||||
assert.match(component, /New session/);
|
||||
assert.match(styles, /\.command-followup \{/);
|
||||
assert.match(styles, /\.command-choice-list \{/);
|
||||
});
|
||||
|
||||
test("shows bridge-managed directory status and sessions for the opened directory", async () => {
|
||||
const [component, styles] = await Promise.all([
|
||||
readFile("ui/src/App.tsx", "utf8"),
|
||||
readFile("ui/src/App.css", "utf8"),
|
||||
]);
|
||||
assert.match(
|
||||
component,
|
||||
/invoke<\{ directories: Directory\[\] \}>\("list_directories"\)/,
|
||||
);
|
||||
assert.match(
|
||||
component,
|
||||
/invoke<\{ sessions: Session\[\] \}>\("list_sessions"/,
|
||||
);
|
||||
assert.match(component, /function chooseDirectory/);
|
||||
assert.match(component, /function handleDirectoryTabKeyDown/);
|
||||
assert.match(component, /function forgetDirectory/);
|
||||
assert.match(component, /window\.confirm/);
|
||||
assert.match(component, /"forget_directory"/);
|
||||
assert.match(component, /bridgeEvent\.type === "agent_state"/);
|
||||
assert.match(component, /setDirectories\(\(current\) =>/);
|
||||
assert.match(
|
||||
component,
|
||||
/state: loadedState\.isStreaming \? "streaming" : "idle"/,
|
||||
);
|
||||
assert.match(component, /className="directory-add"/);
|
||||
assert.match(
|
||||
component,
|
||||
/import \{ open \} from "@tauri-apps\/plugin-dialog"/,
|
||||
);
|
||||
assert.match(component, /function browseForFolder/);
|
||||
assert.match(component, /directory: true/);
|
||||
assert.match(component, /multiple: false/);
|
||||
assert.match(component, /Browse…/);
|
||||
assert.match(component, /const \[isAddingDirectory, setIsAddingDirectory\]/);
|
||||
assert.match(component, /aria-busy=\{isAddingDirectory\}/);
|
||||
assert.match(component, /className="directory-add-status" role="status"/);
|
||||
assert.match(component, /pendingDirectoryPath/);
|
||||
assert.match(component, /Added \$\{worktreeLabel\(pendingDirectoryPath\)\}/);
|
||||
assert.match(component, /Add directory/);
|
||||
assert.doesNotMatch(component, /className="folder-form"/);
|
||||
assert.doesNotMatch(component, /Remembered folders/);
|
||||
assert.match(component, /function startNewSession/);
|
||||
assert.match(component, /function closeCurrentSession/);
|
||||
assert.match(component, /Use \/resume to reopen it/);
|
||||
assert.match(component, /function switchSession/);
|
||||
assert.match(component, /className="session-panel"/);
|
||||
assert.match(component, /New session/);
|
||||
assert.match(component, /state\.isStreaming/);
|
||||
assert.match(styles, /\.directory-add \{/);
|
||||
assert.match(styles, /\.directory-add-status \{/);
|
||||
assert.match(styles, /\.directory-add-spinner \{/);
|
||||
assert.match(styles, /directory-add-spin/);
|
||||
assert.match(styles, /\.directory-tab \{/);
|
||||
assert.match(styles, /\.directory-tab-button \{/);
|
||||
assert.match(styles, /\.directory-close \{/);
|
||||
assert.match(styles, /\.session-panel \{/);
|
||||
assert.match(styles, /\.session-list \{/);
|
||||
});
|
||||
|
||||
test("renders todo-plugin state and compact context/token status", async () => {
|
||||
const [component, styles] = await Promise.all([
|
||||
readFile("ui/src/App.tsx", "utf8"),
|
||||
readFile("ui/src/App.css", "utf8"),
|
||||
]);
|
||||
assert.match(component, /function currentTodos/);
|
||||
assert.match(component, /message\.toolName === "todo"/);
|
||||
assert.match(component, /className="todos-pane"/);
|
||||
assert.match(component, /className="workflow-main"/);
|
||||
assert.match(component, /ctx \{formatTokens\(contextTokens\)\}/);
|
||||
assert.match(component, /tok \{formatTokens\(stats\.tokens\.total\)\}/);
|
||||
assert.match(styles, /\.workflow-main \{/);
|
||||
assert.match(styles, /\.todos-pane \{/);
|
||||
assert.match(styles, /\.status-metric \{/);
|
||||
});
|
||||
|
||||
test("shows an optimistic prompt until the transcript receives it", async () => {
|
||||
const [component, styles] = await Promise.all([
|
||||
readFile("ui/src/App.tsx", "utf8"),
|
||||
readFile("ui/src/App.css", "utf8"),
|
||||
]);
|
||||
assert.match(component, /type PendingSubmission/);
|
||||
assert.match(
|
||||
component,
|
||||
/const \[pendingSubmissions, setPendingSubmissions\]/,
|
||||
);
|
||||
assert.match(component, /setStatus\("Sending prompt to Pi…"\)/);
|
||||
assert.match(component, /phase: "sending"/);
|
||||
assert.match(component, /phase: "sent"/);
|
||||
assert.match(component, /Sent · waiting for Pi/);
|
||||
assert.match(component, /receivedMessages\.some/);
|
||||
assert.match(component, /pending-message/);
|
||||
assert.match(styles, /\.pending-message \{/);
|
||||
assert.match(styles, /\.pending-message\.sending/);
|
||||
});
|
||||
|
||||
test("offers a lower-center control when the conversation is above its latest message", async () => {
|
||||
const [component, styles] = await Promise.all([
|
||||
readFile("ui/src/App.tsx", "utf8"),
|
||||
readFile("ui/src/App.css", "utf8"),
|
||||
]);
|
||||
assert.match(
|
||||
component,
|
||||
/const \[isTranscriptAtBottom, setIsTranscriptAtBottom\]/,
|
||||
);
|
||||
assert.match(component, /function updateTranscriptScrollPosition/);
|
||||
assert.match(component, /function scrollTranscriptToLatest/);
|
||||
assert.match(component, /onScroll=\{updateTranscriptScrollPosition\}/);
|
||||
assert.match(component, /!isTranscriptAtBottom/);
|
||||
assert.match(component, /aria-label="Scroll to latest message"/);
|
||||
assert.match(component, /className="scroll-to-latest"/);
|
||||
assert.match(component, /scrollTranscriptToLatest\(\);/);
|
||||
assert.match(styles, /\.transcript-container \{/);
|
||||
assert.match(styles, /\.scroll-to-latest \{[\s\S]*left: 50%/);
|
||||
assert.match(styles, /\.scroll-to-latest \{[\s\S]*bottom: 14px/);
|
||||
assert.match(styles, /\.scroll-to-latest \{[\s\S]*border-radius: 50%/);
|
||||
});
|
||||
|
||||
test("distinguishes user and assistant messages", async () => {
|
||||
const styles = await readFile("ui/src/App.css", "utf8");
|
||||
assert.match(styles, /\.message\.user \{/);
|
||||
assert.match(styles, /background: #192536/);
|
||||
assert.match(styles, /\.message\.assistant \{/);
|
||||
assert.match(styles, /background: #1a241a/);
|
||||
});
|
||||
|
||||
test("keeps an accessible focus treatment for the prompt editor", async () => {
|
||||
const styles = await readFile("ui/src/App.css", "utf8");
|
||||
assert.match(styles, /\.composer textarea:focus \{/);
|
||||
assert.doesNotMatch(styles, /outline: none/);
|
||||
assert.match(styles, /border-color: #6f8b56/);
|
||||
assert.match(
|
||||
styles,
|
||||
/\.composer textarea:focus \{[^}]*outline: 2px solid #c4ed8b/,
|
||||
);
|
||||
assert.match(styles, /\.composer textarea:focus \{[^}]*outline-offset: -2px/);
|
||||
assert.doesNotMatch(styles, /\.composer textarea:focus \{[^}]*box-shadow:/);
|
||||
assert.match(styles, /textarea:focus-visible/);
|
||||
});
|
||||
|
||||
test("submits the composer with Enter and preserves Shift+Enter for newlines", async () => {
|
||||
const source = await readFile("ui/src/App.tsx", "utf8");
|
||||
assert.match(source, /onKeyDown=\{\(event\) => \{/);
|
||||
assert.match(source, /event\.key !== "Enter"/);
|
||||
assert.match(source, /event\.shiftKey/);
|
||||
assert.match(source, /event\.nativeEvent\.isComposing/);
|
||||
assert.match(source, /event\.preventDefault\(\);\s*void submit\(\);/);
|
||||
assert.match(source, /Enter to send · Shift\+Enter for newline/);
|
||||
});
|
||||
|
||||
test("uses a compact workflow layout that keeps the composer and transcript stable", async () => {
|
||||
const [component, styles] = await Promise.all([
|
||||
readFile("ui/src/App.tsx", "utf8"),
|
||||
readFile("ui/src/App.css", "utf8"),
|
||||
]);
|
||||
assert.match(component, /className="workflow-header"/);
|
||||
assert.match(component, /className="workflow"/);
|
||||
assert.match(component, /className="workflow-footer"/);
|
||||
assert.match(component, /className="composer-stack"/);
|
||||
assert.match(
|
||||
styles,
|
||||
/\.workflow \{[\s\S]*grid-template-rows: minmax\(0, 1fr\) auto/,
|
||||
);
|
||||
assert.match(styles, /\.transcript,\s*\.settings \{[\s\S]*overflow: auto/);
|
||||
assert.match(
|
||||
component,
|
||||
/className=\{`transcript \$\{pendingExtension \? "with-extension" : ""\} \$\{workProgress\.phase\}`\}/,
|
||||
);
|
||||
assert.match(
|
||||
styles,
|
||||
/\.transcript\.with-extension \{[\s\S]*padding-bottom: 112px/,
|
||||
);
|
||||
assert.match(styles, /\.transcript\.working \{/);
|
||||
assert.match(styles, /conic-gradient\(/);
|
||||
assert.match(styles, /transcript-border-orbit/);
|
||||
assert.match(styles, /prefers-reduced-motion: reduce/);
|
||||
assert.match(styles, /font-size: 18px/);
|
||||
assert.match(styles, /\.directory-tab-button \{[\s\S]*font-size: 13px/);
|
||||
assert.match(styles, /\.message pre \{[\s\S]*font-size: 14px/);
|
||||
assert.match(
|
||||
styles,
|
||||
/\.workflow-main \{[\s\S]*grid-template-columns: minmax\(0, 1fr\) 250px/,
|
||||
);
|
||||
assert.match(
|
||||
styles,
|
||||
/\.pi-controls \{[\s\S]*grid-template-columns: repeat\(2, minmax\(0, 1fr\)\)/,
|
||||
);
|
||||
assert.match(styles, /\.scale-settings \{/);
|
||||
assert.match(
|
||||
styles,
|
||||
/\.scale-adjustment \{[\s\S]*grid-template-columns: auto minmax\(0, 1fr\) auto/,
|
||||
);
|
||||
assert.match(styles, /\.scale-adjustment input\[type="range"\] \{/);
|
||||
assert.match(styles, /\.pi-controls \{[\s\S]*padding: 4px 6px/);
|
||||
});
|
||||
|
||||
test("configures a larger desktop window and enables the folder dialog", async () => {
|
||||
const [config, capability, rust] = await Promise.all([
|
||||
test("configures native window behavior and folder dialog capabilities", async () => {
|
||||
const [config, capability, cargo] = await Promise.all([
|
||||
readFile("ui/src-tauri/tauri.conf.json", "utf8"),
|
||||
readFile("ui/src-tauri/capabilities/default.json", "utf8"),
|
||||
readFile("ui/src-tauri/src/lib.rs", "utf8"),
|
||||
readFile("ui/src-tauri/Cargo.toml", "utf8"),
|
||||
]);
|
||||
assert.match(config, /"width": 1100/);
|
||||
assert.match(config, /"height": 840/);
|
||||
assert.match(config, /"minWidth": 640/);
|
||||
assert.match(config, /"minHeight": 520/);
|
||||
assert.match(capability, /"dialog:default"/);
|
||||
assert.match(rust, /\.plugin\(tauri_plugin_dialog::init\(\)\)/);
|
||||
assert.match(config, /"width"\s*:\s*1100/);
|
||||
assert.match(capability, /core:window:allow-start-dragging/);
|
||||
assert.match(cargo, /tauri-plugin-single-instance/);
|
||||
assert.match(cargo, /tauri-plugin-dialog/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user