mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-09-16 02:22:17 +00:00
56b676b04a
Both fired during hydration and competed with rendering the grid, and the layout waited for the user store before it could show a signed-in shell even though the server already knew who the user was. Seed the user from the layout data, and queue the offline sync and backup refresh behind the grid's interactive mark; an explicit refresh, a reconnect or an edit still runs straight away. Backup refreshes now share one in-flight request per generation, and a change of account cancels the pending startup refresh and clears the stale status.
188 lines
5.5 KiB
TypeScript
188 lines
5.5 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { get } from 'svelte/store';
|
|
import {
|
|
backupsNeedingReconnect,
|
|
clearBackupStatus,
|
|
markReconnectNeeded,
|
|
refreshBackupStatus,
|
|
setBackupStatus
|
|
} from '$lib/stores/backupStatus';
|
|
|
|
describe('backup reconnect status', () => {
|
|
beforeEach(() => clearBackupStatus());
|
|
|
|
it('lists only the providers that exports switched off', () => {
|
|
setBackupStatus([
|
|
{ provider: 'google_drive', enabled: false },
|
|
{ provider: 'dropbox', enabled: true }
|
|
]);
|
|
expect(get(backupsNeedingReconnect)).toEqual(['google_drive']);
|
|
});
|
|
|
|
it('adds newly revoked providers without duplicating known ones', () => {
|
|
markReconnectNeeded(['google_drive']);
|
|
markReconnectNeeded(['google_drive', 'dropbox']);
|
|
expect(get(backupsNeedingReconnect)).toEqual(['google_drive', 'dropbox']);
|
|
});
|
|
|
|
it('clears everything, e.g. on sign-out', () => {
|
|
markReconnectNeeded(['dropbox']);
|
|
clearBackupStatus();
|
|
expect(get(backupsNeedingReconnect)).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('refreshBackupStatus', () => {
|
|
const fetchMock = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
clearBackupStatus();
|
|
fetchMock.mockReset();
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
vi.stubGlobal('window', {});
|
|
vi.stubGlobal('navigator', { onLine: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('loads the paused providers from the integrations API', async () => {
|
|
fetchMock.mockResolvedValue(
|
|
new Response(
|
|
JSON.stringify([
|
|
{ provider: 'google_drive', enabled: true },
|
|
{ provider: 'dropbox', enabled: false }
|
|
])
|
|
)
|
|
);
|
|
|
|
await refreshBackupStatus();
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith('/api/export-integrations', {
|
|
credentials: 'include'
|
|
});
|
|
expect(get(backupsNeedingReconnect)).toEqual(['dropbox']);
|
|
});
|
|
|
|
it('clears a stale warning once the provider has been reconnected', async () => {
|
|
markReconnectNeeded(['google_drive']);
|
|
fetchMock.mockResolvedValue(
|
|
new Response(JSON.stringify([{ provider: 'google_drive', enabled: true }]))
|
|
);
|
|
|
|
await refreshBackupStatus();
|
|
|
|
expect(get(backupsNeedingReconnect)).toEqual([]);
|
|
});
|
|
|
|
it('does nothing while offline', async () => {
|
|
vi.stubGlobal('navigator', { onLine: false });
|
|
markReconnectNeeded(['google_drive']);
|
|
|
|
await refreshBackupStatus();
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
expect(get(backupsNeedingReconnect)).toEqual(['google_drive']);
|
|
});
|
|
|
|
it('does nothing during server rendering', async () => {
|
|
vi.stubGlobal('window', undefined);
|
|
|
|
await refreshBackupStatus();
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('keeps the last known status when the API rejects the request', async () => {
|
|
markReconnectNeeded(['google_drive']);
|
|
fetchMock.mockResolvedValue(new Response('Unauthorized', { status: 401 }));
|
|
|
|
await refreshBackupStatus();
|
|
|
|
expect(get(backupsNeedingReconnect)).toEqual(['google_drive']);
|
|
});
|
|
|
|
function deferredResponse() {
|
|
let resolve!: (response: Response) => void;
|
|
const promise = new Promise<Response>((r) => (resolve = r));
|
|
return { promise, resolve };
|
|
}
|
|
|
|
const json = (body: unknown) => new Response(JSON.stringify(body));
|
|
|
|
it('coalesces concurrent refreshes for the same account state', async () => {
|
|
const slow = deferredResponse();
|
|
fetchMock.mockReturnValueOnce(slow.promise);
|
|
const first = refreshBackupStatus();
|
|
const second = refreshBackupStatus();
|
|
expect(second).toBe(first);
|
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
slow.resolve(json([]));
|
|
await Promise.all([first, second]);
|
|
});
|
|
|
|
it('ignores a response overtaken by a new account refresh', async () => {
|
|
const slow = deferredResponse();
|
|
fetchMock
|
|
.mockReturnValueOnce(slow.promise)
|
|
.mockResolvedValueOnce(json([{ provider: 'google_drive', enabled: true }]));
|
|
|
|
const first = refreshBackupStatus();
|
|
clearBackupStatus();
|
|
await refreshBackupStatus();
|
|
slow.resolve(json([{ provider: 'google_drive', enabled: false }]));
|
|
await first;
|
|
|
|
expect(get(backupsNeedingReconnect)).toEqual([]);
|
|
});
|
|
|
|
it('ignores a response that arrives after the status was flagged directly', async () => {
|
|
const slow = deferredResponse();
|
|
fetchMock.mockReturnValueOnce(slow.promise);
|
|
|
|
const pending = refreshBackupStatus();
|
|
markReconnectNeeded(['dropbox']);
|
|
slow.resolve(json([{ provider: 'dropbox', enabled: true }]));
|
|
await pending;
|
|
|
|
expect(get(backupsNeedingReconnect)).toEqual(['dropbox']);
|
|
});
|
|
|
|
it('ignores a response that arrives after the status was cleared', async () => {
|
|
const slow = deferredResponse();
|
|
fetchMock.mockReturnValueOnce(slow.promise);
|
|
|
|
const pending = refreshBackupStatus();
|
|
clearBackupStatus();
|
|
slow.resolve(json([{ provider: 'google_drive', enabled: false }]));
|
|
await pending;
|
|
|
|
expect(get(backupsNeedingReconnect)).toEqual([]);
|
|
});
|
|
|
|
it('ignores a response that arrives after the status was set from another source', async () => {
|
|
const slow = deferredResponse();
|
|
fetchMock.mockReturnValueOnce(slow.promise);
|
|
|
|
const pending = refreshBackupStatus();
|
|
setBackupStatus([{ provider: 'google_drive', enabled: true }]);
|
|
slow.resolve(json([{ provider: 'google_drive', enabled: false }]));
|
|
await pending;
|
|
|
|
expect(get(backupsNeedingReconnect)).toEqual([]);
|
|
});
|
|
|
|
it('keeps the last known status when the request fails', async () => {
|
|
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
markReconnectNeeded(['dropbox']);
|
|
fetchMock.mockRejectedValue(new TypeError('Failed to fetch'));
|
|
|
|
await refreshBackupStatus();
|
|
|
|
expect(get(backupsNeedingReconnect)).toEqual(['dropbox']);
|
|
expect(consoleError).toHaveBeenCalled();
|
|
});
|
|
});
|