diff --git a/frontend/src/api/queries/useAllSettings.ts b/frontend/src/api/queries/useAllSettings.ts index e83539a26..0d6cb757b 100644 --- a/frontend/src/api/queries/useAllSettings.ts +++ b/frontend/src/api/queries/useAllSettings.ts @@ -77,6 +77,7 @@ export function useAllSettings() { if (!msg?.success) return; if (saved) markSaved(saved); queryClient.invalidateQueries({ queryKey: keys.settings.all() }); + queryClient.invalidateQueries({ queryKey: keys.settings.defaults() }); }, }); diff --git a/frontend/src/test/use-all-settings.test.tsx b/frontend/src/test/use-all-settings.test.tsx index e7f197bd6..dba588f51 100644 --- a/frontend/src/test/use-all-settings.test.tsx +++ b/frontend/src/test/use-all-settings.test.tsx @@ -13,6 +13,67 @@ afterEach(() => { }); describe('useAllSettings', () => { + it('refreshes cached default settings after a successful save', async () => { + vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', {})); + const queryClient = makeTestQueryClient(); + const fetchDefaults = vi + .fn() + .mockResolvedValueOnce({ subURI: 'https://example.com/sub/' }) + .mockResolvedValueOnce({ subURI: 'https://example.com/my_custom_path/' }); + const defaultsQuery = { + queryKey: keys.settings.defaults(), + queryFn: fetchDefaults, + staleTime: Infinity, + }; + const wrapper = ({ children }: { children: ReactNode }) => ( + {children} + ); + + await queryClient.fetchQuery(defaultsQuery); + const { result } = renderHook(() => useAllSettings(), { wrapper }); + + await waitFor(() => expect(result.current.fetched).toBe(true)); + await act(async () => { + await result.current.saveAll(); + }); + + const defaults = await queryClient.fetchQuery(defaultsQuery); + expect(fetchDefaults).toHaveBeenCalledTimes(2); + expect(defaults.subURI).toBe('https://example.com/my_custom_path/'); + }); + + it('keeps cached default settings when a save fails', async () => { + vi.spyOn(HttpUtil, 'post').mockImplementation(async (url) => { + if (url === '/panel/api/setting/update') return new Msg(false, 'Save failed'); + return new Msg(true, '', {}); + }); + const queryClient = makeTestQueryClient(); + const fetchDefaults = vi + .fn() + .mockResolvedValueOnce({ subURI: 'https://example.com/sub/' }) + .mockResolvedValueOnce({ subURI: 'https://example.com/my_custom_path/' }); + const defaultsQuery = { + queryKey: keys.settings.defaults(), + queryFn: fetchDefaults, + staleTime: Infinity, + }; + const wrapper = ({ children }: { children: ReactNode }) => ( + {children} + ); + + await queryClient.fetchQuery(defaultsQuery); + const { result } = renderHook(() => useAllSettings(), { wrapper }); + + await waitFor(() => expect(result.current.fetched).toBe(true)); + await act(async () => { + await result.current.saveAll(); + }); + + const defaults = await queryClient.fetchQuery(defaultsQuery); + expect(fetchDefaults).toHaveBeenCalledOnce(); + expect(defaults.subURI).toBe('https://example.com/sub/'); + }); + it('accepts legacy overlength regex settings without logging a response validation warning', async () => { const subJsonUserAgentRegex = 'x'.repeat(2_049); vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', { subJsonUserAgentRegex }));