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 }));