mirror of
https://github.com/vxcontrol/pentagi.git
synced 2026-08-24 20:16:30 +00:00
fix(settings): keep unsaved provider-form edits across a background refetch
The provider detail form was the only one of the three detail forms created
without `resetOptions: { keepDirtyValues: true }`. Its seeding effect keys on
`data`, and settingsProviders is cache-and-network + replaceWithIncoming, so a
background refetch (list→detail race, or a provider* subscription) delivers a
fresh `data` reference mid-edit and the plain reset() silently wipes whatever
the user was typing. settings-prompt and template already guard this, the
latter with a comment naming the exact hazard.
Live-reproduced on the docker branch image: an edit typed during a delayed
refetch reverted to the server value. Regression test delivers a fresh data
reference after an edit and asserts it survives — it fails without the option.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e31c725cc0
commit
81f3d06ebd
@@ -1,4 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ProviderType } from '@/graphql/types';
|
||||
@@ -6,7 +6,7 @@ import { routes } from '@/lib/routes';
|
||||
|
||||
const { navigate } = vi.hoisted(() => ({ navigate: vi.fn() }));
|
||||
|
||||
const state = vi.hoisted(() => ({ params: new URLSearchParams() }));
|
||||
const state = vi.hoisted(() => ({ params: new URLSearchParams(), providerId: 'new' }));
|
||||
|
||||
const setSearch = (search: string) => {
|
||||
state.params = new URLSearchParams(search);
|
||||
@@ -37,6 +37,14 @@ const userDefined = [
|
||||
type: ProviderType.Minimax,
|
||||
updatedAt: '',
|
||||
},
|
||||
{
|
||||
agents: {},
|
||||
createdAt: '',
|
||||
id: 'edit-1',
|
||||
name: 'Persisted Name',
|
||||
type: ProviderType.Anthropic,
|
||||
updatedAt: '',
|
||||
},
|
||||
];
|
||||
|
||||
const settingsProviders = {
|
||||
@@ -65,7 +73,7 @@ vi.mock('react-router-dom', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('react-router-dom')>()),
|
||||
useBlocker: () => ({ proceed: undefined, reset: undefined, state: 'unblocked' }),
|
||||
useNavigate: () => navigate,
|
||||
useParams: () => ({ providerId: 'new' }),
|
||||
useParams: () => ({ providerId: state.providerId }),
|
||||
useSearchParams: () => [state.params, vi.fn()],
|
||||
}));
|
||||
|
||||
@@ -94,6 +102,8 @@ import SettingsProvider from './settings-provider';
|
||||
beforeEach(() => {
|
||||
navigate.mockClear();
|
||||
setSearch('');
|
||||
state.providerId = 'new';
|
||||
queryResult.data = { settingsProviders };
|
||||
queryResult.error = undefined;
|
||||
queryResult.loading = false;
|
||||
});
|
||||
@@ -148,4 +158,25 @@ describe('SettingsProvider create-form type guards', () => {
|
||||
expect(screen.queryByText('Loading provider data...')).not.toBeInTheDocument();
|
||||
expect(navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// The seeding effect re-runs on every settingsProviders refetch (a fresh `data` reference under
|
||||
// cache-and-network); keepDirtyValues is what stops that reset from wiping an in-flight edit.
|
||||
it('preserves an in-flight edit across a background refetch', () => {
|
||||
state.providerId = 'edit-1';
|
||||
const { rerender } = render(<SettingsProvider />);
|
||||
|
||||
const nameInput = screen.getByPlaceholderText('Enter provider name') as HTMLInputElement;
|
||||
|
||||
expect(nameInput.value).toBe('Persisted Name');
|
||||
|
||||
fireEvent.change(nameInput, { target: { value: 'My Unsaved Edit' } });
|
||||
|
||||
// A refetch delivers a new `data` object with the same server content, re-firing the effect.
|
||||
queryResult.data = { settingsProviders };
|
||||
rerender(<SettingsProvider />);
|
||||
|
||||
expect((screen.getByPlaceholderText('Enter provider name') as HTMLInputElement).value).toBe(
|
||||
'My Unsaved Edit',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1203,6 +1203,10 @@ function SettingsProvider() {
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
},
|
||||
// The seeding effect re-runs on every settingsProviders refetch (cache-and-network +
|
||||
// replaceWithIncoming gives a fresh `data`); without this a background refetch landing
|
||||
// mid-edit silently wipes the unsaved form. Matches settings-prompt and template.
|
||||
resetOptions: { keepDirtyValues: true },
|
||||
schema: formSchema,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user