Spaces:
Sleeping
Sleeping
| // Copyright (c) 2025-2026, RTE (https://www.rte-france.com) | |
| // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. | |
| // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, | |
| // you can obtain one at http://mozilla.org/MPL/2.0/. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; | |
| import { | |
| notifications, | |
| notifyError, | |
| notifyInfo, | |
| notifySuccess, | |
| dismissNotification, | |
| clearNotifications, | |
| DEFAULT_TIMEOUT_MS, | |
| } from './notifications'; | |
| const snap = () => notifications.getSnapshot(); | |
| describe('notification store', () => { | |
| beforeEach(() => { notifications.clear(); }); | |
| it('raises a notification with the given severity + message', () => { | |
| notifyError('boom'); | |
| expect(snap()).toHaveLength(1); | |
| expect(snap()[0]).toMatchObject({ severity: 'error', message: 'boom' }); | |
| }); | |
| it('trims and ignores empty messages', () => { | |
| expect(notifyInfo(' ')).toBe(-1); | |
| expect(snap()).toHaveLength(0); | |
| }); | |
| it('makes errors sticky and info/success non-sticky by default', () => { | |
| notifyError('e'); | |
| notifyInfo('i'); | |
| notifySuccess('s'); | |
| const bySeverity = Object.fromEntries(snap().map(n => [n.severity, n.sticky])); | |
| expect(bySeverity).toEqual({ error: true, info: false, success: false }); | |
| }); | |
| it('de-dupes an identical severity+message instead of stacking', () => { | |
| notifyError('same'); | |
| notifyError('same'); | |
| expect(snap()).toHaveLength(1); | |
| // A different message, or a different severity, is a separate toast. | |
| notifyError('other'); | |
| notifyInfo('same'); | |
| expect(snap()).toHaveLength(3); | |
| }); | |
| it('dismisses a single notification by id', () => { | |
| const id = notifyError('x'); | |
| notifyError('y'); | |
| dismissNotification(id); | |
| expect(snap().map(n => n.message)).toEqual(['y']); | |
| }); | |
| it('clears a whole severity', () => { | |
| notifyError('e1'); | |
| notifyError('e2'); | |
| notifyInfo('keep'); | |
| notifications.clearSeverity('error'); | |
| expect(snap().map(n => n.message)).toEqual(['keep']); | |
| }); | |
| it('clears everything', () => { | |
| notifyError('e'); | |
| notifyInfo('i'); | |
| clearNotifications(); | |
| expect(snap()).toHaveLength(0); | |
| }); | |
| it('notifies subscribers on change', () => { | |
| const listener = vi.fn(); | |
| const unsub = notifications.subscribe(listener); | |
| notifyError('a'); | |
| expect(listener).toHaveBeenCalledTimes(1); | |
| unsub(); | |
| notifyError('b'); | |
| expect(listener).toHaveBeenCalledTimes(1); | |
| }); | |
| it('returns a stable snapshot reference between mutations', () => { | |
| notifyError('a'); | |
| const first = snap(); | |
| expect(snap()).toBe(first); // no mutation → same array reference | |
| notifyInfo('b'); | |
| expect(snap()).not.toBe(first); // mutation → new reference | |
| }); | |
| describe('auto-expiry', () => { | |
| beforeEach(() => { vi.useFakeTimers(); }); | |
| afterEach(() => { vi.useRealTimers(); }); | |
| it('auto-dismisses non-sticky toasts after the timeout', () => { | |
| notifyInfo('temp'); | |
| expect(snap()).toHaveLength(1); | |
| vi.advanceTimersByTime(DEFAULT_TIMEOUT_MS); | |
| expect(snap()).toHaveLength(0); | |
| }); | |
| it('keeps sticky (error) toasts past the timeout', () => { | |
| notifyError('stays'); | |
| vi.advanceTimersByTime(DEFAULT_TIMEOUT_MS * 3); | |
| expect(snap()).toHaveLength(1); | |
| }); | |
| }); | |
| }); | |