Count only real edits as unsaved recipe search changes
Before recipe search is set up the card suggests SearXNG and the deployment's URL, which differ from the unset saved values. That suggestion marked the card dirty, so the leave guard warned about unsaved changes the household never made. The leave guard now fires only when the draft differs from the loaded baseline, which includes the suggestion. Save stays enabled so the suggestion can still be stored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -657,6 +657,29 @@ describe('Recipe search card', () => {
|
||||
expect(await screen.findByRole('dialog', { name: 'Leave without saving?' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not count the suggested SearXNG URL as an unsaved change until it is edited', async () => {
|
||||
stubAPI({ 'GET /api/v1/settings/recipe-search': () => jsonResponse(200, UNCONFIGURED) })
|
||||
await renderCard()
|
||||
const unload = () => {
|
||||
const event = new Event('beforeunload', { cancelable: true })
|
||||
act(() => void window.dispatchEvent(event))
|
||||
return event.defaultPrevented
|
||||
}
|
||||
expect(saveSearch()).toBeEnabled()
|
||||
expect(unload()).toBe(false)
|
||||
fireEvent.change(url(), { target: { value: 'https://search.example' } })
|
||||
expect(unload()).toBe(true)
|
||||
fireEvent.change(url(), { target: { value: 'http://searxng:8080' } })
|
||||
expect(unload()).toBe(false)
|
||||
})
|
||||
|
||||
it('leaves the untouched first-use card without asking', async () => {
|
||||
stubAPI({ 'GET /api/v1/settings/recipe-search': () => jsonResponse(200, UNCONFIGURED) })
|
||||
await renderCard()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Back to Settings' }))
|
||||
expect(await screen.findByTestId('path')).toHaveTextContent('/settings')
|
||||
})
|
||||
|
||||
it('keeps 44 px tap targets', async () => {
|
||||
stubAPI({ 'GET /api/v1/settings/recipe-search': () => jsonResponse(200, BRAVE_SETTINGS) })
|
||||
await renderCard()
|
||||
|
||||
@@ -81,16 +81,24 @@ function patchOf(saved: RecipeSearchSettings, draft: Draft): RecipeSearchSetting
|
||||
return patch
|
||||
}
|
||||
|
||||
function sameDraft(a: Draft, b: Draft): boolean {
|
||||
return a.provider === b.provider && a.base_url === b.base_url && a.api_key === b.api_key
|
||||
}
|
||||
|
||||
function RecipeSearchForm({ saved, onDirtyChange }: { saved: RecipeSearchSettings } & RecipeSearchCardProps) {
|
||||
const [draft, setDraft] = useState(() => draftOf(saved))
|
||||
const save = useSaveRecipeSearchSettings()
|
||||
const test = useTestRecipeSearch()
|
||||
const patch = patchOf(saved, draft)
|
||||
const dirty = Object.keys(patch).length > 0
|
||||
// Only the household's own edits are unsaved changes. Before recipe search
|
||||
// is set up the untouched draft already differs from what is saved — it
|
||||
// suggests SearXNG and the deployment's URL — but leaving it loses nothing.
|
||||
const edited = dirty && !sameDraft(draft, draftOf(saved))
|
||||
|
||||
useEffect(() => {
|
||||
onDirtyChange(dirty)
|
||||
}, [dirty, onDirtyChange])
|
||||
onDirtyChange(edited)
|
||||
}, [edited, onDirtyChange])
|
||||
useEffect(() => () => onDirtyChange(false), [onDirtyChange])
|
||||
|
||||
// An edit clears the last save and test results: they describe the saved
|
||||
|
||||
Reference in New Issue
Block a user