mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-06 05:56:19 +01:00
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:
commit
48ffca9438
415 files changed
+22501
-15340
No files matched your search
@@ -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
@@ -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", () => {
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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" }),
|
||||
}));
|
||||
|
||||
Reference in new issue
Block a user