forked from Creditra/Creditra-Frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathToastContainer.test.tsx
More file actions
174 lines (136 loc) · 7.34 KB
/
Copy pathToastContainer.test.tsx
File metadata and controls
174 lines (136 loc) · 7.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import { render, screen, act, fireEvent } from '@testing-library/react';
import { vi } from 'vitest';
import { NotificationProvider } from '../../context/NotificationContext';
import { ToastContainer } from './ToastContainer';
import { useToast } from '../../hooks/useToast';
// Helper wrapper: mounts ToastContainer inside the provider and exposes
// the useToast hook so individual tests can fire toasts.
function TestHarness({
onMount,
}: {
onMount?: (toast: ReturnType<typeof useToast>) => void;
}) {
const toast = useToast();
if (onMount) onMount(toast);
return <ToastContainer />;
}
function renderWithProvider(ui: React.ReactElement) {
return render(<NotificationProvider>{ui}</NotificationProvider>);
}
describe('ToastContainer', () => {
beforeEach(() => { vi.useFakeTimers(); });
afterEach(() => { vi.runAllTimers(); vi.useRealTimers(); });
// ─── Severity icons ────────────────────────────────────────────────────────
it('renders an SVG severity icon for each toast', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => {
toastFns.success('S', 'success');
toastFns.error('E', 'error');
toastFns.warning('W', 'warning');
toastFns.info('I', 'info');
});
const icons = document.querySelectorAll('.toast-type-icon svg');
expect(icons.length).toBe(4);
});
it('marks the icon badge as aria-hidden so AT uses the title instead', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => { toastFns.success('Done', 'Saved.'); });
const badge = document.querySelector('.toast-type-icon');
expect(badge).toHaveAttribute('aria-hidden', 'true');
});
// ─── role="status" for non-urgent types ───────────────────────────────────
it('gives success toasts role="status" and aria-live="polite"', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => { toastFns.success('Done', 'All good.'); });
const toast = document.querySelector('.toast-item');
expect(toast).toHaveAttribute('role', 'status');
expect(toast).toHaveAttribute('aria-live', 'polite');
});
it('gives info toasts role="status"', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => { toastFns.info('FYI', 'Note.'); });
expect(document.querySelector('.toast-item')).toHaveAttribute('role', 'status');
});
it('gives warning toasts role="status"', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => { toastFns.warning('Caution', 'Low balance.'); });
expect(document.querySelector('.toast-item')).toHaveAttribute('role', 'status');
});
// ─── role="alert" for urgent types ────────────────────────────────────────
it('gives error toasts role="alert" and aria-live="assertive"', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => { toastFns.error('Failed', 'Could not connect.'); });
const toast = document.querySelector('.toast-item');
expect(toast).toHaveAttribute('role', 'alert');
expect(toast).toHaveAttribute('aria-live', 'assertive');
});
// ─── Theme colors ──────────────────────────────────────────────────────────
it('applies the success theme color to the icon badge', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => { toastFns.success('OK', 'Saved.'); });
const badge = document.querySelector('.toast-type-icon') as HTMLElement;
// TYPE_COLOR.success.icon = '#3fb950'
expect(badge.style.color).toMatch(/3fb950|rgb\(63,\s*185,\s*80\)/i);
});
it('applies the error theme color to the icon badge', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => { toastFns.error('Oops', 'Broke.'); });
const badge = document.querySelector('.toast-type-icon') as HTMLElement;
// TYPE_COLOR.error.icon = '#f85149'
expect(badge.style.color).toMatch(/f85149|rgb\(248,\s*81,\s*73\)/i);
});
it('applies a left accent border colored by severity', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => { toastFns.error('Err', 'Bad.'); });
const item = document.querySelector('.toast-item') as HTMLElement;
// Should have a non-transparent left border (error color = #f85149)
expect(item.style.borderLeft).toContain('4px solid');
});
// ─── Container role ────────────────────────────────────────────────────────
it('wraps all toasts in a log region labelled "Notifications"', () => {
renderWithProvider(<TestHarness />);
const log = screen.getByRole('log', { name: /notifications/i });
expect(log).toBeInTheDocument();
});
// ─── Dismiss ───────────────────────────────────────────────────────────────
it('removes the toast after clicking dismiss', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => { toastFns.info('Hello', 'World.'); });
expect(screen.getByText('Hello')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /dismiss notification/i }));
act(() => { vi.advanceTimersByTime(400); });
expect(screen.queryByText('Hello')).not.toBeInTheDocument();
});
// ─── Edge cases ────────────────────────────────────────────────────────────
it('caps the toast stack at 5 items', () => {
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => {
for (let i = 0; i < 7; i++) {
toastFns.info(`Toast ${i}`, 'msg');
}
});
expect(document.querySelectorAll('.toast-item').length).toBeLessThanOrEqual(5);
});
it('renders an action button when an action is provided', () => {
const onAction = vi.fn();
let toastFns!: ReturnType<typeof useToast>;
renderWithProvider(<TestHarness onMount={(t) => { toastFns = t; }} />);
act(() => {
toastFns.info('With action', 'Click below.', {
action: { label: 'View details', onClick: onAction },
});
});
expect(screen.getByRole('button', { name: /view details/i })).toBeInTheDocument();
});
});