forked from Talenttrust/Talenttrust-Frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBreadcrumbs.test.tsx
More file actions
224 lines (183 loc) · 8.72 KB
/
Copy pathBreadcrumbs.test.tsx
File metadata and controls
224 lines (183 loc) · 8.72 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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import React from 'react';
import { render, screen, within } from '@testing-library/react';
import Breadcrumbs, { BreadcrumbItem } from '../Breadcrumbs';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const THREE_CRUMBS: BreadcrumbItem[] = [
{ label: 'Dashboard', href: '/' },
{ label: 'Contracts', href: '/contracts' },
{ label: 'Contract #42' },
];
const TWO_CRUMBS: BreadcrumbItem[] = [
{ label: 'Home', href: '/' },
{ label: 'Settings' },
];
const ONE_CRUMB: BreadcrumbItem[] = [{ label: 'Dashboard', href: '/' }];
// ---------------------------------------------------------------------------
// Structure & ARIA
// ---------------------------------------------------------------------------
describe('Breadcrumbs — structure and ARIA', () => {
it('renders a <nav> with aria-label="Breadcrumb"', () => {
render(<Breadcrumbs items={THREE_CRUMBS} />);
expect(screen.getByRole('navigation', { name: 'Breadcrumb' })).toBeInTheDocument();
});
it('contains an ordered list (<ol>) inside the nav', () => {
const { container } = render(<Breadcrumbs items={THREE_CRUMBS} />);
const nav = screen.getByRole('navigation', { name: 'Breadcrumb' });
expect(nav.querySelector('ol')).toBeInTheDocument();
// ol must be a direct or nested child — confirm via container
expect(container.querySelector('nav > ol')).toBeInTheDocument();
});
it('renders one <li> per breadcrumb item', () => {
const { container } = render(<Breadcrumbs items={THREE_CRUMBS} />);
const ol = container.querySelector('ol') as HTMLOListElement;
expect(ol.querySelectorAll(':scope > li')).toHaveLength(THREE_CRUMBS.length);
});
it('renders nothing when items array is empty', () => {
const { container } = render(<Breadcrumbs items={[]} />);
expect(container.firstChild).toBeNull();
});
});
// ---------------------------------------------------------------------------
// Link generation
// ---------------------------------------------------------------------------
describe('Breadcrumbs — link generation', () => {
it('renders ancestor crumbs as links with correct hrefs', () => {
render(<Breadcrumbs items={THREE_CRUMBS} />);
const dashboardLink = screen.getByRole('link', { name: 'Dashboard' });
const contractsLink = screen.getByRole('link', { name: 'Contracts' });
expect(dashboardLink).toBeInTheDocument();
expect(dashboardLink).toHaveAttribute('href', '/');
expect(contractsLink).toBeInTheDocument();
expect(contractsLink).toHaveAttribute('href', '/contracts');
});
it('does not render the final crumb as a link', () => {
render(<Breadcrumbs items={THREE_CRUMBS} />);
// No <a> with text "Contract #42"
expect(screen.queryByRole('link', { name: /Contract #42/i })).not.toBeInTheDocument();
// The label must still be visible as text
expect(screen.getByText('Contract #42')).toBeInTheDocument();
});
it('renders all links with their labels for two-crumb trail', () => {
render(<Breadcrumbs items={TWO_CRUMBS} />);
expect(screen.getByRole('link', { name: 'Home' })).toHaveAttribute('href', '/');
expect(screen.queryByRole('link', { name: 'Settings' })).not.toBeInTheDocument();
});
it('single-crumb list renders the sole crumb without a link', () => {
render(<Breadcrumbs items={ONE_CRUMB} />);
// Even though it has an href, it is the final crumb — must not be a link
expect(screen.queryByRole('link', { name: 'Dashboard' })).not.toBeInTheDocument();
expect(screen.getByText('Dashboard')).toBeInTheDocument();
});
});
// ---------------------------------------------------------------------------
// aria-current
// ---------------------------------------------------------------------------
describe('Breadcrumbs — aria-current', () => {
it('applies aria-current="page" only to the final crumb', () => {
render(<Breadcrumbs items={THREE_CRUMBS} />);
const currentEl = screen.getByText('Contract #42');
expect(currentEl).toHaveAttribute('aria-current', 'page');
});
it('does not apply aria-current to any ancestor crumb', () => {
render(<Breadcrumbs items={THREE_CRUMBS} />);
expect(screen.getByRole('link', { name: 'Dashboard' })).not.toHaveAttribute('aria-current');
expect(screen.getByRole('link', { name: 'Contracts' })).not.toHaveAttribute('aria-current');
});
it('applies aria-current="page" correctly in a two-crumb trail', () => {
render(<Breadcrumbs items={TWO_CRUMBS} />);
expect(screen.getByText('Settings')).toHaveAttribute('aria-current', 'page');
expect(screen.getByRole('link', { name: 'Home' })).not.toHaveAttribute('aria-current');
});
it('applies aria-current="page" to a single-crumb list', () => {
render(<Breadcrumbs items={ONE_CRUMB} />);
expect(screen.getByText('Dashboard')).toHaveAttribute('aria-current', 'page');
});
});
// ---------------------------------------------------------------------------
// Separators
// ---------------------------------------------------------------------------
describe('Breadcrumbs — separators', () => {
it('renders aria-hidden separators between crumbs', () => {
const { container } = render(<Breadcrumbs items={THREE_CRUMBS} />);
const separators = container.querySelectorAll('[aria-hidden="true"]');
// 3 crumbs → 2 separators (one between each adjacent pair)
expect(separators).toHaveLength(2);
});
it('renders no separator before the first crumb', () => {
const { container } = render(<Breadcrumbs items={THREE_CRUMBS} />);
const firstLi = container.querySelector('ol > li:first-child');
expect(firstLi?.querySelector('[aria-hidden="true"]')).toBeNull();
});
});
// ---------------------------------------------------------------------------
// Focus ring
// ---------------------------------------------------------------------------
describe('Breadcrumbs — focus ring', () => {
it('applies the theme-token focus ring to ancestor links', () => {
render(<Breadcrumbs items={THREE_CRUMBS} />);
const dashboardLink = screen.getByRole('link', { name: 'Dashboard' });
expect(dashboardLink.className).toContain('focus-visible:ring-2');
expect(dashboardLink.className).toContain('focus-visible:ring-[var(--ring)]');
expect(dashboardLink.className).toContain('focus-visible:ring-offset-2');
});
it('does not use a hardcoded outline color for the focus ring', () => {
// Regression guard: outline-blue-500 doesn't match --ring in light mode
// (#2563eb) and was never theme-aware for dark mode either.
render(<Breadcrumbs items={THREE_CRUMBS} />);
const dashboardLink = screen.getByRole('link', { name: 'Dashboard' });
expect(dashboardLink.className).not.toContain('outline-blue-500');
});
it('applies the focus ring to every ancestor link, not just the first', () => {
render(<Breadcrumbs items={THREE_CRUMBS} />);
const contractsLink = screen.getByRole('link', { name: 'Contracts' });
expect(contractsLink.className).toContain('focus-visible:ring-[var(--ring)]');
});
});
// ---------------------------------------------------------------------------
// Dynamic label (contract id interpolation)
// ---------------------------------------------------------------------------
describe('Breadcrumbs — dynamic labels', () => {
it('reflects the contract id in the final crumb label', () => {
const id = 'abc-123';
render(
<Breadcrumbs
items={[
{ label: 'Dashboard', href: '/' },
{ label: 'Contracts', href: '/contracts' },
{ label: `Contract #${id}` },
]}
/>,
);
const currentEl = screen.getByText(`Contract #${id}`);
expect(currentEl).toHaveAttribute('aria-current', 'page');
expect(screen.queryByRole('link', { name: `Contract #${id}` })).not.toBeInTheDocument();
});
it('renders all three crumbs from the contract detail page scenario', () => {
render(
<Breadcrumbs
items={[
{ label: 'Dashboard', href: '/' },
{ label: 'Contracts', href: '/contracts' },
{ label: 'Contract #99' },
]}
/>,
);
const nav = screen.getByRole('navigation', { name: 'Breadcrumb' });
expect(within(nav).getByRole('link', { name: 'Dashboard' })).toBeInTheDocument();
expect(within(nav).getByRole('link', { name: 'Contracts' })).toBeInTheDocument();
expect(within(nav).getByText('Contract #99')).toHaveAttribute('aria-current', 'page');
});
it('falls back to "/" for an ancestor crumb with no href', () => {
render(
<Breadcrumbs
items={[
{ label: 'Untitled' },
{ label: 'Current' },
]}
/>,
);
expect(screen.getByRole('link', { name: 'Untitled' })).toHaveAttribute('href', '/');
});
});