fix(codex-config): close fix-round-1 parity gaps against CcConfig
Isolate the codex-CLI-backed reads (mcp/plugins/marketplaces) from the file-backed ones in fetchAll via two independent Promise.all groups, so a missing `codex` binary degrades only the mcp/plugins tabs instead of leaving the whole panel stuck on skeletons. Generalize EditorModal's nameless-type detection behind a `namelessTypes` prop (defaulting to Claude's own ["memory"]) so Codex's config.toml can be created without a name. Port BackupsModal, disable the plugin/mcp Remove button when no stable identifier resolved, hide the inert search box on memory/config, drop a redundant empty state, and reword the delete-dialog copy to be tool-neutral. Co-authored-by: Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
This commit is contained in:
@@ -42,6 +42,7 @@ const codexConfigMock = vi.hoisted(() => {
|
||||
pluginAction: r({ ok: true, stdout: "", stderr: "" }),
|
||||
marketplaceAdd: r({ ok: true, stdout: "", stderr: "" }),
|
||||
marketplaceAction: r({ ok: true, stdout: "", stderr: "" }),
|
||||
backups: r({ items: [] }),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -124,4 +125,63 @@ describe("CodexConfig", () => {
|
||||
|
||||
expect(write).toHaveBeenCalledWith(expect.objectContaining({ type: "rules", name: "default" }));
|
||||
});
|
||||
|
||||
it("keeps the file-backed tabs working when the codex CLI read rejects", async () => {
|
||||
stubCodexApi({ skills: [{ scope: "user", name: "my-skill", file: "/s/my-skill", size: 3 }] });
|
||||
codexConfigMock.mcp.mockRejectedValueOnce(new Error("the codex CLI was not found on PATH"));
|
||||
render(<CodexConfig />);
|
||||
await settle();
|
||||
|
||||
// No page-wide error banner - the CLI-backed rejection is isolated.
|
||||
expect(screen.queryByText(/failed to load/i)).not.toBeInTheDocument();
|
||||
|
||||
// Pure file reads (skills) still rendered their data.
|
||||
await userEvent.click(screen.getByRole("tab", { name: /skills/i }));
|
||||
expect(screen.getByText("my-skill")).toBeInTheDocument();
|
||||
|
||||
// The CLI-owned tab degrades gracefully instead of spinning forever.
|
||||
await userEvent.click(screen.getByRole("tab", { name: /mcp/i }));
|
||||
expect(screen.getByText(/codex cli isn't available/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("creates a nameless-type artifact (config.toml) without requiring a name", async () => {
|
||||
const write = vi.fn().mockResolvedValue({ file: "/c/config.toml", created: true });
|
||||
stubCodexApi({ write });
|
||||
render(<CodexConfig />);
|
||||
await settle();
|
||||
|
||||
await userEvent.click(screen.getByRole("tab", { name: /config/i }));
|
||||
const newButtons = screen.getAllByRole("button", { name: /new/i });
|
||||
await userEvent.click(newButtons[0]!);
|
||||
await settle();
|
||||
|
||||
// No name field, no client-side "Name" validation error blocking save.
|
||||
expect(screen.queryByPlaceholderText(/name/i)).not.toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole("button", { name: /save/i }));
|
||||
|
||||
expect(write).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: "config", name: undefined })
|
||||
);
|
||||
});
|
||||
|
||||
it("opens the backups modal and lists what the codex API returns", async () => {
|
||||
codexConfigMock.backups.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
type: "rules",
|
||||
name: "default.2026-09-08.bak",
|
||||
file: "/r/default.bak",
|
||||
mtime: 0,
|
||||
size: 3,
|
||||
},
|
||||
],
|
||||
});
|
||||
render(<CodexConfig />);
|
||||
await settle();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: /backups/i }));
|
||||
await settle();
|
||||
|
||||
expect(screen.getByText("default.2026-09-08.bak")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user