test(260928-ujj): rote Tests fuer Dashboard-Hintergrund in der Datenbank
- PATCH me/dashboard-background: Allowlist, UUID-Bildkennung, Mandantenbindung - getMe liefert dashboardBackground normalisiert - Web: Uebernahme der alten localStorage-Wahl, Hook liest aus dem Auth-Store Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,7 @@ interface FakeUserRow {
|
|||||||
mustChangePassword: boolean;
|
mustChangePassword: boolean;
|
||||||
avatarPath?: string | null;
|
avatarPath?: string | null;
|
||||||
accentColor?: string | null;
|
accentColor?: string | null;
|
||||||
|
dashboardBackground?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BoundCall {
|
interface BoundCall {
|
||||||
@@ -501,6 +502,8 @@ describe('AuthService.getMe', () => {
|
|||||||
mustChangePassword: false,
|
mustChangePassword: false,
|
||||||
avatarPath: 'avatars/u1.png',
|
avatarPath: 'avatars/u1.png',
|
||||||
accentColor: '#3b82f6',
|
accentColor: '#3b82f6',
|
||||||
|
// quick-260928-ujj: gespeicherter Zusatzschluessel wird bei der Ausgabe verworfen.
|
||||||
|
dashboardBackground: { kind: 'preset', id: 'dunes', extra: 'weg' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const ldapUserRow: FakeUserRow = {
|
const ldapUserRow: FakeUserRow = {
|
||||||
@@ -515,6 +518,7 @@ describe('AuthService.getMe', () => {
|
|||||||
mustChangePassword: false,
|
mustChangePassword: false,
|
||||||
avatarPath: null,
|
avatarPath: null,
|
||||||
accentColor: null,
|
accentColor: null,
|
||||||
|
dashboardBackground: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -535,6 +539,7 @@ describe('AuthService.getMe', () => {
|
|||||||
tenantId: 't1',
|
tenantId: 't1',
|
||||||
mustChangePassword: false,
|
mustChangePassword: false,
|
||||||
accentColor: '#3b82f6',
|
accentColor: '#3b82f6',
|
||||||
|
dashboardBackground: { kind: 'preset', id: 'dunes' },
|
||||||
isLocalUser: true,
|
isLocalUser: true,
|
||||||
hasAvatar: true,
|
hasAvatar: true,
|
||||||
});
|
});
|
||||||
@@ -549,6 +554,30 @@ describe('AuthService.getMe', () => {
|
|||||||
expect(result).toMatchObject({ isLocalUser: false, hasAvatar: false });
|
expect(result).toMatchObject({ isLocalUser: false, hasAvatar: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('quick-260928-ujj: dashboardBackground NULL (nie gewaehlt) kommt als null zurueck', async () => {
|
||||||
|
const result = await service.getMe('t1', 'u2');
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('dashboardBackground', null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('quick-260928-ujj: ungueltiger gespeicherter Hintergrund kommt als null zurueck (T-ujj-01)', async () => {
|
||||||
|
prisma.__users.set('u1', {
|
||||||
|
...prisma.__users.get('u1'),
|
||||||
|
dashboardBackground: { kind: 'image', imageId: '") ; background: url("x' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.getMe('t1', 'u1');
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('dashboardBackground', null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('quick-260928-ujj: dashboardBackground steht im select neben accentColor', async () => {
|
||||||
|
await service.getMe('t1', 'u1');
|
||||||
|
|
||||||
|
const call = prisma.__boundCallLog.find((c: any) => c.method === 'findUnique');
|
||||||
|
expect(call.args.select).toMatchObject({ accentColor: true, dashboardBackground: true });
|
||||||
|
});
|
||||||
|
|
||||||
it('FREMDER Mandant (Klient unter t2, Zeile unter t1): liefert null, kein Fehler', async () => {
|
it('FREMDER Mandant (Klient unter t2, Zeile unter t1): liefert null, kein Fehler', async () => {
|
||||||
const result = await service.getMe('t2', 'u1');
|
const result = await service.getMe('t2', 'u1');
|
||||||
|
|
||||||
|
|||||||
@@ -580,4 +580,87 @@ describe('UserController', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* quick-260928-ujj: Dashboard-Hintergrund pro Benutzer in der Datenbank.
|
||||||
|
* Der Selbstbedienungsweg schreibt ausschliesslich die Zeile des
|
||||||
|
* angemeldeten Benutzers ueber den an dessen Mandanten gebundenen Klienten
|
||||||
|
* (T-ujj-02) und nimmt nur die erlaubten Formen an (T-ujj-01): gespeichert
|
||||||
|
* wird immer ein frisch aufgebautes Objekt ohne Zusatzschluessel.
|
||||||
|
*/
|
||||||
|
describe('Dashboard-Hintergrund (quick-260928-ujj)', () => {
|
||||||
|
const me = { role: Role.USER, tenantId: 't1', id: 'u-me', username: 'me', mustChangePassword: false };
|
||||||
|
const IMAGE_ID = '3f2b8c1e-9a4d-4e7f-8b21-0c5d6e7f8a9b';
|
||||||
|
|
||||||
|
function seedMe(tenantId = 't1', id = 'u-me') {
|
||||||
|
prisma.__seedUser({ id, username: id, tenantId, dashboardBackground: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function storedFor(tenantId: string, id: string) {
|
||||||
|
const row = await prisma.__makeBoundClient(tenantId).user.findUnique({ where: { id } });
|
||||||
|
return row.dashboardBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[{ kind: 'none' }, { kind: 'none' }],
|
||||||
|
[{ kind: 'preset', id: 'dunes' }, { kind: 'preset', id: 'dunes' }],
|
||||||
|
[{ kind: 'preset', id: 'mosaic', extra: 'weg' }, { kind: 'preset', id: 'mosaic' }],
|
||||||
|
[{ kind: 'image', imageId: IMAGE_ID }, { kind: 'image', imageId: IMAGE_ID }],
|
||||||
|
[{ kind: 'image', imageId: IMAGE_ID.toUpperCase(), id: 'mist' }, { kind: 'image', imageId: IMAGE_ID.toUpperCase() }],
|
||||||
|
[{ kind: 'none', id: 'dunes', imageId: IMAGE_ID }, { kind: 'none' }],
|
||||||
|
])('%j wird normalisiert als %j gespeichert, gebunden an den eigenen Mandanten', async (input, expected) => {
|
||||||
|
seedMe();
|
||||||
|
|
||||||
|
const result = await controller.updateDashboardBackground({ background: input }, me);
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: true, dashboardBackground: expected });
|
||||||
|
expect(await storedFor('t1', 'u-me')).toEqual(expected);
|
||||||
|
expectBoundCall(prisma, 't1', 'user', 'update');
|
||||||
|
expect(prisma.__boundCallLog.filter((c: any) => c.method === 'update').every((c: any) => c.tenantId === 't1')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[{ kind: 'preset', id: 'regenbogen' }],
|
||||||
|
[{ kind: 'preset' }],
|
||||||
|
[{ kind: 'preset', id: 42 }],
|
||||||
|
[{ kind: 'gradient', id: 'mist' }],
|
||||||
|
[{ kind: 'image', imageId: 'img-1' }],
|
||||||
|
[{ kind: 'image', imageId: '") ; background: url("https://boese.invalid/x' }],
|
||||||
|
[{ kind: 'image', imageId: `${IMAGE_ID})` }],
|
||||||
|
[{ kind: 'image', imageId: `${IMAGE_ID}0` }],
|
||||||
|
[{ kind: 'image', imageId: '' }],
|
||||||
|
[{ kind: 'image' }],
|
||||||
|
[{}],
|
||||||
|
[null],
|
||||||
|
[undefined],
|
||||||
|
['none'],
|
||||||
|
[42],
|
||||||
|
[[{ kind: 'none' }]],
|
||||||
|
])('ungueltige Wahl %j → BadRequestException, nichts geschrieben', async (background) => {
|
||||||
|
seedMe();
|
||||||
|
|
||||||
|
await expect(controller.updateDashboardBackground({ background }, me)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
expect(prisma.__boundCallLog.some((c: any) => c.method === 'update')).toBe(false);
|
||||||
|
expect(await storedFor('t1', 'u-me')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fehlender Rumpf → BadRequestException', async () => {
|
||||||
|
seedMe();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.updateDashboardBackground(undefined as unknown as { background: unknown }, me),
|
||||||
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
expect(prisma.__boundCallLog.some((c: any) => c.method === 'update')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('zwei Benutzer in zwei Mandanten: nur die Zeile des Anfragenden aendert sich', async () => {
|
||||||
|
seedMe('t1', 'u-me');
|
||||||
|
seedMe('t2', 'u-other');
|
||||||
|
|
||||||
|
await controller.updateDashboardBackground({ background: { kind: 'preset', id: 'mist' } }, me);
|
||||||
|
|
||||||
|
expect(await storedFor('t1', 'u-me')).toEqual({ kind: 'preset', id: 'mist' });
|
||||||
|
expect(await storedFor('t2', 'u-other')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { type AuthUser, useAuthStore } from '@/lib/stores/auth-store';
|
||||||
|
|
||||||
|
// quick-260928-ujj: der Dashboard-Hintergrund steht pro Benutzer in der
|
||||||
|
// Datenbank. Der Hook liest ihn aus dem Auth-Store (Sitzungsantwort) und
|
||||||
|
// speichert ueber die Server-Aktion; eine alte localStorage-Wahl wird
|
||||||
|
// einmalig uebernommen.
|
||||||
|
|
||||||
|
const updateDashboardBackgroundAction = vi.fn();
|
||||||
|
|
||||||
|
vi.mock('@/lib/auth-actions', () => ({
|
||||||
|
updateDashboardBackgroundAction: (...args: unknown[]) => updateDashboardBackgroundAction(...args),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const LEGACY_KEY = 'tessera.dashboardBackground.u1';
|
||||||
|
|
||||||
|
function makeUser(overrides: Partial<AuthUser> = {}): AuthUser {
|
||||||
|
return {
|
||||||
|
id: 'u1',
|
||||||
|
username: 'schalli',
|
||||||
|
displayName: 'Schalli',
|
||||||
|
role: 'USER',
|
||||||
|
tenantId: 't1',
|
||||||
|
accentColor: null,
|
||||||
|
dashboardBackground: null,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderBackgroundHook() {
|
||||||
|
const { useDashboardBackground } = await import('./dashboard-background');
|
||||||
|
return renderHook(() => useDashboardBackground());
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
updateDashboardBackgroundAction.mockReset();
|
||||||
|
updateDashboardBackgroundAction.mockResolvedValue({ success: true });
|
||||||
|
useAuthStore.setState({ user: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
window.localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useDashboardBackground (quick-260928-ujj)', () => {
|
||||||
|
it('ohne angemeldeten Benutzer: kein Hintergrund, kein Speichern', async () => {
|
||||||
|
const { result } = await renderBackgroundHook();
|
||||||
|
|
||||||
|
expect(result.current.background).toEqual({ kind: 'none' });
|
||||||
|
expect(updateDashboardBackgroundAction).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('liest die Wahl aus dem Auth-Store; null ergibt „kein Hintergrund“', async () => {
|
||||||
|
useAuthStore.setState({ user: makeUser({ dashboardBackground: { kind: 'preset', id: 'dunes' } }) });
|
||||||
|
const { result } = await renderBackgroundHook();
|
||||||
|
expect(result.current.background).toEqual({ kind: 'preset', id: 'dunes' });
|
||||||
|
|
||||||
|
act(() => useAuthStore.setState({ user: makeUser({ dashboardBackground: null }) }));
|
||||||
|
expect(result.current.background).toEqual({ kind: 'none' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('choose() setzt den Store sofort und speichert ueber die Server-Aktion', async () => {
|
||||||
|
useAuthStore.setState({ user: makeUser({ dashboardBackground: { kind: 'none' } }) });
|
||||||
|
const { result } = await renderBackgroundHook();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.choose({ kind: 'preset', id: 'mosaic' });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useAuthStore.getState().user?.dashboardBackground).toEqual({ kind: 'preset', id: 'mosaic' });
|
||||||
|
expect(result.current.background).toEqual({ kind: 'preset', id: 'mosaic' });
|
||||||
|
expect(updateDashboardBackgroundAction).toHaveBeenCalledWith({ kind: 'preset', id: 'mosaic' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['Fehlerantwort', () => updateDashboardBackgroundAction.mockResolvedValue({ success: false, error: 'saveError' })],
|
||||||
|
['Ausnahme', () => updateDashboardBackgroundAction.mockRejectedValue(new Error('offline'))],
|
||||||
|
])('Speichern schlaegt fehl (%s): der vorige Wert wird zurueckgesetzt', async (_label, arrange) => {
|
||||||
|
arrange();
|
||||||
|
useAuthStore.setState({ user: makeUser({ dashboardBackground: { kind: 'preset', id: 'dunes' } }) });
|
||||||
|
const { result } = await renderBackgroundHook();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.choose({ kind: 'preset', id: 'mist' });
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.background).toEqual({ kind: 'preset', id: 'dunes' }));
|
||||||
|
expect(useAuthStore.getState().user?.dashboardBackground).toEqual({ kind: 'preset', id: 'dunes' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Server-Wert null und alte localStorage-Wahl: wird genau einmal gespeichert, Schluessel entfernt', async () => {
|
||||||
|
window.localStorage.setItem(LEGACY_KEY, JSON.stringify({ kind: 'preset', id: 'pebble' }));
|
||||||
|
useAuthStore.setState({ user: makeUser({ dashboardBackground: null }) });
|
||||||
|
|
||||||
|
const { result, rerender } = await renderBackgroundHook();
|
||||||
|
|
||||||
|
await waitFor(() => expect(updateDashboardBackgroundAction).toHaveBeenCalledTimes(1));
|
||||||
|
expect(updateDashboardBackgroundAction).toHaveBeenCalledWith({ kind: 'preset', id: 'pebble' });
|
||||||
|
expect(result.current.background).toEqual({ kind: 'preset', id: 'pebble' });
|
||||||
|
expect(window.localStorage.getItem(LEGACY_KEY)).toBeNull();
|
||||||
|
|
||||||
|
// erneutes Rendern und ein neues Benutzerobjekt desselben Benutzers loesen nichts mehr aus
|
||||||
|
rerender();
|
||||||
|
act(() => useAuthStore.setState({ user: makeUser({ dashboardBackground: null }) }));
|
||||||
|
rerender();
|
||||||
|
expect(updateDashboardBackgroundAction).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Server-Wert gesetzt: keine Uebernahme, der alte Schluessel wird nur aufgeraeumt', async () => {
|
||||||
|
window.localStorage.setItem(LEGACY_KEY, JSON.stringify({ kind: 'preset', id: 'pebble' }));
|
||||||
|
useAuthStore.setState({ user: makeUser({ dashboardBackground: { kind: 'none' } }) });
|
||||||
|
|
||||||
|
const { result } = await renderBackgroundHook();
|
||||||
|
|
||||||
|
await waitFor(() => expect(window.localStorage.getItem(LEGACY_KEY)).toBeNull());
|
||||||
|
expect(updateDashboardBackgroundAction).not.toHaveBeenCalled();
|
||||||
|
expect(result.current.background).toEqual({ kind: 'none' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Server-Wert null ohne alte Wahl: nichts wird gespeichert', async () => {
|
||||||
|
useAuthStore.setState({ user: makeUser({ dashboardBackground: null }) });
|
||||||
|
|
||||||
|
const { result } = await renderBackgroundHook();
|
||||||
|
|
||||||
|
expect(result.current.background).toEqual({ kind: 'none' });
|
||||||
|
expect(updateDashboardBackgroundAction).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -103,6 +103,7 @@ describe('Header — Sitzungswaechter (quick-260917-gyd)', () => {
|
|||||||
tenantId: 't1',
|
tenantId: 't1',
|
||||||
hasAvatar: false,
|
hasAvatar: false,
|
||||||
accentColor: null,
|
accentColor: null,
|
||||||
|
dashboardBackground: { kind: 'preset', id: 'dunes' },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,6 +112,8 @@ describe('Header — Sitzungswaechter (quick-260917-gyd)', () => {
|
|||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(useAuthStore.getState().user?.username).toBe('schalli'),
|
expect(useAuthStore.getState().user?.username).toBe('schalli'),
|
||||||
);
|
);
|
||||||
|
// quick-260928-ujj: der Hintergrund kommt mit derselben Sitzungsantwort wie accentColor.
|
||||||
|
expect(useAuthStore.getState().user?.dashboardBackground).toEqual({ kind: 'preset', id: 'dunes' });
|
||||||
expect((window as any).location.href).toBe('');
|
expect((window as any).location.href).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +1,72 @@
|
|||||||
|
import { DASHBOARD_BACKGROUND_PRESET_IDS } from '@tessera/shared';
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
import {
|
import {
|
||||||
BACKGROUND_PRESETS,
|
BACKGROUND_PRESETS,
|
||||||
DARK_FALLBACK_ID,
|
DARK_FALLBACK_ID,
|
||||||
loadDashboardBackground,
|
|
||||||
presetBackground,
|
presetBackground,
|
||||||
saveDashboardBackground,
|
takeLegacyDashboardBackground,
|
||||||
} from './dashboard-background';
|
} from './dashboard-background';
|
||||||
|
|
||||||
|
const IMAGE_ID = '3f2b8c1e-9a4d-4e7f-8b21-0c5d6e7f8a9b';
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('dashboard-background (Design „Mosaik“, Prototyp)', () => {
|
describe('dashboard-background (Design „Mosaik“)', () => {
|
||||||
it('ohne gespeicherte Wahl: kein Hintergrund', () => {
|
describe('takeLegacyDashboardBackground (quick-260928-ujj)', () => {
|
||||||
expect(loadDashboardBackground('u1')).toEqual({ kind: 'none' });
|
it('ohne alte Wahl: null', () => {
|
||||||
|
expect(takeLegacyDashboardBackground('u1')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('speichert je Benutzer getrennt', () => {
|
it('liefert eine gueltige alte Wahl je Benutzer und entfernt den Schluessel', () => {
|
||||||
saveDashboardBackground('u1', { kind: 'preset', id: 'bloom' });
|
|
||||||
saveDashboardBackground('u2', { kind: 'image', imageId: 'img-1' });
|
|
||||||
expect(loadDashboardBackground('u1')).toEqual({ kind: 'preset', id: 'bloom' });
|
|
||||||
expect(loadDashboardBackground('u2')).toEqual({ kind: 'image', imageId: 'img-1' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('verwirft kaputte oder unbekannte Werte', () => {
|
|
||||||
window.localStorage.setItem('tessera.dashboardBackground.u1', '{kaputt');
|
|
||||||
expect(loadDashboardBackground('u1')).toEqual({ kind: 'none' });
|
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
'tessera.dashboardBackground.u1',
|
'tessera.dashboardBackground.u1',
|
||||||
JSON.stringify({ kind: 'preset', id: 'regenbogen' }),
|
JSON.stringify({ kind: 'preset', id: 'bloom' }),
|
||||||
);
|
);
|
||||||
expect(loadDashboardBackground('u1')).toEqual({ kind: 'none' });
|
window.localStorage.setItem(
|
||||||
|
'tessera.dashboardBackground.u2',
|
||||||
|
JSON.stringify({ kind: 'image', imageId: IMAGE_ID }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(takeLegacyDashboardBackground('u1')).toEqual({ kind: 'preset', id: 'bloom' });
|
||||||
|
expect(window.localStorage.getItem('tessera.dashboardBackground.u1')).toBeNull();
|
||||||
|
// der Schluessel des anderen Benutzers bleibt, bis dieser sich anmeldet
|
||||||
|
expect(window.localStorage.getItem('tessera.dashboardBackground.u2')).not.toBeNull();
|
||||||
|
expect(takeLegacyDashboardBackground('u2')).toEqual({ kind: 'image', imageId: IMAGE_ID });
|
||||||
|
// zweiter Aufruf: nichts mehr da
|
||||||
|
expect(takeLegacyDashboardBackground('u1')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gesperrter Speicher wirft nicht', () => {
|
it.each([
|
||||||
|
['{kaputt'],
|
||||||
|
[JSON.stringify({ kind: 'preset', id: 'regenbogen' })],
|
||||||
|
// Prototyp-Werte mit beliebiger Bildkennung werden nicht uebernommen
|
||||||
|
[JSON.stringify({ kind: 'image', imageId: 'img-1' })],
|
||||||
|
[JSON.stringify(null)],
|
||||||
|
])('ungueltiger Wert %j: null, Schluessel entfernt', (raw) => {
|
||||||
|
window.localStorage.setItem('tessera.dashboardBackground.u1', raw);
|
||||||
|
|
||||||
|
expect(takeLegacyDashboardBackground('u1')).toBeNull();
|
||||||
|
expect(window.localStorage.getItem('tessera.dashboardBackground.u1')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gesperrter Speicher: null ohne Ausnahme', () => {
|
||||||
vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
|
vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
|
||||||
throw new Error('blocked');
|
throw new Error('blocked');
|
||||||
});
|
});
|
||||||
vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
|
vi.spyOn(Storage.prototype, 'removeItem').mockImplementation(() => {
|
||||||
throw new Error('blocked');
|
throw new Error('blocked');
|
||||||
});
|
});
|
||||||
expect(() => saveDashboardBackground('u1', { kind: 'preset', id: 'mist' })).not.toThrow();
|
|
||||||
expect(loadDashboardBackground('u1')).toEqual({ kind: 'none' });
|
expect(() => takeLegacyDashboardBackground('u1')).not.toThrow();
|
||||||
|
expect(takeLegacyDashboardBackground('u1')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('die eingebauten Hintergruende sind deckungsgleich mit der Pruefregel in @tessera/shared', () => {
|
||||||
|
expect(BACKGROUND_PRESETS.map((p) => p.id).sort()).toEqual([...DASHBOARD_BACKGROUND_PRESET_IDS].sort());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('jeder eingebaute Hintergrund liefert hell und dunkel ein Bild (dunkel notfalls den Ersatz)', () => {
|
it('jeder eingebaute Hintergrund liefert hell und dunkel ein Bild (dunkel notfalls den Ersatz)', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user