@micha.bigler/ui-core-micha 3.14.4 → 4.0.0
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/CHANGELOG.md +25 -0
- package/README.md +7 -0
- package/dist/chat/kitSxRegistry.js +9 -0
- package/dist/components/charts/kitSxRegistry.js +6 -0
- package/dist/entries/charts.js +6 -0
- package/dist/entries/chat.js +8 -0
- package/dist/entries/markdown.js +1 -0
- package/dist/index.js +0 -14
- package/dist/pages/AccountPage.js +1 -2
- package/dist/pages/LoginPage.js +1 -2
- package/dist/pages/PasswordChangePage.js +1 -2
- package/dist/pages/PasswordInvitePage.js +1 -2
- package/dist/pages/PasswordResetRequestPage.js +1 -2
- package/dist/pages/SignUpPage.js +1 -2
- package/dist/pages/SignupConfirmPage.js +1 -2
- package/dist/theme/kitSxEntry.js +11 -0
- package/dist/theme/kitSxRegistry.js +2 -15
- package/package.json +32 -7
- package/.github/workflows/ci.yml +0 -35
- package/.github/workflows/publish.yml +0 -126
- package/DESIGN.md +0 -60
- package/WORK_ORDERS.md +0 -150
- package/dev/entries.jsx +0 -721
- package/dev/fixtures.js +0 -26
- package/dev/main.jsx +0 -75
- package/dev/mockTransport.jsx +0 -128
- package/docs/CHART-LAYOUT.md +0 -157
- package/docs/DARK-TOKENS.md +0 -140
- package/docs/TYPE-SCALE.md +0 -111
- package/docs/messaging-deviations.md +0 -118
- package/index.html +0 -12
- package/renovate.json +0 -10
- package/scripts/chart-api-census.mjs +0 -415
- package/src/auth/AuthContext.jsx +0 -158
- package/src/auth/ReauthModal.jsx +0 -82
- package/src/auth/UserMenu.jsx +0 -121
- package/src/auth/apiClient.jsx +0 -232
- package/src/auth/authApi.jsx +0 -539
- package/src/auth/authConfig.jsx +0 -34
- package/src/auth/reauth.js +0 -47
- package/src/chat/ChatComposer.jsx +0 -36
- package/src/chat/ChatList.jsx +0 -87
- package/src/chat/ChatThread.jsx +0 -128
- package/src/chat/ChatWorkspace.jsx +0 -244
- package/src/chat/MarkdownReply.jsx +0 -22
- package/src/chat/ModelPicker.jsx +0 -85
- package/src/chat/api.js +0 -18
- package/src/chat/useChatSocket.js +0 -32
- package/src/chat/wire.js +0 -7
- package/src/components/AccessCodeManager.jsx +0 -288
- package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
- package/src/components/AllowedEmailDomainsManager.jsx +0 -92
- package/src/components/AuthFactorRequirementCard.jsx +0 -95
- package/src/components/BulkInviteCsvTab.jsx +0 -198
- package/src/components/EmptyState.jsx +0 -38
- package/src/components/LoginForm.jsx +0 -159
- package/src/components/MFAComponent.jsx +0 -350
- package/src/components/MfaLoginComponent.jsx +0 -252
- package/src/components/MobileBottomNav.jsx +0 -82
- package/src/components/PasskeysComponent.jsx +0 -252
- package/src/components/PasswordChangeForm.jsx +0 -87
- package/src/components/PasswordResetRequestForm.jsx +0 -43
- package/src/components/PasswordSetForm.jsx +0 -97
- package/src/components/ProfileComponent.jsx +0 -348
- package/src/components/QrSignupManager.jsx +0 -404
- package/src/components/QrSignupValidityManager.jsx +0 -106
- package/src/components/RegistrationMethodsManager.jsx +0 -150
- package/src/components/SecurityComponent.jsx +0 -141
- package/src/components/SocialLoginButtons.jsx +0 -83
- package/src/components/SoftChip.jsx +0 -93
- package/src/components/StatTile.jsx +0 -39
- package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
- package/src/components/TurnstileWidget.jsx +0 -99
- package/src/components/UserInviteComponent.jsx +0 -76
- package/src/components/UserListComponent.jsx +0 -618
- package/src/components/charts/BarChart.jsx +0 -102
- package/src/components/charts/ChartFrame.jsx +0 -214
- package/src/components/charts/LineChart.jsx +0 -126
- package/src/components/charts/ScatterChart.jsx +0 -299
- package/src/components/charts/TimeSeriesChart.jsx +0 -300
- package/src/components/charts/chartDefaults.js +0 -644
- package/src/components/charts/chartLabels.js +0 -36
- package/src/components/charts/exportChart.js +0 -258
- package/src/components/charts/formatters.js +0 -39
- package/src/components/charts/palette.js +0 -33
- package/src/components/charts/yearTickInterval.js +0 -7
- package/src/i18n/authTranslations.ts +0 -2651
- package/src/i18n/chartsTranslations.ts +0 -91
- package/src/i18n/chatTranslations.ts +0 -55
- package/src/i18n/messagingTranslations.ts +0 -120
- package/src/i18n/notificationsTranslations.ts +0 -32
- package/src/i18n/onboardingTranslations.ts +0 -29
- package/src/i18n/sectionNavTranslations.ts +0 -20
- package/src/i18n/uiCoreTranslations.js +0 -29
- package/src/i18n/userMenuTranslations.ts +0 -5
- package/src/index.js +0 -158
- package/src/layout/PageLayout.jsx +0 -36
- package/src/layout/SectionNav.jsx +0 -320
- package/src/messaging/AttachmentList.jsx +0 -145
- package/src/messaging/Composer.jsx +0 -197
- package/src/messaging/ConversationLaunchers.jsx +0 -51
- package/src/messaging/ConversationList.jsx +0 -109
- package/src/messaging/DirectMessageLauncher.jsx +0 -112
- package/src/messaging/MessageBubble.jsx +0 -183
- package/src/messaging/MessagingProvider.jsx +0 -0
- package/src/messaging/MessagingScopeConfig.jsx +0 -24
- package/src/messaging/PollCard.jsx +0 -163
- package/src/messaging/ReactionBar.jsx +0 -43
- package/src/messaging/ReadTicks.jsx +0 -82
- package/src/messaging/Thread.jsx +0 -195
- package/src/messaging/api.js +0 -48
- package/src/messaging/composerImageCompression.js +0 -35
- package/src/messaging/linkifyText.jsx +0 -37
- package/src/notifications/NotificationBell.jsx +0 -84
- package/src/notifications/NotificationSettings.jsx +0 -330
- package/src/notifications/NotificationsProvider.jsx +0 -224
- package/src/notifications/PopupSurface.jsx +0 -105
- package/src/notifications/api.js +0 -55
- package/src/notifications/feedApi.js +0 -18
- package/src/notifications/realtime.jsx +0 -138
- package/src/notifications/serviceWorker/sw.js +0 -50
- package/src/onboarding/OnboardingProvider.jsx +0 -326
- package/src/onboarding/OnboardingWizard.jsx +0 -61
- package/src/onboarding/WizardDialogShell.jsx +0 -51
- package/src/onboarding/api.js +0 -13
- package/src/onboarding/stepSelection.js +0 -16
- package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
- package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
- package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
- package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
- package/src/pages/AccountPage.jsx +0 -394
- package/src/pages/LoginPage.jsx +0 -343
- package/src/pages/PasswordChangePage.jsx +0 -62
- package/src/pages/PasswordInvitePage.jsx +0 -177
- package/src/pages/PasswordResetRequestPage.jsx +0 -68
- package/src/pages/SignUpPage.jsx +0 -268
- package/src/pages/SignupConfirmPage.jsx +0 -155
- package/src/testing/checkKitIntegration.js +0 -233
- package/src/theme/createAppTheme.js +0 -423
- package/src/theme/fonts.js +0 -3
- package/src/theme/index.js +0 -21
- package/src/theme/kitSxRegistry.js +0 -285
- package/src/theme/themeCompleteness.js +0 -1045
- package/src/theme/tokens.js +0 -478
- package/src/utils/auth-errors.js +0 -76
- package/src/utils/authService.js +0 -223
- package/src/utils/resolveErrorText.js +0 -28
- package/src/utils/webauthn.js +0 -51
- package/tests/AccountPage.profileStatements.test.jsx +0 -162
- package/tests/AccountPage.test.jsx +0 -200
- package/tests/AuthContext.test.jsx +0 -129
- package/tests/BarChart.test.jsx +0 -112
- package/tests/BrowserPushStep.test.jsx +0 -110
- package/tests/ChartFrame.test.jsx +0 -336
- package/tests/ConversationList.test.jsx +0 -144
- package/tests/CookieConsentStep.test.jsx +0 -69
- package/tests/EmptyState.test.jsx +0 -62
- package/tests/LineChart.test.jsx +0 -80
- package/tests/MessagingProvider.test.jsx +0 -50
- package/tests/MobileBottomNav.test.jsx +0 -319
- package/tests/NotificationBell.test.jsx +0 -60
- package/tests/NotificationSettings.test.jsx +0 -276
- package/tests/NotificationsProvider.test.jsx +0 -240
- package/tests/OnboardingProvider.test.jsx +0 -294
- package/tests/OnboardingWizard.test.jsx +0 -143
- package/tests/PasswordChangeForm.test.jsx +0 -70
- package/tests/PasswordInvitePage.test.jsx +0 -142
- package/tests/PasswordSetForm.test.jsx +0 -70
- package/tests/PopupSurface.test.jsx +0 -142
- package/tests/ProfileComponent.refresh.test.jsx +0 -194
- package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
- package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
- package/tests/ScatterChart.test.jsx +0 -270
- package/tests/SectionNav.test.jsx +0 -363
- package/tests/SignUpPage.test.jsx +0 -208
- package/tests/SoftChip.test.jsx +0 -105
- package/tests/StatTile.test.jsx +0 -57
- package/tests/Thread.test.jsx +0 -151
- package/tests/TimeSeriesChart.test.jsx +0 -367
- package/tests/UserListComponent.test.jsx +0 -192
- package/tests/UserMenu.test.jsx +0 -171
- package/tests/apiClientLanguage.test.js +0 -33
- package/tests/apiClientPublicPaths.test.js +0 -178
- package/tests/authApiLogin.test.js +0 -42
- package/tests/authApiRegistration.test.js +0 -31
- package/tests/authErrorCodeRendering.test.jsx +0 -77
- package/tests/authErrors.test.js +0 -74
- package/tests/authTranslations.test.js +0 -74
- package/tests/chart-api-census.test.js +0 -366
- package/tests/chartAxisTickBand.test.jsx +0 -95
- package/tests/chartDefaults.test.js +0 -902
- package/tests/chartLabels.test.js +0 -50
- package/tests/chartsPalette.test.js +0 -99
- package/tests/chatApi.test.js +0 -48
- package/tests/chatDriverNames.test.jsx +0 -71
- package/tests/chatExports.test.js +0 -3
- package/tests/chatList.test.jsx +0 -84
- package/tests/chatPicker.test.jsx +0 -82
- package/tests/chatSocket.test.jsx +0 -26
- package/tests/chatThread.test.jsx +0 -116
- package/tests/chatTranslations.test.js +0 -47
- package/tests/chatWorkspace.test.jsx +0 -234
- package/tests/checkKitIntegration.test.jsx +0 -219
- package/tests/createAppTheme.test.js +0 -132
- package/tests/createAppThemeDark.test.js +0 -127
- package/tests/createAppThemeLightGuard.test.js +0 -40
- package/tests/createAppThemePageContrast.test.js +0 -108
- package/tests/createAppThemeTypography.test.js +0 -53
- package/tests/errorCallSiteGuard.test.js +0 -32
- package/tests/exportChart.test.js +0 -313
- package/tests/feedApi.test.js +0 -46
- package/tests/fixtures/light-theme-baseline.json +0 -2234
- package/tests/i18nAggregate.test.js +0 -120
- package/tests/i18nGermanVariant.test.js +0 -65
- package/tests/loginPageErrorResolution.test.jsx +0 -102
- package/tests/messagingApi.test.js +0 -32
- package/tests/messagingChunk6.test.jsx +0 -116
- package/tests/messagingCompactBubble.test.jsx +0 -64
- package/tests/messagingComposer.test.jsx +0 -136
- package/tests/messagingComposerChunk5.test.jsx +0 -113
- package/tests/messagingContractConformance.test.js +0 -69
- package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
- package/tests/messagingExports.test.js +0 -21
- package/tests/messagingInteractions.test.jsx +0 -136
- package/tests/messagingLastMessagePreview.test.jsx +0 -93
- package/tests/messagingLinkify.test.jsx +0 -51
- package/tests/messagingMessageActions.test.jsx +0 -163
- package/tests/messagingMsg6.test.jsx +0 -105
- package/tests/messagingMsg6f.test.jsx +0 -155
- package/tests/messagingMsg6g.test.jsx +0 -166
- package/tests/messagingMsg6h.test.jsx +0 -136
- package/tests/messagingMsg6i.test.jsx +0 -129
- package/tests/messagingPollRendering.test.jsx +0 -142
- package/tests/messagingReactionMerge.test.jsx +0 -77
- package/tests/messagingReadTicksTooltip.test.jsx +0 -36
- package/tests/messagingThreadReceipts.test.jsx +0 -178
- package/tests/messagingThreadingQuoting.test.jsx +0 -87
- package/tests/messagingUnreadLifecycle.test.jsx +0 -78
- package/tests/notificationsApi.test.js +0 -41
- package/tests/notificationsExports.test.js +0 -34
- package/tests/onboardingDescriptors.test.js +0 -132
- package/tests/packageTreeShaking.test.js +0 -126
- package/tests/realtime.test.jsx +0 -219
- package/tests/resolveErrorText.test.js +0 -72
- package/tests/stepSelection.test.js +0 -53
- package/tests/themeCompleteness.test.js +0 -626
- package/tests/timeSeriesLegend.test.js +0 -99
- package/tests/userScreenTranslations.test.jsx +0 -218
- package/tsconfig.build.json +0 -18
- package/vite.config.mjs +0 -6
- package/work-orders/AUTH-1.md +0 -74
- package/work-orders/AUTH-2.md +0 -74
- package/work-orders/AUTH-4.md +0 -183
- package/work-orders/AUTH-5.md +0 -154
- package/work-orders/AUTH-7.md +0 -181
- package/work-orders/AUTH-8.md +0 -184
- package/work-orders/CHART-1.md +0 -285
- package/work-orders/CHART-2.md +0 -222
- package/work-orders/CHART-3.md +0 -163
- package/work-orders/CHART-4.md +0 -203
- package/work-orders/CHART-5.md +0 -226
- package/work-orders/CHART-6.md +0 -428
- package/work-orders/CHART-7.md +0 -175
- package/work-orders/DX-1.md +0 -169
- package/work-orders/DX-2.md +0 -188
- package/work-orders/DX-3.md +0 -195
- package/work-orders/I18N-1.md +0 -254
- package/work-orders/I18N-3.md +0 -207
- package/work-orders/MSG-3.md +0 -375
- package/work-orders/MSG-3b.md +0 -1002
- package/work-orders/MSG-3c.md +0 -378
- package/work-orders/MSG-3d.md +0 -268
- package/work-orders/MSG-3e.md +0 -164
- package/work-orders/MSG-3f.md +0 -391
- package/work-orders/MSG-6e.md +0 -308
- package/work-orders/MSG-6f.md +0 -214
- package/work-orders/MSG-6g.md +0 -244
- package/work-orders/MSG-6h.md +0 -238
- package/work-orders/MSG-6i.md +0 -271
- package/work-orders/MSG-6j.md +0 -117
- package/work-orders/NOTIF-12.md +0 -214
- package/work-orders/NOTIF-13.md +0 -142
- package/work-orders/NOTIF-14.md +0 -151
- package/work-orders/PRIM-1.md +0 -177
- package/work-orders/SHELL-1.md +0 -255
- package/work-orders/SHELL-2.md +0 -197
- package/work-orders/SHELL-3.md +0 -359
- package/work-orders/SHELL-4.md +0 -351
- package/work-orders/SHELL-5.md +0 -345
- package/work-orders/SHELL-6.md +0 -210
- package/work-orders/THEME-1.md +0 -587
- package/work-orders/THEME-10.md +0 -303
- package/work-orders/THEME-11.md +0 -208
- package/work-orders/THEME-2.md +0 -339
- package/work-orders/THEME-3.md +0 -210
- package/work-orders/THEME-4.md +0 -271
- package/work-orders/THEME-5.md +0 -341
- package/work-orders/THEME-6.md +0 -247
- package/work-orders/THEME-7.md +0 -82
- package/work-orders/THEME-8.md +0 -211
- package/work-orders/THEME-9.md +0 -194
- package/work-orders/UCM-AUTH-10.md +0 -490
- package/work-orders/UCM-AUTH-11.md +0 -185
- package/work-orders/UCM-AUTH-12.md +0 -98
- package/work-orders/UCM-AUTH-13.md +0 -47
- package/work-orders/UCM-AUTH-9.md +0 -207
- package/work-orders/UCM-CHART-10.md +0 -150
- package/work-orders/UCM-CHART-11.md +0 -132
- package/work-orders/UCM-CHART-12.md +0 -348
- package/work-orders/UCM-CHART-13.md +0 -122
- package/work-orders/UCM-CHART-14.md +0 -198
- package/work-orders/UCM-CHART-15.md +0 -187
- package/work-orders/UCM-CHART-16.md +0 -169
- package/work-orders/UCM-CHART-17.md +0 -195
- package/work-orders/UCM-CHART-18.md +0 -200
- package/work-orders/UCM-CHART-8.md +0 -147
- package/work-orders/UCM-CHART-9.md +0 -265
- package/work-orders/UCM-CHAT-1.md +0 -190
- package/work-orders/UCM-CHAT-2.md +0 -75
- package/work-orders/UCM-CI-1.md +0 -65
- package/work-orders/UCM-DEP-1.md +0 -63
- package/work-orders/UCM-I18N-4.md +0 -226
- package/work-orders/UCM-I18N-5.md +0 -211
- package/work-orders/UCM-MSG-20.md +0 -192
- package/work-orders/UCM-PRIM-2.md +0 -114
- package/work-orders/UCM-SHELL-7.md +0 -134
- package/work-orders/UCM-SHELL-8.md +0 -125
- package/work-orders/UCM-TEST-1.md +0 -304
- package/work-orders/UCM-THEME-13.md +0 -69
- package/work-orders/UCM-THEME-14.md +0 -312
- package/work-orders/UCM-THEME-15.md +0 -356
- package/work-orders/UCM-THEME-16.md +0 -80
- package/work-orders/UCM-THEME-17.md +0 -111
- package/work-orders/UCM-THEME-18.md +0 -184
- package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
- package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
- package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
- package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
- package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
- package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
- package/work-orders/assets/UCM-THEME-17-type-scale.html +0 -391
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import i18next from 'i18next';
|
|
4
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { cleanup, render, screen } from '@testing-library/react';
|
|
6
|
-
import { I18nextProvider } from 'react-i18next';
|
|
7
|
-
import { ThemeProvider } from '@mui/material/styles';
|
|
8
|
-
|
|
9
|
-
const scatterSpy = vi.hoisted(() => vi.fn(({ children }) => <div data-testid="mui-scatter">{children}</div>));
|
|
10
|
-
vi.mock('@mui/x-charts/ScatterChart', () => ({ ScatterChart: scatterSpy }));
|
|
11
|
-
vi.mock('@mui/x-charts/hooks', () => ({
|
|
12
|
-
useXScale: () => (v) => v * 10,
|
|
13
|
-
useYScale: () => (v) => 100 - v * 10,
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
ScatterChart,
|
|
18
|
-
ScatterReferenceCurve,
|
|
19
|
-
ScatterReferenceLine,
|
|
20
|
-
scaleBubbleRadius,
|
|
21
|
-
} from '../src/components/charts/ScatterChart';
|
|
22
|
-
import { createAppTheme } from '../src/theme/createAppTheme';
|
|
23
|
-
|
|
24
|
-
const theme = createAppTheme({ palette: { primary: { main: '#3D5A99' } } });
|
|
25
|
-
const i18n = i18next.createInstance();
|
|
26
|
-
i18n.init({ lng: 'en', resources: { en: { translation: {} } }, interpolation: { escapeValue: false } });
|
|
27
|
-
|
|
28
|
-
function withProviders(children) {
|
|
29
|
-
return React.createElement(
|
|
30
|
-
ThemeProvider,
|
|
31
|
-
{ theme },
|
|
32
|
-
React.createElement(I18nextProvider, { i18n }, children),
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function renderScatter(props = {}) {
|
|
37
|
-
return render(withProviders(
|
|
38
|
-
React.createElement(ScatterChart, {
|
|
39
|
-
series: [{ id: 's', data: [{ x: 1, y: 1 }] }],
|
|
40
|
-
...props,
|
|
41
|
-
}),
|
|
42
|
-
));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// THEME-10 -- the formula the access-gap panel already hand-rolls
|
|
46
|
-
// (`max(3, 10*sqrt(pop/maxPop))`), pinned as a pure function.
|
|
47
|
-
describe('scaleBubbleRadius', () => {
|
|
48
|
-
it('matches the access-panel formula across the domain', () => {
|
|
49
|
-
expect(scaleBubbleRadius(0, 100)).toBe(3);
|
|
50
|
-
expect(scaleBubbleRadius(100, 100)).toBe(10);
|
|
51
|
-
expect(scaleBubbleRadius(25, 100)).toBeCloseTo(5, 5);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('floors at the minimum radius for a degenerate or non-finite input', () => {
|
|
55
|
-
expect(scaleBubbleRadius(5, 0)).toBe(3);
|
|
56
|
-
expect(scaleBubbleRadius(NaN, 100)).toBe(3);
|
|
57
|
-
expect(scaleBubbleRadius(5, NaN)).toBe(3);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('ScatterChart mark count and colour default (THEME-10)', () => {
|
|
62
|
-
afterEach(() => { cleanup(); vi.clearAllMocks(); });
|
|
63
|
-
|
|
64
|
-
// The failure mode nobody sees: a scatter that silently drops points.
|
|
65
|
-
it('renders every supplied point, none dropped', () => {
|
|
66
|
-
renderScatter({ series: [{ id: 's', data: [{ x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 3 }] }] });
|
|
67
|
-
expect(scatterSpy.mock.calls.at(-1)[0].series[0].data).toHaveLength(3);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('defaults a single undifferentiated series to the neutral role colour, not a series hue', () => {
|
|
71
|
-
renderScatter();
|
|
72
|
-
expect(scatterSpy.mock.calls.at(-1)[0].colors).toEqual([theme.palette.text.secondary]);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('uses the categorical palette, not the neutral tone, once a caller supplies multiple series', () => {
|
|
76
|
-
renderScatter({
|
|
77
|
-
series: [
|
|
78
|
-
{ id: 'a', data: [{ x: 1, y: 1 }] },
|
|
79
|
-
{ id: 'b', data: [{ x: 2, y: 2 }] },
|
|
80
|
-
],
|
|
81
|
-
});
|
|
82
|
-
const { colors } = scatterSpy.mock.calls.at(-1)[0];
|
|
83
|
-
expect(colors).not.toEqual([theme.palette.text.secondary]);
|
|
84
|
-
expect(colors.length).toBeGreaterThan(1);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('lets a caller override the palette explicitly (e.g. a family/role register)', () => {
|
|
88
|
-
renderScatter({ palette: ['#ABCDEF'] });
|
|
89
|
-
expect(scatterSpy.mock.calls.at(-1)[0].colors).toEqual(['#ABCDEF']);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// reviewer/ui_reviewer U3: a single series that sets its OWN colour is not "undifferentiated"
|
|
93
|
-
// -- the neutral default only applies when nothing else specifies a colour. This pins that
|
|
94
|
-
// ScatterChart itself never strips or overrides a series' own `.color` (MUI's own resolution
|
|
95
|
-
// of series.color vs. the colors fallback array is MUI's own contract, out of scope here --
|
|
96
|
-
// same boundary BarChart.test.jsx already draws for the identical `colors={palette ||
|
|
97
|
-
// neutralPalette.categorical}` pattern).
|
|
98
|
-
it('leaves a single series\' own explicit colour untouched and does not treat it as neutral', () => {
|
|
99
|
-
renderScatter({ series: [{ id: 's', color: '#123456', data: [{ x: 1, y: 1 }] }] });
|
|
100
|
-
const props = scatterSpy.mock.calls.at(-1)[0];
|
|
101
|
-
expect(props.series[0].color).toBe('#123456');
|
|
102
|
-
expect(props.colors).not.toEqual([theme.palette.text.secondary]);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('ScatterChart bubble z-order (THEME-10)', () => {
|
|
107
|
-
afterEach(() => { cleanup(); vi.clearAllMocks(); });
|
|
108
|
-
|
|
109
|
-
it('sorts each series largest-first when sizeAccessor is set, so a small bubble is never hidden under a large one', () => {
|
|
110
|
-
renderScatter({
|
|
111
|
-
sizeAccessor: (p) => p.pop,
|
|
112
|
-
series: [{ id: 's', data: [{ x: 1, y: 1, pop: 10 }, { x: 2, y: 2, pop: 500 }, { x: 3, y: 3, pop: 100 }] }],
|
|
113
|
-
});
|
|
114
|
-
const data = scatterSpy.mock.calls.at(-1)[0].series[0].data;
|
|
115
|
-
expect(data.map((p) => p.pop)).toEqual([500, 100, 10]);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('leaves point order untouched outside bubble mode', () => {
|
|
119
|
-
const original = [{ x: 1, y: 1 }, { x: 2, y: 2 }];
|
|
120
|
-
renderScatter({ series: [{ id: 's', data: original }] });
|
|
121
|
-
expect(scatterSpy.mock.calls.at(-1)[0].series[0].data).toEqual(original);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('ScatterChart per-point marker style (THEME-10)', () => {
|
|
126
|
-
afterEach(() => { cleanup(); vi.clearAllMocks(); });
|
|
127
|
-
|
|
128
|
-
it('renders a hollow marker (fill: none) only for the point getPointStyle marks hollow', () => {
|
|
129
|
-
renderScatter({
|
|
130
|
-
getPointStyle: (p) => (p.isStatusQuo ? { hollow: true } : {}),
|
|
131
|
-
series: [{ id: 's', data: [{ x: 1, y: 1, isStatusQuo: true }, { x: 2, y: 2, isStatusQuo: false }] }],
|
|
132
|
-
});
|
|
133
|
-
const Marker = scatterSpy.mock.calls.at(-1)[0].slots.marker;
|
|
134
|
-
const markerProps = { seriesId: 's', color: '#111', x: 0, y: 0, isHighlighted: false, isFaded: false, size: 5 };
|
|
135
|
-
|
|
136
|
-
const hollow = render(React.createElement(Marker, { ...markerProps, dataIndex: 0 }));
|
|
137
|
-
const filled = render(React.createElement(Marker, { ...markerProps, dataIndex: 1 }));
|
|
138
|
-
|
|
139
|
-
expect(hollow.container.querySelector('circle').getAttribute('fill')).toBe('none');
|
|
140
|
-
expect(filled.container.querySelector('circle').getAttribute('fill')).toBe('#111');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('sizes a bubble marker from sizeAccessor via scaleBubbleRadius, resolved against the POST-sort index', () => {
|
|
144
|
-
renderScatter({
|
|
145
|
-
sizeAccessor: (p) => p.pop,
|
|
146
|
-
series: [{ id: 's', data: [{ x: 1, y: 1, pop: 0 }, { x: 2, y: 2, pop: 100 }] }],
|
|
147
|
-
});
|
|
148
|
-
const Marker = scatterSpy.mock.calls.at(-1)[0].slots.marker;
|
|
149
|
-
const markerProps = { seriesId: 's', color: '#111', x: 0, y: 0, isHighlighted: false, isFaded: false, size: 5 };
|
|
150
|
-
|
|
151
|
-
// Data was re-sorted largest-first: index 0 is now pop:100, index 1 is pop:0.
|
|
152
|
-
const big = render(React.createElement(Marker, { ...markerProps, dataIndex: 0 }));
|
|
153
|
-
const small = render(React.createElement(Marker, { ...markerProps, dataIndex: 1 }));
|
|
154
|
-
|
|
155
|
-
expect(Number(big.container.querySelector('circle').getAttribute('r'))).toBeCloseTo(10, 5);
|
|
156
|
-
expect(Number(small.container.querySelector('circle').getAttribute('r'))).toBe(3);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Live-render caught regression: bubble sizing must share ONE max across every series (a
|
|
160
|
-
// single comparable scale, matching the access-gap panel's own
|
|
161
|
-
// `maxPop = Math.max(1, ...points.map(p => p.population))` over ALL points regardless of
|
|
162
|
-
// division/settlement) -- a per-series max would let each series' own largest point always
|
|
163
|
-
// render at full radius, making cross-series bubble size meaningless.
|
|
164
|
-
it('sizes bubbles against ONE chart-wide max across all series, not a max per series', () => {
|
|
165
|
-
renderScatter({
|
|
166
|
-
sizeAccessor: (p) => p.pop,
|
|
167
|
-
series: [
|
|
168
|
-
{ id: 'small-series', data: [{ x: 1, y: 1, pop: 100 }] },
|
|
169
|
-
{ id: 'big-series', data: [{ x: 2, y: 2, pop: 1000 }] },
|
|
170
|
-
],
|
|
171
|
-
});
|
|
172
|
-
const props = scatterSpy.mock.calls.at(-1)[0];
|
|
173
|
-
const Marker = props.slots.marker;
|
|
174
|
-
const markerProps = { color: '#111', x: 0, y: 0, isHighlighted: false, isFaded: false, size: 5 };
|
|
175
|
-
|
|
176
|
-
// If sized per-series, "small-series"'s own only point (pop:100) would be its own series
|
|
177
|
-
// max and render at full radius (10) -- wrongly implying it's as large as big-series's point.
|
|
178
|
-
const smallSeriesPoint = render(React.createElement(Marker, { ...markerProps, seriesId: 'small-series', dataIndex: 0 }));
|
|
179
|
-
const bigSeriesPoint = render(React.createElement(Marker, { ...markerProps, seriesId: 'big-series', dataIndex: 0 }));
|
|
180
|
-
|
|
181
|
-
const smallR = Number(smallSeriesPoint.container.querySelector('circle').getAttribute('r'));
|
|
182
|
-
const bigR = Number(bigSeriesPoint.container.querySelector('circle').getAttribute('r'));
|
|
183
|
-
expect(bigR).toBeCloseTo(10, 5);
|
|
184
|
-
expect(smallR).toBeCloseTo(10 * Math.sqrt(100 / 1000), 5);
|
|
185
|
-
expect(smallR).toBeLessThan(bigR);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('renders a square/diamond shape when getPointStyle requests one', () => {
|
|
189
|
-
renderScatter({
|
|
190
|
-
getPointStyle: (p) => ({ shape: p.shape }),
|
|
191
|
-
series: [{ id: 's', data: [{ x: 1, y: 1, shape: 'square' }, { x: 2, y: 2, shape: 'diamond' }] }],
|
|
192
|
-
});
|
|
193
|
-
const Marker = scatterSpy.mock.calls.at(-1)[0].slots.marker;
|
|
194
|
-
const markerProps = { seriesId: 's', color: '#111', x: 0, y: 0, isHighlighted: false, isFaded: false, size: 5 };
|
|
195
|
-
|
|
196
|
-
const square = render(React.createElement(Marker, { ...markerProps, dataIndex: 0 }));
|
|
197
|
-
const diamond = render(React.createElement(Marker, { ...markerProps, dataIndex: 1 }));
|
|
198
|
-
|
|
199
|
-
expect(square.container.querySelector('rect')).not.toBeNull();
|
|
200
|
-
expect(diamond.container.querySelector('polygon')).not.toBeNull();
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
describe('ScatterReferenceCurve / ScatterReferenceLine (THEME-10)', () => {
|
|
205
|
-
it('draws a path through the curve points using the chart\'s own live scale', () => {
|
|
206
|
-
const { container } = render(React.createElement(ScatterReferenceCurve, {
|
|
207
|
-
points: [{ x: 0, y: 0 }, { x: 1, y: 1 }],
|
|
208
|
-
color: '#222',
|
|
209
|
-
}));
|
|
210
|
-
expect(container.querySelector('path').getAttribute('d')).toBe('M0,100 L10,90');
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('draws a labelled reference line at the given label position', () => {
|
|
214
|
-
const { container } = render(React.createElement(ScatterReferenceLine, {
|
|
215
|
-
from: { x: 0, y: 0 },
|
|
216
|
-
to: { x: 1, y: 1 },
|
|
217
|
-
label: 'No gap',
|
|
218
|
-
labelAt: { x: 0.9, y: 0.9 },
|
|
219
|
-
color: '#222',
|
|
220
|
-
}));
|
|
221
|
-
expect(container.querySelector('line')).not.toBeNull();
|
|
222
|
-
expect(container.textContent).toBe('No gap');
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('draws an unlabelled reference line when no label is given', () => {
|
|
226
|
-
const { container } = render(React.createElement(ScatterReferenceLine, {
|
|
227
|
-
from: { x: 0, y: 0 },
|
|
228
|
-
to: { x: 1, y: 1 },
|
|
229
|
-
color: '#222',
|
|
230
|
-
}));
|
|
231
|
-
expect(container.querySelector('line')).not.toBeNull();
|
|
232
|
-
expect(container.querySelector('text')).toBeNull();
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// UCM-CHART-12: `size`/`height` resolution, wired through the actual component. Replaces the
|
|
237
|
-
// deleted `minHeight`/`aspect` CHART-8 trio.
|
|
238
|
-
describe('ScatterChart size/height resolution (UCM-CHART-12, UCM-CHART-15)', () => {
|
|
239
|
-
afterEach(() => { cleanup(); vi.clearAllMocks(); });
|
|
240
|
-
|
|
241
|
-
it.each([
|
|
242
|
-
['compact', 320],
|
|
243
|
-
['standard', 400],
|
|
244
|
-
['tall', 480],
|
|
245
|
-
['extra_tall', 560],
|
|
246
|
-
['super_tall', 640],
|
|
247
|
-
])('resolves size="%s" to %ipx, on both the chart height and the wrapper', (size, px) => {
|
|
248
|
-
renderScatter({ size });
|
|
249
|
-
expect(scatterSpy.mock.calls.at(-1)[0].height).toBe(px);
|
|
250
|
-
expect(window.getComputedStyle(screen.getByTestId('scatter-chart-container')).height).toBe(`${px}px`);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('lets height override size entirely, as the documented escape', () => {
|
|
254
|
-
renderScatter({ size: 'tall', height: 280 });
|
|
255
|
-
expect(scatterSpy.mock.calls.at(-1)[0].height).toBe(280);
|
|
256
|
-
expect(window.getComputedStyle(screen.getByTestId('scatter-chart-container')).height).toBe('280px');
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('throws in dev when a caller still passes minHeight, naming the replacement', () => {
|
|
260
|
-
expect(() => renderScatter({ minHeight: 300 })).toThrow(/size=.*standard.*tall/);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('throws in dev when a caller still passes aspect, naming the removal', () => {
|
|
264
|
-
expect(() => renderScatter({ aspect: 1.8 })).toThrow(/aspect/);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it('throws in dev when a caller still passes margin, naming the removal', () => {
|
|
268
|
-
expect(() => renderScatter({ margin: { top: 1 } })).toThrow(/margin/);
|
|
269
|
-
});
|
|
270
|
-
});
|
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
-
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
5
|
-
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
|
6
|
-
|
|
7
|
-
const translation = vi.hoisted(() => ({
|
|
8
|
-
t: vi.fn((key) => ({
|
|
9
|
-
'SectionNav.TITLE': 'Switch section',
|
|
10
|
-
'SectionNav.TRIGGER_EYEBROW': 'Section',
|
|
11
|
-
'SectionNav.LAST_OPENED': 'Last opened',
|
|
12
|
-
}[key] ?? key)),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
vi.mock('react-i18next', () => ({ useTranslation: () => translation }));
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
SECTION_NAV_DRAWER_PAPER_SX,
|
|
19
|
-
SectionNav,
|
|
20
|
-
} from '../src/layout/SectionNav';
|
|
21
|
-
import { sectionNavTranslations } from '../src/i18n/sectionNavTranslations';
|
|
22
|
-
import { createAppTheme } from '../src/theme/createAppTheme';
|
|
23
|
-
|
|
24
|
-
const groups = [
|
|
25
|
-
{
|
|
26
|
-
key: 'account',
|
|
27
|
-
label: 'My account',
|
|
28
|
-
items: [
|
|
29
|
-
{ key: 'profile', label: 'Profile' },
|
|
30
|
-
{ key: 'security', label: 'Security' },
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
key: 'management',
|
|
35
|
-
label: 'Management',
|
|
36
|
-
items: [
|
|
37
|
-
{ key: 'users', label: 'Users' },
|
|
38
|
-
{ key: 'invite', label: 'Invite' },
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
key: 'help',
|
|
43
|
-
label: 'Help',
|
|
44
|
-
items: [{ key: 'support', label: 'Support' }],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
key: 'more',
|
|
48
|
-
label: 'More',
|
|
49
|
-
items: [{ key: 'notifications', label: 'Notifications' }],
|
|
50
|
-
},
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
const theme = createAppTheme({ palette: { primary: { main: '#0F62FE' } } });
|
|
54
|
-
|
|
55
|
-
function renderNav(props = {}, currentTheme = theme) {
|
|
56
|
-
return render(
|
|
57
|
-
<ThemeProvider theme={currentTheme}>
|
|
58
|
-
<SectionNav
|
|
59
|
-
mode="desktop"
|
|
60
|
-
groups={groups}
|
|
61
|
-
activeKey="profile"
|
|
62
|
-
onSelect={vi.fn()}
|
|
63
|
-
{...props}
|
|
64
|
-
>
|
|
65
|
-
<div data-testid="section-content">Section content</div>
|
|
66
|
-
</SectionNav>
|
|
67
|
-
</ThemeProvider>,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function expectListsToContainListItemButtons(lists) {
|
|
72
|
-
expect(lists.length).toBeGreaterThan(0);
|
|
73
|
-
lists.forEach((list) => {
|
|
74
|
-
expect(Array.from(list.children).every((child) => child.tagName === 'LI')).toBe(true);
|
|
75
|
-
expect(list.querySelectorAll(':scope > li')).toHaveLength(list.children.length);
|
|
76
|
-
list.querySelectorAll(':scope > li').forEach((item) => {
|
|
77
|
-
expect(item.querySelectorAll(':scope > .MuiListItemButton-root')).toHaveLength(1);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
describe('SectionNav', () => {
|
|
83
|
-
afterEach(() => {
|
|
84
|
-
cleanup();
|
|
85
|
-
vi.clearAllMocks();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('renders finished group and item labels in order, one section per item', () => {
|
|
89
|
-
renderNav();
|
|
90
|
-
|
|
91
|
-
expect(screen.getAllByRole('button').map((item) => item.textContent)).toEqual([
|
|
92
|
-
'Profile',
|
|
93
|
-
'Security',
|
|
94
|
-
'Users',
|
|
95
|
-
'Invite',
|
|
96
|
-
'Support',
|
|
97
|
-
'Notifications',
|
|
98
|
-
]);
|
|
99
|
-
expect(screen.getAllByText(/My account|Management|Help|More/).map((item) => item.textContent))
|
|
100
|
-
.toEqual(['My account', 'Management', 'Help', 'More']);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('selects exactly the matching item, tolerates no match, and selects an optional overview for null', () => {
|
|
104
|
-
const { rerender } = renderNav({ activeKey: 'users', overviewItem: { label: 'Overview' } });
|
|
105
|
-
const selected = () => screen.getAllByRole('button')
|
|
106
|
-
.filter((item) => item.classList.contains('Mui-selected'));
|
|
107
|
-
|
|
108
|
-
expect(selected()).toHaveLength(1);
|
|
109
|
-
expect(selected()[0].textContent).toBe('Users');
|
|
110
|
-
|
|
111
|
-
rerender(
|
|
112
|
-
<ThemeProvider theme={theme}>
|
|
113
|
-
<SectionNav
|
|
114
|
-
mode="desktop"
|
|
115
|
-
groups={groups}
|
|
116
|
-
activeKey="missing"
|
|
117
|
-
overviewItem={{ label: 'Overview' }}
|
|
118
|
-
onSelect={vi.fn()}
|
|
119
|
-
/>
|
|
120
|
-
</ThemeProvider>,
|
|
121
|
-
);
|
|
122
|
-
expect(selected()).toHaveLength(0);
|
|
123
|
-
|
|
124
|
-
rerender(
|
|
125
|
-
<ThemeProvider theme={theme}>
|
|
126
|
-
<SectionNav
|
|
127
|
-
mode="desktop"
|
|
128
|
-
groups={groups}
|
|
129
|
-
activeKey={null}
|
|
130
|
-
overviewItem={{ label: 'Overview' }}
|
|
131
|
-
onSelect={vi.fn()}
|
|
132
|
-
/>
|
|
133
|
-
</ThemeProvider>,
|
|
134
|
-
);
|
|
135
|
-
expect(selected()).toHaveLength(1);
|
|
136
|
-
expect(selected()[0].textContent).toBe('Overview');
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('omits overview and remembered UI completely when those props are absent', () => {
|
|
140
|
-
const { container } = renderNav();
|
|
141
|
-
|
|
142
|
-
expect(screen.queryByText('Overview')).toBeNull();
|
|
143
|
-
expect(screen.queryByText('Last opened')).toBeNull();
|
|
144
|
-
expect(container.querySelectorAll('.MuiPaper-root')).toHaveLength(groups.length);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('renders overview and grouped controls inside list items on desktop and mobile', () => {
|
|
148
|
-
const overviewItem = { label: 'Overview' };
|
|
149
|
-
const { container } = renderNav({ overviewItem });
|
|
150
|
-
|
|
151
|
-
expectListsToContainListItemButtons(container.querySelectorAll('nav ul'));
|
|
152
|
-
expect(container.querySelectorAll('nav ul')).toHaveLength(groups.length + 1);
|
|
153
|
-
|
|
154
|
-
cleanup();
|
|
155
|
-
renderNav({ mode: 'mobile', overviewItem, open: true });
|
|
156
|
-
const dialog = screen.getByRole('dialog', { name: 'Switch section' });
|
|
157
|
-
const lists = dialog.querySelectorAll('ul');
|
|
158
|
-
|
|
159
|
-
expectListsToContainListItemButtons(lists);
|
|
160
|
-
expect(lists).toHaveLength(groups.length + 1);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('passes null for overview selection and closes the mobile drawer', async () => {
|
|
164
|
-
const onSelect = vi.fn();
|
|
165
|
-
renderNav({ mode: 'mobile', onSelect, overviewItem: { label: 'Overview' } });
|
|
166
|
-
|
|
167
|
-
fireEvent.click(screen.getByRole('button', { name: /Section Profile/i }));
|
|
168
|
-
expect(screen.getByRole('dialog', { name: 'Switch section' })).toBeTruthy();
|
|
169
|
-
fireEvent.click(screen.getByRole('button', { name: 'Overview' }));
|
|
170
|
-
|
|
171
|
-
expect(onSelect).toHaveBeenCalledOnce();
|
|
172
|
-
expect(onSelect).toHaveBeenCalledWith(null);
|
|
173
|
-
await waitFor(() => expect(screen.queryByRole('dialog', { name: 'Switch section' })).toBeNull());
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('shows the remembered secondary line only on its unselected item', () => {
|
|
177
|
-
renderNav({ activeKey: 'profile', rememberedKey: 'security' });
|
|
178
|
-
expect(screen.getByText('Last opened')).toBeTruthy();
|
|
179
|
-
|
|
180
|
-
cleanup();
|
|
181
|
-
renderNav({ activeKey: 'security', rememberedKey: 'security' });
|
|
182
|
-
expect(screen.queryByText('Last opened')).toBeNull();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('calls onSelect with the item key and closes the uncontrolled drawer', async () => {
|
|
186
|
-
const onSelect = vi.fn();
|
|
187
|
-
renderNav({ mode: 'mobile', onSelect });
|
|
188
|
-
|
|
189
|
-
const trigger = screen.getByRole('button', { name: /Section Profile/i });
|
|
190
|
-
expect(trigger).toBeTruthy();
|
|
191
|
-
expect(trigger.getAttribute('aria-expanded')).toBe('false');
|
|
192
|
-
fireEvent.click(trigger);
|
|
193
|
-
expect(trigger.getAttribute('aria-expanded')).toBe('true');
|
|
194
|
-
|
|
195
|
-
fireEvent.click(screen.getByRole('button', { name: 'Security' }));
|
|
196
|
-
expect(onSelect).toHaveBeenCalledOnce();
|
|
197
|
-
expect(onSelect).toHaveBeenCalledWith('security');
|
|
198
|
-
expect(trigger.getAttribute('aria-expanded')).toBe('false');
|
|
199
|
-
await waitFor(() => expect(screen.queryByRole('button', { name: 'Security' })).toBeNull());
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('defers to the caller in controlled mode: the trigger calls onOpen, not internal state', () => {
|
|
203
|
-
const onOpen = vi.fn();
|
|
204
|
-
const onClose = vi.fn();
|
|
205
|
-
const { rerender } = renderNav({ mode: 'mobile', open: false, onOpen, onClose });
|
|
206
|
-
|
|
207
|
-
const trigger = screen.getByRole('button', { name: /Section Profile/i });
|
|
208
|
-
expect(trigger).toBeTruthy();
|
|
209
|
-
fireEvent.click(trigger);
|
|
210
|
-
|
|
211
|
-
expect(onOpen).toHaveBeenCalledOnce();
|
|
212
|
-
expect(trigger.getAttribute('aria-expanded')).toBe('false');
|
|
213
|
-
expect(screen.queryByRole('button', { name: 'Security' })).toBeNull();
|
|
214
|
-
|
|
215
|
-
rerender(
|
|
216
|
-
<ThemeProvider theme={theme}>
|
|
217
|
-
<SectionNav
|
|
218
|
-
mode="mobile"
|
|
219
|
-
groups={groups}
|
|
220
|
-
activeKey="profile"
|
|
221
|
-
onSelect={vi.fn()}
|
|
222
|
-
open
|
|
223
|
-
onOpen={onOpen}
|
|
224
|
-
onClose={onClose}
|
|
225
|
-
>
|
|
226
|
-
<div data-testid="section-content">Section content</div>
|
|
227
|
-
</SectionNav>
|
|
228
|
-
</ThemeProvider>,
|
|
229
|
-
);
|
|
230
|
-
expect(trigger.getAttribute('aria-expanded')).toBe('true');
|
|
231
|
-
expect(screen.getByRole('button', { name: 'Security' })).toBeTruthy();
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('omits a dead controlled trigger while keeping children and the caller-driven drawer', () => {
|
|
235
|
-
const { rerender } = renderNav({ mode: 'mobile', open: false });
|
|
236
|
-
|
|
237
|
-
expect(screen.queryByRole('button', { name: /Section Profile/i })).toBeNull();
|
|
238
|
-
expect(screen.getByTestId('section-content')).toBeTruthy();
|
|
239
|
-
expect(screen.queryByRole('dialog', { name: 'Switch section' })).toBeNull();
|
|
240
|
-
|
|
241
|
-
rerender(
|
|
242
|
-
<ThemeProvider theme={theme}>
|
|
243
|
-
<SectionNav
|
|
244
|
-
mode="mobile"
|
|
245
|
-
groups={groups}
|
|
246
|
-
activeKey="profile"
|
|
247
|
-
onSelect={vi.fn()}
|
|
248
|
-
open
|
|
249
|
-
>
|
|
250
|
-
<div data-testid="section-content">Section content</div>
|
|
251
|
-
</SectionNav>
|
|
252
|
-
</ThemeProvider>,
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
expect(screen.queryByRole('button', { name: /Section Profile/i })).toBeNull();
|
|
256
|
-
expect(screen.getByTestId('section-content')).toBeTruthy();
|
|
257
|
-
expect(screen.getByRole('dialog', { name: 'Switch section' })).toBeTruthy();
|
|
258
|
-
expect(screen.getByRole('button', { name: 'Security' })).toBeTruthy();
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('keeps the desktop sidebar and children for every open/onOpen combination', () => {
|
|
262
|
-
const propCombinations = [
|
|
263
|
-
{},
|
|
264
|
-
{ open: false, onOpen: vi.fn() },
|
|
265
|
-
{ open: false },
|
|
266
|
-
];
|
|
267
|
-
|
|
268
|
-
propCombinations.forEach((props) => {
|
|
269
|
-
const { container } = renderNav(props);
|
|
270
|
-
|
|
271
|
-
expect(container.querySelector('[data-section-nav-mode="desktop"]')).toBeTruthy();
|
|
272
|
-
expect(container.querySelector('nav')).toBeTruthy();
|
|
273
|
-
expect(screen.getByTestId('section-content')).toBeTruthy();
|
|
274
|
-
expect(screen.queryByRole('button', { name: /Section Profile/i })).toBeNull();
|
|
275
|
-
expect(document.querySelector('.MuiDrawer-root')).toBeNull();
|
|
276
|
-
|
|
277
|
-
cleanup();
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('renders the desktop sidebar and children in a two-column grid, while mobile renders a trigger and no sidebar', () => {
|
|
282
|
-
const { container } = renderNav({ sidebarWidth: 312 });
|
|
283
|
-
const desktopRoot = container.querySelector('[data-section-nav-mode="desktop"]');
|
|
284
|
-
|
|
285
|
-
expect(desktopRoot).toBeTruthy();
|
|
286
|
-
expect(getComputedStyle(desktopRoot).display).toBe('grid');
|
|
287
|
-
expect(getComputedStyle(desktopRoot).gridTemplateColumns).toBe('312px minmax(0, 1fr)');
|
|
288
|
-
expect(container.querySelector('nav')).toBeTruthy();
|
|
289
|
-
expect(screen.getByTestId('section-content')).toBeTruthy();
|
|
290
|
-
|
|
291
|
-
cleanup();
|
|
292
|
-
const mobile = renderNav({ mode: 'mobile' });
|
|
293
|
-
expect(screen.getByRole('button', { name: /Section Profile/i })).toBeTruthy();
|
|
294
|
-
expect(mobile.container.querySelector('nav')).toBeNull();
|
|
295
|
-
expect(mobile.container.querySelector('[data-section-nav-mode="desktop"]')).toBeNull();
|
|
296
|
-
expect(screen.getByTestId('section-content')).toBeTruthy();
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it('resolves the default drawer z-index above the SHELL-3 bottom bar', () => {
|
|
300
|
-
renderNav({ mode: 'mobile' });
|
|
301
|
-
fireEvent.click(screen.getByRole('button', { name: /Section Profile/i }));
|
|
302
|
-
const drawer = document.querySelector('.MuiDrawer-root');
|
|
303
|
-
const drawerZIndex = Number(getComputedStyle(drawer).zIndex);
|
|
304
|
-
|
|
305
|
-
expect(drawerZIndex).toBe(theme.zIndex.drawer + 3);
|
|
306
|
-
expect(drawerZIndex).toBeGreaterThan(theme.zIndex.drawer + 2);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('follows surface and divider tokens under both createAppTheme and plain MUI themes', () => {
|
|
310
|
-
const themes = [
|
|
311
|
-
createAppTheme({
|
|
312
|
-
palette: {
|
|
313
|
-
primary: { main: '#123456' },
|
|
314
|
-
background: { paper: '#F1F2F3' },
|
|
315
|
-
divider: '#445566',
|
|
316
|
-
},
|
|
317
|
-
}),
|
|
318
|
-
createTheme({
|
|
319
|
-
palette: {
|
|
320
|
-
primary: { main: '#654321' },
|
|
321
|
-
background: { paper: '#E7E8E9' },
|
|
322
|
-
divider: '#665544',
|
|
323
|
-
},
|
|
324
|
-
}),
|
|
325
|
-
];
|
|
326
|
-
const expected = [
|
|
327
|
-
{ surface: 'rgb(241, 242, 243)', divider: 'rgb(68, 85, 102)' },
|
|
328
|
-
{ surface: 'rgb(231, 232, 233)', divider: 'rgb(102, 85, 68)' },
|
|
329
|
-
];
|
|
330
|
-
|
|
331
|
-
themes.forEach((currentTheme, index) => {
|
|
332
|
-
const { container } = renderNav({}, currentTheme);
|
|
333
|
-
const groupPaper = container.querySelector('.MuiPaper-root');
|
|
334
|
-
const groupHeader = groupPaper.querySelector('[id]');
|
|
335
|
-
|
|
336
|
-
expect(getComputedStyle(groupPaper).backgroundColor).toBe(expected[index].surface);
|
|
337
|
-
expect(getComputedStyle(groupHeader).borderBottomColor).toBe(expected[index].divider);
|
|
338
|
-
cleanup();
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it('keeps the promoted drawer dimensions and safe-area declaration', () => {
|
|
343
|
-
expect(SECTION_NAV_DRAWER_PAPER_SX).toMatchObject({
|
|
344
|
-
borderTopLeftRadius: 18,
|
|
345
|
-
borderTopRightRadius: 18,
|
|
346
|
-
maxHeight: '78dvh',
|
|
347
|
-
p: 2,
|
|
348
|
-
pb: 'max(16px, env(safe-area-inset-bottom))',
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('ships every component-owned chrome string in all supported locales', () => {
|
|
353
|
-
expect(Object.keys(sectionNavTranslations)).toEqual([
|
|
354
|
-
'SectionNav.TITLE',
|
|
355
|
-
'SectionNav.TRIGGER_EYEBROW',
|
|
356
|
-
'SectionNav.LAST_OPENED',
|
|
357
|
-
]);
|
|
358
|
-
Object.values(sectionNavTranslations).forEach((translations) => {
|
|
359
|
-
expect(Object.keys(translations)).toEqual(['de', 'fr', 'en', 'sw']);
|
|
360
|
-
expect(Object.values(translations).every(Boolean)).toBe(true);
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
});
|