mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-15 10:14:36 +00:00
1 parent
da6c99e3df
commit
77e73dd091
4 files changed
+513
-27
No files matched your search
@@ -40,7 +40,11 @@ vi.mock('@/components/shared/code-editor', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('@/components/compatible-cli/codex-control-center-tab', () => ({
|
||||
CodexControlCenterTab: () => <div>Control Center</div>,
|
||||
CodexControlCenterTab: ({ disabled }: { disabled?: boolean }) => (
|
||||
<div data-testid="codex-control-center" data-disabled={String(Boolean(disabled))}>
|
||||
Control Center
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@/components/compatible-cli/codex-docs-tab', () => ({
|
||||
@@ -53,6 +57,15 @@ vi.mock('@/components/compatible-cli/codex-overview-tab', () => ({
|
||||
|
||||
import { CodexPage } from '@/pages/codex';
|
||||
|
||||
const joinedMigrationRawText = `[notice.model_migrations]
|
||||
"gpt-5.3-codex" = "gpt-5.4"[agents.code_simplifier]
|
||||
description = "Keep exact text"
|
||||
`;
|
||||
const repairedMigrationRawText = joinedMigrationRawText.replace(
|
||||
'"gpt-5.4"[agents.code_simplifier]',
|
||||
'"gpt-5.4"\n[agents.code_simplifier]'
|
||||
);
|
||||
|
||||
const diagnostics = {
|
||||
binary: {
|
||||
installed: true,
|
||||
@@ -228,5 +241,184 @@ describe('CodexPage', () => {
|
||||
|
||||
expect(screen.getByText(/Read-only: Refusing symlink file for safety\./)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('codex raw editor')).toHaveAttribute('readonly');
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'Preview one-newline repair' })
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('previews the one-newline repair as an unsaved draft before explicitly saving it', async () => {
|
||||
mocks.saveRawConfigAsync.mockResolvedValue({ success: true, mtime: 101 });
|
||||
mocks.useCodex.mockReturnValue(
|
||||
buildUseCodexResult({
|
||||
rawConfig: {
|
||||
path: '$CODEX_HOME/config.toml',
|
||||
resolvedPath: '/tmp/.codex/config.toml',
|
||||
exists: true,
|
||||
mtime: 100,
|
||||
rawText: joinedMigrationRawText,
|
||||
config: null,
|
||||
parseError: 'Invalid TOML',
|
||||
readError: null,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
render(<CodexPage />);
|
||||
|
||||
await userEvent.click(screen.getByRole('tab', { name: 'Control Center' }));
|
||||
expect(screen.getByTestId('codex-control-center')).toHaveAttribute('data-disabled', 'true');
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Preview one-newline repair' }));
|
||||
|
||||
expect(screen.getByLabelText('codex raw editor')).toHaveValue(repairedMigrationRawText);
|
||||
expect(screen.getByText('Unsaved')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Save' })).toBeEnabled();
|
||||
expect(screen.getByTestId('codex-control-center')).toHaveAttribute('data-disabled', 'true');
|
||||
expect(mocks.saveRawConfigAsync).not.toHaveBeenCalled();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mocks.saveRawConfigAsync).toHaveBeenCalledWith({
|
||||
rawText: repairedMigrationRawText,
|
||||
expectedMtime: 100,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the repair draft bound to its original mtime across a fetched snapshot update', async () => {
|
||||
mocks.saveRawConfigAsync.mockResolvedValue({ success: true, mtime: 201 });
|
||||
let rawConfig = {
|
||||
path: '$CODEX_HOME/config.toml',
|
||||
resolvedPath: '/tmp/.codex/config.toml',
|
||||
exists: true,
|
||||
mtime: 100,
|
||||
rawText: joinedMigrationRawText,
|
||||
config: null,
|
||||
parseError: 'Invalid TOML',
|
||||
readError: null,
|
||||
};
|
||||
mocks.useCodex.mockImplementation(() => buildUseCodexResult({ rawConfig }));
|
||||
|
||||
const view = render(<CodexPage />);
|
||||
await userEvent.click(screen.getByRole('tab', { name: 'Control Center' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Preview one-newline repair' }));
|
||||
|
||||
rawConfig = {
|
||||
...rawConfig,
|
||||
mtime: 200,
|
||||
rawText: 'model = "gpt-5.4-mini"\n',
|
||||
config: { model: 'gpt-5.4-mini' },
|
||||
parseError: null,
|
||||
};
|
||||
view.rerender(<CodexPage />);
|
||||
|
||||
expect(screen.getByLabelText('codex raw editor')).toHaveValue(repairedMigrationRawText);
|
||||
expect(screen.getByTestId('codex-control-center')).toHaveAttribute('data-disabled', 'true');
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mocks.saveRawConfigAsync).toHaveBeenCalledWith({
|
||||
rawText: repairedMigrationRawText,
|
||||
expectedMtime: 100,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps manual raw edits bound to the snapshot mtime captured on first edit', async () => {
|
||||
mocks.saveRawConfigAsync.mockResolvedValue({ success: true, mtime: 201 });
|
||||
let rawConfig = {
|
||||
path: '$CODEX_HOME/config.toml',
|
||||
resolvedPath: '/tmp/.codex/config.toml',
|
||||
exists: true,
|
||||
mtime: 100,
|
||||
rawText: 'model = "gpt-5.4"\n',
|
||||
config: { model: 'gpt-5.4' },
|
||||
parseError: null,
|
||||
readError: null,
|
||||
};
|
||||
mocks.useCodex.mockImplementation(() => buildUseCodexResult({ rawConfig }));
|
||||
|
||||
const view = render(<CodexPage />);
|
||||
const editor = screen.getByLabelText('codex raw editor');
|
||||
await userEvent.clear(editor);
|
||||
await userEvent.type(editor, 'model = "gpt-5.4-mini"');
|
||||
|
||||
rawConfig = {
|
||||
...rawConfig,
|
||||
mtime: 200,
|
||||
rawText: 'model = "gpt-5.4-high"\n',
|
||||
config: { model: 'gpt-5.4-high' },
|
||||
};
|
||||
view.rerender(<CodexPage />);
|
||||
|
||||
expect(screen.getByLabelText('codex raw editor')).toHaveValue('model = "gpt-5.4-mini"');
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mocks.saveRawConfigAsync).toHaveBeenCalledWith({
|
||||
rawText: 'model = "gpt-5.4-mini"',
|
||||
expectedMtime: 100,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not offer the targeted repair for generic invalid TOML', () => {
|
||||
mocks.useCodex.mockReturnValue(
|
||||
buildUseCodexResult({
|
||||
rawConfig: {
|
||||
path: '$CODEX_HOME/config.toml',
|
||||
resolvedPath: '/tmp/.codex/config.toml',
|
||||
exists: true,
|
||||
mtime: 100,
|
||||
rawText: 'model = "gpt-5.4"\n[features\n',
|
||||
config: null,
|
||||
parseError: 'Invalid TOML',
|
||||
readError: null,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
render(<CodexPage />);
|
||||
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'Preview one-newline repair' })
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('re-enables structured controls only after the saved valid snapshot is loaded', async () => {
|
||||
mocks.saveRawConfigAsync.mockResolvedValue({ success: true, mtime: 101 });
|
||||
let rawConfig = {
|
||||
path: '$CODEX_HOME/config.toml',
|
||||
resolvedPath: '/tmp/.codex/config.toml',
|
||||
exists: true,
|
||||
mtime: 100,
|
||||
rawText: joinedMigrationRawText,
|
||||
config: null,
|
||||
parseError: 'Invalid TOML',
|
||||
readError: null,
|
||||
};
|
||||
mocks.useCodex.mockImplementation(() => buildUseCodexResult({ rawConfig }));
|
||||
|
||||
const view = render(<CodexPage />);
|
||||
await userEvent.click(screen.getByRole('tab', { name: 'Control Center' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Preview one-newline repair' }));
|
||||
expect(screen.getByTestId('codex-control-center')).toHaveAttribute('data-disabled', 'true');
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
||||
await waitFor(() => expect(mocks.saveRawConfigAsync).toHaveBeenCalledTimes(1));
|
||||
|
||||
rawConfig = {
|
||||
...rawConfig,
|
||||
mtime: 101,
|
||||
rawText: repairedMigrationRawText,
|
||||
config: {
|
||||
notice: { model_migrations: { 'gpt-5.3-codex': 'gpt-5.4' } },
|
||||
agents: { code_simplifier: { description: 'Keep exact text' } },
|
||||
},
|
||||
parseError: null,
|
||||
};
|
||||
view.rerender(<CodexPage />);
|
||||
|
||||
expect(screen.getByTestId('codex-control-center')).toHaveAttribute('data-disabled', 'false');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user