merge: reconcile published native prerequisite and current master

Preserve canonical startup ownership, scoped evidence, current Storybook IDs, and the Codex form controls while carrying Claude effort and immutable source bindings.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 20:40:11 -05:00
commit 48ffca9438
415 files changed
+22501 -15340

No files matched your search

+201 -444
View File
@@ -266,6 +266,46 @@ function setInputValue(input: HTMLInputElement, value: string) {
input.dispatchEvent(new Event("input", { bubbles: true }));
}
function daytonaEnvironments() {
return [
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
];
}
function e2bEnvironments() {
return [
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "E2B",
driver: "sandbox",
config: { provider: "e2b" },
}),
];
}
function renderFormView(root: Root, queryClient: QueryClient, children: Parameters<Root["render"]>[0]) {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>{children}</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
);
}
function createFormQueryClient() {
return new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
}
async function renderForm(
environments: Environment[],
agentOverrides: Partial<Agent> = {},
@@ -286,35 +326,24 @@ async function renderForm(
document.body.appendChild(container);
const root = createRoot(container);
const onSave = vi.fn();
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AgentConfigForm
mode="edit"
agent={makeAgent(agentOverrides)}
onSave={onSave}
hidePromptTemplate
content={options.content}
environmentVariablesPlacement={options.environmentVariablesPlacement}
hideInlineSave={options.hideInlineSave}
onDirtyChange={options.onDirtyChange}
onSaveActionChange={options.onSaveActionChange}
onCancelActionChange={options.onCancelActionChange}
showAdapterTypeField={options.showAdapterTypeField ?? false}
showAdapterTestEnvironmentButton={options.showAdapterTestEnvironmentButton ?? false}
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AgentConfigForm
mode="edit"
agent={makeAgent(agentOverrides)}
onSave={onSave}
hidePromptTemplate
content={options.content}
environmentVariablesPlacement={options.environmentVariablesPlacement}
hideInlineSave={options.hideInlineSave}
onDirtyChange={options.onDirtyChange}
onSaveActionChange={options.onSaveActionChange}
onCancelActionChange={options.onCancelActionChange}
showAdapterTypeField={options.showAdapterTypeField ?? false}
showAdapterTestEnvironmentButton={options.showAdapterTestEnvironmentButton ?? false}
/>
);
});
@@ -332,12 +361,7 @@ async function renderCreateForm(
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
const queryClient = createFormQueryClient();
const values = {
...defaultCreateValues,
@@ -347,21 +371,15 @@ async function renderCreateForm(
const onChange = vi.fn();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AgentConfigForm
mode="create"
values={values}
onChange={onChange}
hidePromptTemplate
showAdapterTypeField={false}
showAdapterTestEnvironmentButton={options.showAdapterTestEnvironmentButton ?? false}
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AgentConfigForm
mode="create"
values={values}
onChange={onChange}
hidePromptTemplate
showAdapterTypeField={false}
showAdapterTestEnvironmentButton={options.showAdapterTestEnvironmentButton ?? false}
/>
);
});
@@ -466,15 +484,7 @@ function findByAriaLabel(container: HTMLElement, label: string) {
async function renderCodexSandbox(agentOverrides: Partial<Agent> = {}) {
return renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
],
daytonaEnvironments(),
{ defaultEnvironmentId: "sandbox-1", ...agentOverrides },
{ showAdapterTestEnvironmentButton: true },
);
@@ -485,15 +495,7 @@ async function renderCodexSandbox(agentOverrides: Partial<Agent> = {}) {
// provider advertises the login pseudo-terminal capability the login needs.
async function renderVendorSandbox(agentOverrides: Partial<Agent> = {}) {
return renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
],
daytonaEnvironments(),
{ adapterType: "vendor_local", defaultEnvironmentId: "sandbox-1", ...agentOverrides },
{ showAdapterTestEnvironmentButton: true },
);
@@ -505,15 +507,7 @@ async function renderVendorSandbox(agentOverrides: Partial<Agent> = {}) {
// login needs.
async function renderGrokSandbox(agentOverrides: Partial<Agent> = {}) {
return renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
],
daytonaEnvironments(),
{ adapterType: "grok_local", defaultEnvironmentId: "sandbox-1", ...agentOverrides },
{ showAdapterTestEnvironmentButton: true },
);
@@ -521,15 +515,7 @@ async function renderGrokSandbox(agentOverrides: Partial<Agent> = {}) {
async function renderClaudeSandbox(agentOverrides: Partial<Agent> = {}) {
return renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
],
daytonaEnvironments(),
{ adapterType: "claude_local", defaultEnvironmentId: "sandbox-1", ...agentOverrides },
{ showAdapterTestEnvironmentButton: true },
);
@@ -539,15 +525,7 @@ async function renderCreateClaudeSandbox(
valueOverrides: Partial<typeof defaultCreateValues> = {},
) {
return renderCreateForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
],
daytonaEnvironments(),
{ adapterType: "claude_local", defaultEnvironmentId: "sandbox-1", ...valueOverrides },
{ showAdapterTestEnvironmentButton: true },
);
@@ -565,9 +543,7 @@ async function renderStatefulCreateClaudeSandbox(environments: Environment[]) {
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
const valuesRef: { current: typeof defaultCreateValues } = {
current: {
@@ -593,15 +569,7 @@ async function renderStatefulCreateClaudeSandbox(environments: Environment[]) {
}
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<Harness />
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
);
renderFormView(root, queryClient, <Harness />);
});
await flushReact();
@@ -1057,15 +1025,7 @@ describe("AgentConfigForm environment selector", () => {
});
it("shows concise Environment copy when one runnable non-local environment exists", async () => {
const result = await renderForm([
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "E2B",
driver: "sandbox",
config: { provider: "e2b" },
}),
]);
const result = await renderForm(e2bEnvironments());
roots.push(result.root);
const text = result.container.textContent ?? "";
@@ -1082,15 +1042,7 @@ describe("AgentConfigForm environment selector", () => {
it("shows the environment override for Grok local agents", async () => {
const result = await renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "E2B",
driver: "sandbox",
config: { provider: "e2b" },
}),
],
e2bEnvironments(),
{ adapterType: "grok_local" },
);
roots.push(result.root);
@@ -1104,15 +1056,7 @@ describe("AgentConfigForm environment selector", () => {
it("shows the environment override for Kimi local agents", async () => {
const result = await renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "E2B",
driver: "sandbox",
config: { provider: "e2b" },
}),
],
e2bEnvironments(),
{ adapterType: "kimi_local" },
);
roots.push(result.root);
@@ -1405,15 +1349,7 @@ describe("AgentConfigForm environment selector", () => {
// no capability, so the panel stays hidden even after the auth-missing check.
mockAgentsApi.testEnvironment.mockResolvedValue(AUTH_MISSING_RESULT);
const result = await renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "E2B",
driver: "sandbox",
config: { provider: "e2b" },
}),
],
e2bEnvironments(),
{ adapterType: "codex_local", defaultEnvironmentId: "sandbox-1" },
{ showAdapterTestEnvironmentButton: true },
);
@@ -1484,15 +1420,7 @@ describe("AgentConfigForm environment selector", () => {
it("hides the Login button for a Claude sandbox whose provider lacks the setup-token login capability", async () => {
mockAgentsApi.testEnvironment.mockResolvedValue(CLAUDE_AUTH_MISSING_RESULT);
const result = await renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "E2B",
driver: "sandbox",
config: { provider: "e2b" },
}),
],
e2bEnvironments(),
{ adapterType: "claude_local", defaultEnvironmentId: "sandbox-1" },
{ showAdapterTestEnvironmentButton: true },
);
@@ -1521,15 +1449,7 @@ describe("AgentConfigForm environment selector", () => {
}),
);
const result = await renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
],
daytonaEnvironments(),
{ adapterType: "claude_local", defaultEnvironmentId: "sandbox-1" },
{ showAdapterTestEnvironmentButton: true },
);
@@ -1547,15 +1467,7 @@ describe("AgentConfigForm environment selector", () => {
// after the auth-missing check.
mockAgentsApi.testEnvironment.mockResolvedValue(PTY_VENDOR_AUTH_MISSING_RESULT);
const result = await renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "E2B",
driver: "sandbox",
config: { provider: "e2b" },
}),
],
e2bEnvironments(),
{ adapterType: "pty_vendor_local", defaultEnvironmentId: "sandbox-1" },
{ showAdapterTestEnvironmentButton: true },
);
@@ -1572,15 +1484,7 @@ describe("AgentConfigForm environment selector", () => {
// provider capability, not the adapter name.
mockAgentsApi.testEnvironment.mockResolvedValue(PTY_VENDOR_AUTH_MISSING_RESULT);
const result = await renderForm(
[
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
],
daytonaEnvironments(),
{ adapterType: "pty_vendor_local", defaultEnvironmentId: "sandbox-1" },
{ showAdapterTestEnvironmentButton: true },
);
@@ -1597,23 +1501,13 @@ describe("AgentConfigForm environment selector", () => {
// lifts the feedback and renders `AdapterLoginPanel` from the lifted login
// descriptor. Without the descriptor the Login button never appears.
mockAgentsApi.testEnvironment.mockResolvedValue(AUTH_MISSING_RESULT);
mockEnvironmentsApi.list.mockResolvedValue([
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
]);
mockEnvironmentsApi.list.mockResolvedValue(daytonaEnvironments());
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
function LiftedFeedbackHarness() {
const [login, setLogin] = useState<AdapterLoginDescriptor | null>(null);
@@ -1644,15 +1538,7 @@ describe("AgentConfigForm environment selector", () => {
}
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<LiftedFeedbackHarness />
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
);
renderFormView(root, queryClient, <LiftedFeedbackHarness />);
});
await flushReact();
@@ -1917,12 +1803,12 @@ describe("AgentConfigForm environment selector", () => {
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container); roots.push(root);
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } });
const queryClient = createFormQueryClient();
await act(async () => {
root.render(<QueryClientProvider client={queryClient}><ToastProvider><TooltipProvider>
renderFormView(root, queryClient,
<AdapterLoginPanel companyId="company-1" adapterType="grok_local" environmentId="new-sandbox"
aiConnection={{ ...intent, agentIds: [] }} chrome="onboarding" autoStart />
</TooltipProvider></ToastProvider></QueryClientProvider>);
aiConnection={{ ...intent, agentIds: [] }} chrome="onboarding" autoStart />
);
});
await flushUntil(() => Boolean(findButton(container, "Cancel previous sign-in and retry")));
expect(mockAgentsApi.startAdapterAuthLogin).not.toHaveBeenCalled();
@@ -1954,25 +1840,17 @@ describe("AgentConfigForm environment selector", () => {
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
chrome="onboarding"
autoStart
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
chrome="onboarding"
autoStart
/>
);
});
// Wait for the card itself, then assert it is actually there: an absence
@@ -2009,25 +1887,17 @@ describe("AgentConfigForm environment selector", () => {
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
autoStart
onAccountBinding={onAccountBinding}
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
autoStart
onAccountBinding={onAccountBinding}
/>
);
});
await flushUntil(() => onAccountBinding.mock.calls.length > 0);
@@ -2040,6 +1910,7 @@ describe("AgentConfigForm environment selector", () => {
// The panel narrates the bind as its own state, separate from the login's
// success line — the bind is a second save that can still fail.
await flushUntil(() => container.textContent?.includes("Agent bound to the signed-in account") ?? false);
expect(container.textContent).toContain("Agent bound to the signed-in account");
});
it("a failed bind save renders an explicit Retry instead of silently latching the claim", async () => {
@@ -2071,25 +1942,17 @@ describe("AgentConfigForm environment selector", () => {
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
autoStart
onAccountBinding={onAccountBinding}
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
autoStart
onAccountBinding={onAccountBinding}
/>
);
});
await flushUntil(() => container.textContent?.includes("Could not bind this agent") ?? false);
@@ -2147,25 +2010,17 @@ describe("AgentConfigForm environment selector", () => {
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
autoStart
onAccountBinding={onAccountBinding}
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
autoStart
onAccountBinding={onAccountBinding}
/>
);
});
await flushUntil(() => onAccountBinding.mock.calls.length === 1);
@@ -2219,25 +2074,17 @@ describe("AgentConfigForm environment selector", () => {
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
autoStart
onAccountBinding={onAccountBinding}
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AdapterLoginPanel
companyId="company-1"
adapterType="codex_local"
environmentId="sandbox-1"
autoStart
onAccountBinding={onAccountBinding}
/>
);
});
await flushUntil(() => container.textContent?.includes("Binding this agent") ?? false);
@@ -2270,7 +2117,7 @@ describe("AgentConfigForm environment selector", () => {
<AgentConfigForm mode="edit" agent={saved} onSave={onSave} hidePromptTemplate showAdapterTypeField={false} />
</>;
}
await act(async () => root.render(<QueryClientProvider client={queryClient}><ToastProvider><TooltipProvider><Harness /></TooltipProvider></ToastProvider></QueryClientProvider>));
await act(async () => renderFormView(root, queryClient, <Harness />));
await flushReact();
const nameInput = container.querySelector<HTMLInputElement>('input[placeholder="Agent name"]')!;
setInputValue(nameInput, "Unsaved name");
@@ -2322,23 +2169,13 @@ describe("AgentConfigForm environment selector", () => {
releaseSaves.push(resolve);
}),
);
mockEnvironmentsApi.list.mockResolvedValue([
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
]);
mockEnvironmentsApi.list.mockResolvedValue(daytonaEnvironments());
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
// The harness swaps the agent object the way the page does after a save
// refresh: same mounted form, new agent identity.
@@ -2358,15 +2195,7 @@ describe("AgentConfigForm environment selector", () => {
);
}
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<RefreshHarness />
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
);
renderFormView(root, queryClient, <RefreshHarness />);
});
await flushReact();
@@ -2873,15 +2702,7 @@ describe("AgentConfigForm environment selector", () => {
.mockResolvedValueOnce(staleList)
.mockResolvedValue(freshList);
const result = await renderStatefulCreateClaudeSandbox([
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
]);
const result = await renderStatefulCreateClaudeSandbox(daytonaEnvironments());
roots.push(result.root);
await runTest(result.container);
@@ -2921,24 +2742,16 @@ describe("AgentConfigForm environment selector", () => {
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AdapterLoginPanel
companyId="company-1"
adapterType="claude_local"
environmentId="sandbox-1"
onStored={onStored}
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AdapterLoginPanel
companyId="company-1"
adapterType="claude_local"
environmentId="sandbox-1"
onStored={onStored}
/>
);
});
await startLogin(container);
@@ -2958,25 +2771,17 @@ describe("AgentConfigForm environment selector", () => {
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AdapterLoginPanel
companyId="company-1"
adapterType="claude_local"
environmentId="sandbox-1"
chrome="onboarding"
autoStart
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AdapterLoginPanel
companyId="company-1"
adapterType="claude_local"
environmentId="sandbox-1"
chrome="onboarding"
autoStart
/>
);
});
// Wait for the card itself, then assert it is actually there: an absence
@@ -2998,24 +2803,16 @@ describe("AgentConfigForm environment selector", () => {
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AdapterLoginPanel
companyId="company-1"
adapterType="claude_local"
environmentId="sandbox-1"
onApplyStored={onApplyStored}
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AdapterLoginPanel
companyId="company-1"
adapterType="claude_local"
environmentId="sandbox-1"
onApplyStored={onApplyStored}
/>
);
});
await flushUntil(() => Boolean(findButton(container, "Use saved login")));
@@ -3040,24 +2837,16 @@ describe("AgentConfigForm environment selector", () => {
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AdapterLoginPanel
companyId="company-1"
adapterType="claude_local"
environmentId="sandbox-1"
onApplyStored={onApplyStored}
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AdapterLoginPanel
companyId="company-1"
adapterType="claude_local"
environmentId="sandbox-1"
onApplyStored={onApplyStored}
/>
);
});
await flushUntil(() => Boolean(findButton(container, "Sign in")));
@@ -3445,42 +3234,26 @@ describe("AgentConfigForm environment selector", () => {
await flushFake();
};
mockEnvironmentsApi.list.mockResolvedValue([
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
]);
mockEnvironmentsApi.list.mockResolvedValue(daytonaEnvironments());
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AgentConfigForm
mode="edit"
agent={makeAgent({
adapterType: "claude_local",
defaultEnvironmentId: "sandbox-1",
})}
onSave={vi.fn()}
hidePromptTemplate
showAdapterTypeField={false}
showAdapterTestEnvironmentButton
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AgentConfigForm
mode="edit"
agent={makeAgent({
adapterType: "claude_local",
defaultEnvironmentId: "sandbox-1",
})}
onSave={vi.fn()}
hidePromptTemplate
showAdapterTypeField={false}
showAdapterTestEnvironmentButton
/>
);
});
await flushFake();
@@ -3767,44 +3540,28 @@ describe("AgentConfigForm create-mode Claude OAuth binding", () => {
// environment. The helper returns the `onSave` spy so a test can read the patch
// that a stored login sends to the agent-update path.
async function renderEditClaudeSandbox(agentOverrides: Partial<Agent> = {}) {
mockEnvironmentsApi.list.mockResolvedValue([
makeEnvironment({ id: "local-1", name: "Local", driver: "local" }),
makeEnvironment({
id: "sandbox-1",
name: "Daytona",
driver: "sandbox",
config: { provider: "daytona" },
}),
]);
mockEnvironmentsApi.list.mockResolvedValue(daytonaEnvironments());
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const queryClient = createFormQueryClient();
const onSave = vi.fn().mockResolvedValue(undefined);
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<TooltipProvider>
<AgentConfigForm
mode="edit"
agent={makeAgent({
adapterType: "claude_local",
defaultEnvironmentId: "sandbox-1",
...agentOverrides,
})}
onSave={onSave}
hidePromptTemplate
showAdapterTypeField={false}
showAdapterTestEnvironmentButton
/>
</TooltipProvider>
</ToastProvider>
</QueryClientProvider>,
renderFormView(root, queryClient,
<AgentConfigForm
mode="edit"
agent={makeAgent({
adapterType: "claude_local",
defaultEnvironmentId: "sandbox-1",
...agentOverrides,
})}
onSave={onSave}
hidePromptTemplate
showAdapterTypeField={false}
showAdapterTestEnvironmentButton
/>
);
});
File diff suppressed because it is too large. Load diff
+29 -153
View File
@@ -439,6 +439,18 @@ function createExecutionState(overrides: Partial<IssueExecutionState> = {}): Iss
};
}
function createRelation(id: string, identifier: string, title: string, status: Issue["status"] = "todo") {
return {
id,
identifier,
title,
status,
priority: "medium" as const,
assigneeAgentId: null,
assigneeUserId: null,
};
}
function renderPropertiesWithQueryClient(container: HTMLDivElement, props: ComponentProps<typeof IssueProperties>, hiddenSettings: string[] = []) {
const queryClient = new QueryClient({
defaultOptions: {
@@ -663,24 +675,8 @@ describe("IssueProperties", () => {
});
it("stacks property chips vertically and aligns each label with the first chip", async () => {
const blockedBy = Array.from({ length: 3 }, (_, index) => ({
id: `blocker-${index + 1}`,
identifier: `BLOCK-${index + 1}`,
title: `Blocker ${index + 1}`,
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
})) as NonNullable<Issue["blockedBy"]>;
const blocks = Array.from({ length: 3 }, (_, index) => ({
id: `blocked-${index + 1}`,
identifier: `BLOCKED-${index + 1}`,
title: `Blocked task ${index + 1}`,
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
})) as NonNullable<Issue["blocks"]>;
const blockedBy = Array.from({ length: 3 }, (_, index) => (createRelation(`blocker-${index + 1}`, `BLOCK-${index + 1}`, `Blocker ${index + 1}`))) as NonNullable<Issue["blockedBy"]>;
const blocks = Array.from({ length: 3 }, (_, index) => (createRelation(`blocked-${index + 1}`, `BLOCKED-${index + 1}`, `Blocked task ${index + 1}`))) as NonNullable<Issue["blocks"]>;
const labels = Array.from({ length: 3 }, (_, index) => createLabel({
id: `label-${index + 1}`,
name: `Label ${index + 1}`,
@@ -1416,15 +1412,7 @@ describe("IssueProperties", () => {
const root = renderProperties(container, {
issue: createIssue({
blockedBy: [
{
id: "issue-2",
identifier: "PAP-2",
title: "Existing blocker",
status: "in_progress",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
createRelation("issue-2", "PAP-2", "Existing blocker", "in_progress"),
],
}),
childIssues: [],
@@ -1516,24 +1504,8 @@ describe("IssueProperties", () => {
const root = renderProperties(container, {
issue: createIssue({
blockedBy: [
{
id: "issue-2",
identifier: "PAP-2",
title: "Existing blocker",
status: "in_progress",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
{
id: "issue-4",
identifier: "PAP-4",
title: "Keep blocker",
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
createRelation("issue-2", "PAP-2", "Existing blocker", "in_progress"),
createRelation("issue-4", "PAP-4", "Keep blocker"),
],
}),
childIssues: [],
@@ -1569,24 +1541,8 @@ describe("IssueProperties", () => {
const root = renderProperties(container, {
issue: createIssue({
blockedBy: [
{
id: "issue-2",
identifier: "PAP-2",
title: "Existing blocker",
status: "in_progress",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
{
id: "issue-4",
identifier: "PAP-4",
title: "Keep blocker",
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
createRelation("issue-2", "PAP-2", "Existing blocker", "in_progress"),
createRelation("issue-4", "PAP-4", "Keep blocker"),
],
}),
childIssues: [],
@@ -1617,15 +1573,7 @@ describe("IssueProperties", () => {
});
it("summarizes long blocked-by and subtask relationships in their picker triggers", async () => {
const blockedBy = Array.from({ length: 7 }, (_, index) => ({
id: `blocker-${index + 1}`,
identifier: `BLOCK-${index + 1}`,
title: `Blocker ${index + 1}`,
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
})) as NonNullable<Issue["blockedBy"]>;
const blockedBy = Array.from({ length: 7 }, (_, index) => (createRelation(`blocker-${index + 1}`, `BLOCK-${index + 1}`, `Blocker ${index + 1}`))) as NonNullable<Issue["blockedBy"]>;
const childIssues = Array.from({ length: 7 }, (_, index) => createIssue({
id: `child-${index + 1}`,
identifier: `SUB-${index + 1}`,
@@ -1677,15 +1625,7 @@ describe("IssueProperties", () => {
enableTaskWatchdogs: false,
enableClassicTaskInterface: true,
});
const blocking = Array.from({ length: 7 }, (_, index) => ({
id: `blocking-${index + 1}`,
identifier: `BLOCKING-${index + 1}`,
title: `Blocking issue ${index + 1}`,
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
})) as NonNullable<Issue["blocks"]>;
const blocking = Array.from({ length: 7 }, (_, index) => (createRelation(`blocking-${index + 1}`, `BLOCKING-${index + 1}`, `Blocking issue ${index + 1}`))) as NonNullable<Issue["blocks"]>;
const relatedIssues = Array.from({ length: 7 }, (_, index) => ({
id: `related-${index + 1}`,
identifier: `RELATED-${index + 1}`,
@@ -1803,15 +1743,7 @@ describe("IssueProperties", () => {
});
it("updates the blocked-by relationship summary when the issue changes", async () => {
const blockedBy = Array.from({ length: 7 }, (_, index) => ({
id: `blocker-${index + 1}`,
identifier: `BLOCK-${index + 1}`,
title: `Blocker ${index + 1}`,
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
})) as NonNullable<Issue["blockedBy"]>;
const blockedBy = Array.from({ length: 7 }, (_, index) => (createRelation(`blocker-${index + 1}`, `BLOCK-${index + 1}`, `Blocker ${index + 1}`))) as NonNullable<Issue["blockedBy"]>;
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
@@ -2134,30 +2066,14 @@ describe("IssueProperties", () => {
relatedWork: {
outbound: [
{
issue: {
id: "issue-22",
identifier: "PAP-22",
title: "Related task",
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
issue: createRelation("issue-22", "PAP-22", "Related task"),
mentionCount: 1,
sources: [{ kind: "description", sourceRecordId: null, label: "description", matchedText: "PAP-22" }],
},
],
inbound: [
{
issue: {
id: "issue-23",
identifier: "PAP-23",
title: "Mentions this task",
status: "in_progress",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
issue: createRelation("issue-23", "PAP-23", "Mentions this task", "in_progress"),
mentionCount: 1,
sources: [{ kind: "description", sourceRecordId: null, label: "description", matchedText: "PAP-1" }],
},
@@ -2193,65 +2109,25 @@ describe("IssueProperties", () => {
const root = renderProperties(container, {
issue: createIssue({
blockedBy: [
{
id: "issue-22",
identifier: "PAP-22",
title: "Blocker",
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
createRelation("issue-22", "PAP-22", "Blocker"),
],
blocks: [
{
id: "issue-33",
identifier: "PAP-33",
title: "Blocked issue",
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
createRelation("issue-33", "PAP-33", "Blocked issue"),
],
relatedWork: {
outbound: [
{
issue: {
id: "issue-22",
identifier: "PAP-22",
title: "Blocker",
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
issue: createRelation("issue-22", "PAP-22", "Blocker"),
mentionCount: 1,
sources: [{ kind: "description", sourceRecordId: null, label: "description", matchedText: "PAP-22" }],
},
{
issue: {
id: "issue-33",
identifier: "PAP-33",
title: "Blocked issue",
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
issue: createRelation("issue-33", "PAP-33", "Blocked issue"),
mentionCount: 1,
sources: [{ kind: "description", sourceRecordId: null, label: "description", matchedText: "PAP-33" }],
},
{
issue: {
id: "child-44",
identifier: "PAP-44",
title: "Child issue",
status: "todo",
priority: "medium",
assigneeAgentId: null,
assigneeUserId: null,
},
issue: createRelation("child-44", "PAP-44", "Child issue"),
mentionCount: 1,
sources: [{ kind: "description", sourceRecordId: null, label: "description", matchedText: "PAP-44" }],
},
@@ -214,6 +214,10 @@ describe("IssueThreadInteractionCard", () => {
expect(host.querySelector('[data-testid="connection-intent-actions"]')).toBeTruthy();
expect(host.textContent).toContain("Connect");
expect(host.textContent).toContain("Not now");
const labels = Array.from(host.querySelectorAll("button")).map((button) => button.textContent?.trim());
expect(labels).toContain("Connect");
expect(labels).toContain("Not now");
expect(host.textContent).toContain("Access is added only for this agent");
const loadButton = Array.from(host.querySelectorAll("button")).find((button) =>
button.textContent?.trim() === "Connect",
);
@@ -241,16 +245,7 @@ describe("IssueThreadInteractionCard", () => {
expect(dialog?.textContent).toContain("Complete connection setup without leaving this task.");
});
it("keeps connection resolution controls exclusive to the addressed user", () => {
const host = renderCard({
interaction: pendingConnectionIntentInteraction,
currentUserId: "another-user",
});
expect(host.querySelector('[data-testid="connection-intent-waiting"]')).toBeTruthy();
expect(Array.from(host.querySelectorAll("button")).map((button) => button.textContent?.trim())).not.toContain("Connect");
expect(host.textContent).not.toContain("Not now");
});
;
it("exposes pending question options as selectable radio and checkbox controls", () => {
const host = renderCard({
@@ -258,6 +253,8 @@ describe("IssueThreadInteractionCard", () => {
onSubmitInteractionAnswers: vi.fn(),
});
expect(host.childElementCount).toBeGreaterThan(0);
const singleGroup = host.querySelector('[role="radiogroup"]');
expect(singleGroup?.getAttribute("aria-labelledby")).toBe(
"interaction-questions-default-collapse-depth-prompt",
@@ -433,15 +430,7 @@ describe("IssueThreadInteractionCard", () => {
expect(host.textContent).toBe("");
});
it("still renders a legitimate ask_user_questions card", () => {
const host = renderCard({
interaction: pendingAskUserQuestionsInteraction,
onSubmitInteractionAnswers: vi.fn(),
});
expect(host.childElementCount).toBeGreaterThan(0);
expect(host.querySelectorAll('[role="radio"]').length).toBeGreaterThan(0);
});
;
it("keeps a closed native select set closed while preserving direct-question defaults", () => {
const closed = {
@@ -1589,17 +1578,7 @@ describe("IssueThreadInteractionCard connection-authorization card", () => {
describe("IssueThreadInteractionCard connection-intent card", () => {
const USER_LABELS = new Map([[issueThreadInteractionFixtureMeta.currentUserId, "Carol"]]);
it("offers the addressed user the shared setup entry point and Not now", () => {
const host = renderCard({
interaction: pendingConnectionIntentInteraction,
currentUserId: issueThreadInteractionFixtureMeta.currentUserId,
});
expect(host.querySelector('[data-testid="connection-intent-actions"]')).not.toBeNull();
const labels = Array.from(host.querySelectorAll("button")).map((button) => button.textContent?.trim());
expect(labels).toContain("Connect");
expect(labels).toContain("Not now");
expect(host.textContent).toContain("Access is added only for this agent");
});
;
it("shows another viewer only who it is waiting for", () => {
const host = renderCard({
@@ -1612,6 +1591,7 @@ describe("IssueThreadInteractionCard connection-intent card", () => {
expect(host.querySelector('[data-testid="connection-intent-actions"]')).toBeNull();
expect(host.querySelectorAll("button")).toHaveLength(0);
expect(host.innerHTML).not.toContain("authorizationUrl");
expect(host.textContent).not.toContain("Not now");
});
it("renders retry and connected terminal states without generic confirmation actions", () => {
+6 -16
View File
@@ -584,9 +584,12 @@ describe("MarkdownBody", () => {
it("opens external links in a new tab with safe rel attributes", () => {
const html = renderMarkdown("[docs](https://example.com/docs)");
expect(html).toContain('href="https://example.com/docs"');
expect(html).toContain("<a href=\"https://example.com/docs\"");
expect(html).toContain('target="_blank"');
expect(html).toContain('rel="noopener noreferrer"');
expect(html).not.toContain("lucide-github");
expect(html).toContain("lucide-external-link");
expect(html).toMatch(/<span style="white-space:nowrap">s<svg[^>]*lucide-external-link/);
});
it("opens GitHub links in a new tab", () => {
@@ -623,22 +626,9 @@ describe("MarkdownBody", () => {
expect(html).toContain('class="lucide lucide-github mr-1 inline h-3.5 w-3.5 align-(--va-0_125em)"');
});
it("does not prefix non-GitHub markdown links with the GitHub icon", () => {
const html = renderMarkdown("[docs](https://example.com/docs)");
;
expect(html).toContain('<a href="https://example.com/docs"');
expect(html).not.toContain("lucide-github");
});
it("suffixes external links with a new-tab icon glued to the last character", () => {
const html = renderMarkdown("[docs](https://example.com/docs)");
expect(html).toContain('target="_blank"');
expect(html).toContain("lucide-external-link");
// Last character "s" must sit in a no-wrap span with the icon so the
// indicator never wraps away from the link text.
expect(html).toMatch(/<span style="white-space:nowrap">s<svg[^>]*lucide-external-link/);
});
;
it("does not render the new-tab icon on internal links", () => {
const html = renderMarkdown("[settings](/company/settings)");
+34 -158
View File
@@ -273,6 +273,16 @@ async function flushReact() {
});
}
function renderWizardView(root: ReturnType<typeof createRoot>, queryClient: QueryClient) {
return act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
}
const SESSION_USER_ID = "user-b";
function render() {
@@ -422,13 +432,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
mockAdapterRegistry.list = [{ type: "claude_local" }, { type: "codex_local" }];
const { root, queryClient } = render();
const renderTree = () =>
act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
renderWizardView(root, queryClient);
await renderTree();
await flushReact();
return { root, renderTree };
@@ -516,13 +520,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
]);
mockAdapterRegistry.list = [{ type: "claude_local" }, { type: "codex_local" }];
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
const clickText = async (match: (t: string) => boolean) => {
@@ -592,13 +590,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
mockAdapterRegistry.list = [{ type: "claude_local" }, { type: "codex_local" }];
const { root, queryClient } = render();
const renderTree = () =>
act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
renderWizardView(root, queryClient);
await renderTree();
await flushReact();
@@ -772,13 +764,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
mockCompaniesApi.list.mockResolvedValue([]);
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
const clickByText = async (match: (text: string) => boolean) => {
@@ -842,6 +828,8 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
"No stored Claude login was found for this agent.",
);
expect(document.body.textContent).toContain("No working authentication was found");
await act(async () => root.unmount());
});
@@ -1240,31 +1228,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
await act(async () => root.unmount());
});
it("blocks the hire when the probe reports warn with adapter_auth_missing and no stored login exists", async () => {
// The default `beforeEach` mock already rejects with a 404 `ApiError`.
mockAgentsApi.testEnvironment.mockResolvedValue({
adapterType: "claude_local",
status: "warn" as const,
checks: [
{
code: ADAPTER_AUTH_MISSING_CHECK_CODE,
level: "warn" as const,
message: "No stored Claude login was found for this agent.",
},
],
testedAt: new Date().toISOString(),
});
const { root, clickByText } = await openConnectStep();
await clickByText((t) => isArcPrimary(t));
expect(mockAgentsApi.hire).not.toHaveBeenCalled();
expect(document.body.textContent).toContain(
"No working authentication was found",
);
await act(async () => root.unmount());
});
;
it("reads the stored-login status once for each create attempt", async () => {
mockAgentsApi.getClaudeOAuthTokenStatus.mockReset();
@@ -1321,13 +1285,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
const { container, root, queryClient } = render();
const renderTree = () =>
act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
renderWizardView(root, queryClient);
await renderTree();
await flushReact();
@@ -1394,13 +1352,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
});
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
const companyNameInput = document.querySelector("input") as HTMLInputElement;
@@ -1451,13 +1403,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
);
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
expect(document.querySelector("#onboarding-agent-name")).toBeNull();
@@ -1513,13 +1459,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
]);
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
// Falls back to the wizard's default first step, not the stale step 4
@@ -1553,13 +1493,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
mockCompaniesApi.list.mockRejectedValue(new Error("company list unavailable"));
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
// It mounts: the companies query sets `retry: false`, and with no
@@ -1598,13 +1532,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
mockCompany.loading = false;
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
expect(localStorage).toHaveBeenCalled();
@@ -1641,13 +1569,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
mockCompaniesApi.list.mockRejectedValue(new Error("refetch failed"));
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
// Mounted rather than blank...
@@ -1689,13 +1611,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
mockCompaniesApi.list.mockRejectedValue(new Error("company list unavailable"));
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
expect(window.localStorage.getItem(ONBOARDING_STORAGE_KEY)).toBe(draft);
@@ -1733,13 +1649,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
mockCompaniesApi.list.mockRejectedValue(new Error("refetch failed for the new account"));
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
// Account A's agent name must not appear in account B's wizard.
@@ -1787,13 +1697,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
]);
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
// Mounted — so this is a real observation, not an unmounted false pass.
@@ -1823,13 +1727,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
);
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
expect(window.localStorage.getItem(ONBOARDING_STORAGE_KEY)).not.toBeNull();
@@ -1861,13 +1759,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
// B's session: the refetch fails, so A's data is retained.
mockCompaniesApi.list.mockRejectedValue(new Error("refetch failed"));
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
// Mounted, so this observes the real thing rather than an empty document.
@@ -1903,13 +1795,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
});
mockCompaniesApi.list.mockReturnValue(new Promise(() => {}));
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
// Nothing mounted, so nothing could have written over the draft.
@@ -1927,13 +1813,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
const { root, queryClient } = render();
mockCompaniesApi.list.mockReturnValue(new Promise(() => {}));
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
await flushReact();
expect(window.localStorage.getItem(ONBOARDING_STORAGE_KEY)).toBeNull();
@@ -2002,13 +1882,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
mockDialog.onboardingOptions = {};
const { root, queryClient } = render();
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<OnboardingWizard />
</QueryClientProvider>,
);
});
await renderWizardView(root, queryClient);
// The login-panel gate chains several dependent queries (environments,
// instance settings, environment capabilities, then the auth signal
// itself), each settling on its own render. One flush is not always
@@ -3052,6 +2926,8 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
it("renders no 'Use saved login' control", async () => {
mockAgentsApi.getAdapterAuthSignal.mockResolvedValue({ status: "absent" });
const { root } = await openStep4({ adapterType: "claude_local" });
await pickSource(/Claude/);
expect(document.body.textContent).toContain("Sign in to Claude then come back and enter authorization code");
expect(document.body.textContent).not.toContain("Use saved login");
await act(async () => root.unmount());
});
@@ -6,7 +6,7 @@ import { expect, it, vi } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { decisionModelsApi } from "@/api/decision-models";
import { DecisionModelSettingsSection, DecisionModelSettingsView } from "./DecisionModelSettings";
import { choices, configured, result } from "../../../storybook/stories/decision-models/fixtures";
import { choices, configured, result } from "../../../storybook/support/decision-models/fixtures";
vi.mock("@/context/CompanyContext", () => ({ useCompany: () => ({ selectedCompanyId: "company-storybook" }) }));
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
@@ -5,7 +5,7 @@ import { MemoryRouter } from "react-router-dom";
import { expect, it, vi } from "vitest";
import { updateFastResponseSchema } from "@paperclipai/shared";
import { FastResponseSettingsView } from "./FastResponseSettings";
import { choices } from "../../../storybook/stories/decision-models/fixtures";
import { choices } from "../../../storybook/support/decision-models/fixtures";
vi.mock("@/context/CompanyContext", () => ({
useCompany: () => ({ selectedCompanyId: "company-storybook" }),
}));