@micha.bigler/ui-core-micha 2.32.0 → 2.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/WORK_ORDERS.md +2 -0
- package/dev/entries.jsx +56 -0
- package/dev/main.jsx +11 -5
- package/dist/components/MobileBottomNav.js +44 -0
- package/dist/index.js +1 -0
- package/dist/theme/createAppTheme.js +15 -0
- package/dist/theme/themeCompleteness.js +16 -0
- package/dist/theme/tokens.js +24 -0
- package/package.json +1 -1
- package/src/components/MobileBottomNav.jsx +82 -0
- package/src/index.js +1 -0
- package/src/theme/createAppTheme.js +15 -0
- package/src/theme/themeCompleteness.js +16 -0
- package/src/theme/tokens.js +24 -0
- package/tests/MobileBottomNav.test.jsx +319 -0
- package/tests/createAppTheme.test.js +22 -1
- package/tests/themeCompleteness.test.js +48 -1
- package/work-orders/SHELL-3.md +359 -0
- package/work-orders/SHELL-4.md +351 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react';
|
|
5
|
+
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
MOBILE_BOTTOM_NAV_ACTION_SX,
|
|
9
|
+
MOBILE_BOTTOM_NAV_ROOT_SX,
|
|
10
|
+
MobileBottomNav,
|
|
11
|
+
} from '../src/components/MobileBottomNav';
|
|
12
|
+
import { createAppTheme } from '../src/theme/createAppTheme';
|
|
13
|
+
|
|
14
|
+
const theme = createAppTheme({ palette: { primary: { main: '#0F62FE' } } });
|
|
15
|
+
|
|
16
|
+
function Icon() {
|
|
17
|
+
return <svg aria-hidden="true" data-testid="destination-icon" />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const destinations = [
|
|
21
|
+
{ route: '/first', label: 'First', icon: Icon },
|
|
22
|
+
{ route: '/second', label: 'Second', icon: Icon },
|
|
23
|
+
{ route: '/third', label: 'Third', icon: Icon },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function setMediaQueryMatches(matches) {
|
|
27
|
+
window.matchMedia = vi.fn().mockImplementation((query) => ({
|
|
28
|
+
matches: typeof matches === 'function' ? matches(query) : matches,
|
|
29
|
+
media: query,
|
|
30
|
+
onchange: null,
|
|
31
|
+
addEventListener: vi.fn(),
|
|
32
|
+
removeEventListener: vi.fn(),
|
|
33
|
+
addListener: vi.fn(),
|
|
34
|
+
removeListener: vi.fn(),
|
|
35
|
+
dispatchEvent: vi.fn(),
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function setViewportWidth(width) {
|
|
40
|
+
setMediaQueryMatches((query) => {
|
|
41
|
+
const minWidth = query.match(/min-width:(\d+)px/)?.[1];
|
|
42
|
+
return minWidth ? width >= Number(minWidth) : false;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function renderNav(props = {}) {
|
|
47
|
+
return render(
|
|
48
|
+
<ThemeProvider theme={theme}>
|
|
49
|
+
<MobileBottomNav
|
|
50
|
+
destinations={destinations}
|
|
51
|
+
activeRoute="/first"
|
|
52
|
+
onNavigate={vi.fn()}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
</ThemeProvider>,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('MobileBottomNav', () => {
|
|
60
|
+
beforeEach(() => setMediaQueryMatches(false));
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
cleanup();
|
|
64
|
+
vi.restoreAllMocks();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('renders one action per destination, in order, with visible labels', () => {
|
|
68
|
+
renderNav();
|
|
69
|
+
|
|
70
|
+
expect(screen.getAllByRole('button').map((action) => action.textContent)).toEqual([
|
|
71
|
+
'First',
|
|
72
|
+
'Second',
|
|
73
|
+
'Third',
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('selects exactly the matching route and tolerates no match', () => {
|
|
78
|
+
const { rerender } = renderNav({ activeRoute: '/second' });
|
|
79
|
+
const selected = () => screen.getAllByRole('button')
|
|
80
|
+
.filter((action) => action.classList.contains('Mui-selected'));
|
|
81
|
+
|
|
82
|
+
expect(selected()).toHaveLength(1);
|
|
83
|
+
expect(selected()[0].textContent).toBe('Second');
|
|
84
|
+
expect(selected()[0].getAttribute('aria-current')).toBe('page');
|
|
85
|
+
expect(screen.getAllByRole('button').filter((action) => action.hasAttribute('aria-current')))
|
|
86
|
+
.toHaveLength(1);
|
|
87
|
+
|
|
88
|
+
rerender(
|
|
89
|
+
<ThemeProvider theme={theme}>
|
|
90
|
+
<MobileBottomNav
|
|
91
|
+
destinations={destinations}
|
|
92
|
+
activeRoute="/not-in-the-bar"
|
|
93
|
+
onNavigate={vi.fn()}
|
|
94
|
+
/>
|
|
95
|
+
</ThemeProvider>,
|
|
96
|
+
);
|
|
97
|
+
expect(selected()).toHaveLength(0);
|
|
98
|
+
expect(screen.getAllByRole('button').some((action) => action.hasAttribute('aria-current')))
|
|
99
|
+
.toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('calls onNavigate with the selected destination route', () => {
|
|
103
|
+
const onNavigate = vi.fn();
|
|
104
|
+
renderNav({ onNavigate });
|
|
105
|
+
|
|
106
|
+
fireEvent.click(screen.getByRole('button', { name: 'Second' }));
|
|
107
|
+
|
|
108
|
+
expect(onNavigate).toHaveBeenCalledOnce();
|
|
109
|
+
expect(onNavigate).toHaveBeenCalledWith('/second');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('renders a positive badge only on its own destination', () => {
|
|
113
|
+
renderNav({
|
|
114
|
+
destinations: [
|
|
115
|
+
{ route: '/inbox', label: 'Inbox', icon: Icon, badgeCount: 4 },
|
|
116
|
+
{ route: '/empty', label: 'Empty', icon: Icon, badgeCount: 0 },
|
|
117
|
+
{ route: '/unset', label: 'Unset', icon: Icon },
|
|
118
|
+
],
|
|
119
|
+
activeRoute: '/inbox',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const actions = screen.getAllByRole('button');
|
|
123
|
+
expect(within(actions[0]).getByText('4')).toBeTruthy();
|
|
124
|
+
expect(actions[0].querySelectorAll('.MuiBadge-root')).toHaveLength(1);
|
|
125
|
+
expect(actions[1].querySelector('.MuiBadge-root')).toBeNull();
|
|
126
|
+
expect(actions[2].querySelector('.MuiBadge-root')).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('uses shortLabel when present and label when it is absent', () => {
|
|
130
|
+
renderNav({
|
|
131
|
+
destinations: [
|
|
132
|
+
{ route: '/registrations', label: 'My registrations', shortLabel: 'Mine', icon: Icon },
|
|
133
|
+
{ route: '/messages', label: 'Messages', icon: Icon },
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(screen.getAllByRole('button').map((action) => action.textContent)).toEqual([
|
|
138
|
+
'Mine',
|
|
139
|
+
'Messages',
|
|
140
|
+
]);
|
|
141
|
+
expect(screen.getByRole('button', { name: 'My registrations' }).textContent).toBe('Mine');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('resolves its surface and action colours from both baseline and plain MUI themes', () => {
|
|
145
|
+
const themes = [
|
|
146
|
+
createAppTheme({
|
|
147
|
+
palette: {
|
|
148
|
+
primary: { main: '#123456' },
|
|
149
|
+
background: { paper: '#F1F2F3' },
|
|
150
|
+
text: { secondary: '#445566' },
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
createTheme({
|
|
154
|
+
palette: {
|
|
155
|
+
primary: { main: '#654321' },
|
|
156
|
+
background: { paper: '#E7E8E9' },
|
|
157
|
+
text: { secondary: '#665544' },
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
];
|
|
161
|
+
const expected = [
|
|
162
|
+
{ background: 'rgb(241, 242, 243)', selected: 'rgb(18, 52, 86)', rest: 'rgb(68, 85, 102)' },
|
|
163
|
+
{ background: 'rgb(231, 232, 233)', selected: 'rgb(101, 67, 33)', rest: 'rgb(102, 85, 68)' },
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
themes.forEach((currentTheme, index) => {
|
|
167
|
+
const { container } = render(
|
|
168
|
+
<ThemeProvider theme={currentTheme}>
|
|
169
|
+
<MobileBottomNav
|
|
170
|
+
destinations={destinations}
|
|
171
|
+
activeRoute="/first"
|
|
172
|
+
onNavigate={vi.fn()}
|
|
173
|
+
/>
|
|
174
|
+
</ThemeProvider>,
|
|
175
|
+
);
|
|
176
|
+
const actions = screen.getAllByRole('button');
|
|
177
|
+
|
|
178
|
+
expect(getComputedStyle(container.firstElementChild).backgroundColor)
|
|
179
|
+
.toBe(expected[index].background);
|
|
180
|
+
expect(getComputedStyle(actions[0]).color).toBe(expected[index].selected);
|
|
181
|
+
expect(getComputedStyle(actions[1]).color).toBe(expected[index].rest);
|
|
182
|
+
cleanup();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('keeps its structural edge and evenly divided actions under a plain MUI theme', () => {
|
|
187
|
+
const fiveDestinations = [
|
|
188
|
+
...destinations,
|
|
189
|
+
{ route: '/fourth', label: 'Fourth', icon: Icon },
|
|
190
|
+
{ route: '/fifth', label: 'Fifth', icon: Icon },
|
|
191
|
+
];
|
|
192
|
+
setViewportWidth(375);
|
|
193
|
+
const { container } = render(
|
|
194
|
+
<ThemeProvider theme={createTheme()}>
|
|
195
|
+
<MobileBottomNav
|
|
196
|
+
destinations={fiveDestinations}
|
|
197
|
+
activeRoute="/first"
|
|
198
|
+
onNavigate={vi.fn()}
|
|
199
|
+
/>
|
|
200
|
+
</ThemeProvider>,
|
|
201
|
+
);
|
|
202
|
+
const navStyle = getComputedStyle(container.firstElementChild);
|
|
203
|
+
const actionStyles = screen.getAllByRole('button').map((action) => getComputedStyle(action));
|
|
204
|
+
|
|
205
|
+
expect(navStyle.borderTopWidth).toBe('1px');
|
|
206
|
+
expect(navStyle.borderTopStyle).toBe('solid');
|
|
207
|
+
expect(actionStyles).toHaveLength(5);
|
|
208
|
+
actionStyles.forEach((actionStyle) => {
|
|
209
|
+
expect(actionStyle.minWidth).toBe('0px');
|
|
210
|
+
expect(actionStyle.maxWidth).toBe('none');
|
|
211
|
+
expect(actionStyle.flexGrow).toBe('1');
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('keeps component-owned sx keys out of baseline theme overrides', () => {
|
|
216
|
+
const componentSurfaces = [
|
|
217
|
+
...Object.keys(MOBILE_BOTTOM_NAV_ROOT_SX)
|
|
218
|
+
.map((key) => `MuiBottomNavigation.${key}`),
|
|
219
|
+
...Object.keys(MOBILE_BOTTOM_NAV_ACTION_SX)
|
|
220
|
+
.map((key) => `MuiBottomNavigationAction.${key}`),
|
|
221
|
+
];
|
|
222
|
+
const themeSurfaces = [
|
|
223
|
+
...Object.keys(theme.components.MuiBottomNavigation.styleOverrides.root)
|
|
224
|
+
.map((key) => `MuiBottomNavigation.${key}`),
|
|
225
|
+
...Object.keys(theme.components.MuiBottomNavigationAction.styleOverrides.root)
|
|
226
|
+
.map((key) => `MuiBottomNavigationAction.${key}`),
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
expect(componentSurfaces.filter((surface) => themeSurfaces.includes(surface))).toEqual([]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it.each([
|
|
233
|
+
['object', {
|
|
234
|
+
position: 'absolute',
|
|
235
|
+
borderTop: '3px dashed',
|
|
236
|
+
borderColor: 'primary.main',
|
|
237
|
+
}],
|
|
238
|
+
['callback', (currentTheme) => ({
|
|
239
|
+
position: 'absolute',
|
|
240
|
+
borderTop: '3px dashed',
|
|
241
|
+
borderColor: currentTheme.palette.primary.main,
|
|
242
|
+
})],
|
|
243
|
+
['array', [
|
|
244
|
+
{ borderTop: '2px dotted' },
|
|
245
|
+
{
|
|
246
|
+
position: 'absolute',
|
|
247
|
+
borderTop: '3px dashed',
|
|
248
|
+
borderColor: 'primary.main',
|
|
249
|
+
},
|
|
250
|
+
]],
|
|
251
|
+
])('lets caller %s sx override the component defaults', (_kind, sx) => {
|
|
252
|
+
const { container } = renderNav({
|
|
253
|
+
sx,
|
|
254
|
+
});
|
|
255
|
+
const navStyle = getComputedStyle(container.firstElementChild);
|
|
256
|
+
|
|
257
|
+
expect(navStyle.position).toBe('absolute');
|
|
258
|
+
expect(navStyle.borderTopWidth).toBe('3px');
|
|
259
|
+
expect(navStyle.borderTopStyle).toBe('dashed');
|
|
260
|
+
expect(navStyle.borderTopColor).toBe('rgb(15, 98, 254)');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('preserves baseline density and label styling after the structural values move', () => {
|
|
264
|
+
renderNav();
|
|
265
|
+
const [selectedAction] = screen.getAllByRole('button');
|
|
266
|
+
const actionStyle = getComputedStyle(selectedAction);
|
|
267
|
+
const labelStyle = getComputedStyle(
|
|
268
|
+
selectedAction.querySelector('.MuiBottomNavigationAction-label'),
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
expect(actionStyle.minWidth).toBe('0px');
|
|
272
|
+
expect(actionStyle.maxWidth).toBe('none');
|
|
273
|
+
expect(actionStyle.paddingLeft).toBe('2px');
|
|
274
|
+
expect(actionStyle.paddingRight).toBe('2px');
|
|
275
|
+
expect(actionStyle.gap).toBe('2px');
|
|
276
|
+
expect(labelStyle.fontSize).toBe('12px');
|
|
277
|
+
expect(labelStyle.fontWeight).toBe('500');
|
|
278
|
+
expect(labelStyle.lineHeight).toBe('1.2');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('renders nothing at and above hideAbove', () => {
|
|
282
|
+
setMediaQueryMatches((query) => query === '(min-width:900px)');
|
|
283
|
+
const { container } = renderNav();
|
|
284
|
+
|
|
285
|
+
expect(window.matchMedia).toHaveBeenCalledWith('(min-width:900px)');
|
|
286
|
+
expect(container.childElementCount).toBe(0);
|
|
287
|
+
expect(screen.queryByRole('navigation')).toBeNull();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('declares safe-area bottom padding on the fixed bar', () => {
|
|
291
|
+
const { container } = renderNav();
|
|
292
|
+
const nav = container.firstElementChild;
|
|
293
|
+
|
|
294
|
+
expect(nav.classList.contains('MuiBottomNavigation-root')).toBe(true);
|
|
295
|
+
// jsdom drops env() declarations while parsing CSS, so assert the sx
|
|
296
|
+
// declaration before browser CSS parsing instead of a computed inset.
|
|
297
|
+
expect(MOBILE_BOTTOM_NAV_ROOT_SX.paddingBottom).toBe('env(safe-area-inset-bottom)');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('renders the five-item, two-badge specimen at 375px and nothing at 1280px', () => {
|
|
301
|
+
const specimen = [
|
|
302
|
+
{ route: '/triage', label: 'Triage', icon: Icon, badgeCount: 7 },
|
|
303
|
+
{ route: '/board', label: 'Board', icon: Icon, badgeCount: 3 },
|
|
304
|
+
{ route: '/chat', label: 'Chat', icon: Icon },
|
|
305
|
+
{ route: '/status', label: 'Status', icon: Icon },
|
|
306
|
+
{ route: '/more', label: 'More', icon: Icon, emphasis: true },
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
setViewportWidth(375);
|
|
310
|
+
renderNav({ destinations: specimen, activeRoute: '/triage' });
|
|
311
|
+
expect(screen.getAllByRole('button')).toHaveLength(5);
|
|
312
|
+
expect(document.querySelectorAll('.MuiBadge-root')).toHaveLength(2);
|
|
313
|
+
|
|
314
|
+
cleanup();
|
|
315
|
+
setViewportWidth(1280);
|
|
316
|
+
renderNav({ destinations: specimen, activeRoute: '/triage' });
|
|
317
|
+
expect(screen.queryByRole('button')).toBeNull();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createTheme } from '@mui/material/styles';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import { assertThemeComplete, createAppTheme } from '../src/
|
|
4
|
+
import { assertThemeComplete, createAppTheme } from '../src/theme';
|
|
5
5
|
import { BASELINE_STATIC } from '../src/theme/tokens';
|
|
6
6
|
|
|
7
7
|
describe('createAppTheme', () => {
|
|
@@ -12,6 +12,27 @@ describe('createAppTheme', () => {
|
|
|
12
12
|
expect(theme.typography.body1.fontSize).toBe('14px');
|
|
13
13
|
expect(theme.shape.borderRadius).toBe(3);
|
|
14
14
|
expect(theme.components.MuiPaper.defaultProps).toMatchObject({ elevation: 0, variant: 'outlined' });
|
|
15
|
+
expect(theme.components.MuiBottomNavigationAction.styleOverrides.label).toMatchObject({
|
|
16
|
+
fontSize: '12px',
|
|
17
|
+
fontWeight: 500,
|
|
18
|
+
lineHeight: 1.2,
|
|
19
|
+
maxWidth: '100%',
|
|
20
|
+
whiteSpace: 'nowrap',
|
|
21
|
+
overflow: 'hidden',
|
|
22
|
+
textOverflow: 'ellipsis',
|
|
23
|
+
'&.Mui-selected': { fontSize: '12px' },
|
|
24
|
+
});
|
|
25
|
+
expect(theme.components.MuiBottomNavigationAction.styleOverrides.root).toMatchObject({
|
|
26
|
+
padding: '0 2px',
|
|
27
|
+
gap: '2px',
|
|
28
|
+
'& .MuiSvgIcon-root': { width: '22px', height: '22px' },
|
|
29
|
+
});
|
|
30
|
+
expect(theme.components.MuiBottomNavigation.styleOverrides.root).not.toHaveProperty('borderTop');
|
|
31
|
+
expect(theme.components.MuiBottomNavigation.styleOverrides.root).not.toHaveProperty('borderColor');
|
|
32
|
+
expect(theme.components.MuiBottomNavigationAction.styleOverrides.root)
|
|
33
|
+
.not.toHaveProperty('minWidth');
|
|
34
|
+
expect(theme.components.MuiBottomNavigationAction.styleOverrides.root)
|
|
35
|
+
.not.toHaveProperty('maxWidth');
|
|
15
36
|
expect(theme.palette.dataSeries.categorical).toHaveLength(6);
|
|
16
37
|
expect(theme.palette.primary.light).not.toBe(createTheme().palette.primary.light);
|
|
17
38
|
// MUI's createTheme(options, ...args) only augments the FIRST argument's
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
assertThemeComplete,
|
|
6
6
|
calculateContrastRatio,
|
|
7
7
|
createAppTheme,
|
|
8
|
-
|
|
8
|
+
THEME_COMPLETENESS_SURFACES,
|
|
9
|
+
} from '../src/theme';
|
|
9
10
|
|
|
10
11
|
describe('theme completeness', () => {
|
|
11
12
|
it('finds untouched MUI surfaces and accepts createAppTheme', () => {
|
|
@@ -19,9 +20,55 @@ describe('theme completeness', () => {
|
|
|
19
20
|
expect(assertThemeComplete(createTheme({ components: { MuiButton: {} } })).findings
|
|
20
21
|
.map(({ surface }) => surface))
|
|
21
22
|
.toContain('components.MuiButton.styleOverrides.root.minHeight');
|
|
23
|
+
expect(bare.findings.map(({ surface }) => surface)).toEqual(expect.arrayContaining([
|
|
24
|
+
'components.MuiBottomNavigation.styleOverrides.root.boxShadow',
|
|
25
|
+
'components.MuiBottomNavigation.styleOverrides.root.backgroundColor',
|
|
26
|
+
'components.MuiBottomNavigationAction.styleOverrides.root.padding',
|
|
27
|
+
'components.MuiBottomNavigationAction.styleOverrides.root.gap',
|
|
28
|
+
'components.MuiBottomNavigationAction.styleOverrides.root.& .MuiSvgIcon-root.width',
|
|
29
|
+
'components.MuiBottomNavigationAction.styleOverrides.root.& .MuiSvgIcon-root.height',
|
|
30
|
+
'components.MuiBottomNavigationAction.styleOverrides.root.color',
|
|
31
|
+
'components.MuiBottomNavigationAction.styleOverrides.label.fontSize',
|
|
32
|
+
'components.MuiBottomNavigationAction.styleOverrides.label.fontWeight',
|
|
33
|
+
'components.MuiBottomNavigationAction.styleOverrides.label.lineHeight',
|
|
34
|
+
'components.MuiBottomNavigationAction.styleOverrides.label.maxWidth',
|
|
35
|
+
'components.MuiBottomNavigationAction.styleOverrides.label.whiteSpace',
|
|
36
|
+
'components.MuiBottomNavigationAction.styleOverrides.label.overflow',
|
|
37
|
+
'components.MuiBottomNavigationAction.styleOverrides.label.textOverflow',
|
|
38
|
+
'components.MuiBottomNavigationAction.styleOverrides.label.&.Mui-selected.fontSize',
|
|
39
|
+
'components.MuiBottomNavigationAction.styleOverrides.root.&.Mui-selected.color',
|
|
40
|
+
]));
|
|
22
41
|
expect(baseline.findings).toEqual([]);
|
|
23
42
|
});
|
|
24
43
|
|
|
44
|
+
it('keeps the narrowed bottom-navigation inventory complete', () => {
|
|
45
|
+
const bottomNavigationSurfaces = THEME_COMPLETENESS_SURFACES
|
|
46
|
+
.map(({ surface }) => surface)
|
|
47
|
+
.filter((surface) => surface.startsWith('components.MuiBottomNavigation'));
|
|
48
|
+
|
|
49
|
+
expect(bottomNavigationSurfaces).toHaveLength(16);
|
|
50
|
+
[
|
|
51
|
+
'components.MuiBottomNavigation.styleOverrides.root.borderTop',
|
|
52
|
+
'components.MuiBottomNavigation.styleOverrides.root.borderColor',
|
|
53
|
+
'components.MuiBottomNavigationAction.styleOverrides.root.minWidth',
|
|
54
|
+
'components.MuiBottomNavigationAction.styleOverrides.root.maxWidth',
|
|
55
|
+
].forEach((surface) => expect(bottomNavigationSurfaces).not.toContain(surface));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('ignores a valid exemption for a deregistered surface', () => {
|
|
59
|
+
const result = assertThemeComplete(
|
|
60
|
+
createAppTheme({ palette: { primary: { main: '#0F62FE' } } }),
|
|
61
|
+
{
|
|
62
|
+
exemptions: [{
|
|
63
|
+
surface: 'components.MuiBottomNavigation.styleOverrides.root.borderTop',
|
|
64
|
+
reason: 'Legacy exemption retained while the application upgrades.',
|
|
65
|
+
}],
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(result.findings).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
|
|
25
72
|
it('suppresses exactly one surface when an exemption includes a reason', () => {
|
|
26
73
|
const theme = createTheme();
|
|
27
74
|
const before = assertThemeComplete(theme).findings;
|