@happyvertical/smrt-ui 0.30.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/AGENTS.md +50 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/dist/actions/__tests__/ripple.test.js +28 -0
- package/dist/actions/permission.d.ts +34 -0
- package/dist/actions/permission.d.ts.map +1 -0
- package/dist/actions/permission.js +70 -0
- package/dist/actions/ripple.d.ts +7 -0
- package/dist/actions/ripple.d.ts.map +1 -0
- package/dist/actions/ripple.js +65 -0
- package/dist/components/calendar/Calendar.svelte +520 -0
- package/dist/components/calendar/Calendar.svelte.d.ts +17 -0
- package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -0
- package/dist/components/calendar/DayView.svelte +389 -0
- package/dist/components/calendar/DayView.svelte.d.ts +13 -0
- package/dist/components/calendar/DayView.svelte.d.ts.map +1 -0
- package/dist/components/calendar/index.d.ts +6 -0
- package/dist/components/calendar/index.d.ts.map +1 -0
- package/dist/components/calendar/index.js +5 -0
- package/dist/components/chat/MessageBubble.svelte +126 -0
- package/dist/components/chat/MessageBubble.svelte.d.ts +30 -0
- package/dist/components/chat/MessageBubble.svelte.d.ts.map +1 -0
- package/dist/components/chat/ReactionPicker.svelte +89 -0
- package/dist/components/chat/ReactionPicker.svelte.d.ts +19 -0
- package/dist/components/chat/ReactionPicker.svelte.d.ts.map +1 -0
- package/dist/components/chat/TypingIndicator.svelte +90 -0
- package/dist/components/chat/TypingIndicator.svelte.d.ts +17 -0
- package/dist/components/chat/TypingIndicator.svelte.d.ts.map +1 -0
- package/dist/components/chat/__tests__/chat-primitives.test.js +67 -0
- package/dist/components/chat/index.d.ts +10 -0
- package/dist/components/chat/index.d.ts.map +1 -0
- package/dist/components/chat/index.js +9 -0
- package/dist/components/data/DataTable.svelte +519 -0
- package/dist/components/data/DataTable.svelte.d.ts +49 -0
- package/dist/components/data/DataTable.svelte.d.ts.map +1 -0
- package/dist/components/data/__tests__/DataTable.test.js +48 -0
- package/dist/components/data/__tests__/data-table-helpers.test.js +36 -0
- package/dist/components/data/index.d.ts +6 -0
- package/dist/components/data/index.d.ts.map +1 -0
- package/dist/components/data/index.js +5 -0
- package/dist/components/data/types.d.ts +104 -0
- package/dist/components/data/types.d.ts.map +1 -0
- package/dist/components/data/types.js +45 -0
- package/dist/components/display/ConfidenceBadge.svelte +142 -0
- package/dist/components/display/ConfidenceBadge.svelte.d.ts +25 -0
- package/dist/components/display/ConfidenceBadge.svelte.d.ts.map +1 -0
- package/dist/components/display/CurrencyDisplay.svelte +106 -0
- package/dist/components/display/CurrencyDisplay.svelte.d.ts +30 -0
- package/dist/components/display/CurrencyDisplay.svelte.d.ts.map +1 -0
- package/dist/components/display/DateDisplay.svelte +122 -0
- package/dist/components/display/DateDisplay.svelte.d.ts +24 -0
- package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -0
- package/dist/components/display/Icon.svelte +77 -0
- package/dist/components/display/Icon.svelte.d.ts +28 -0
- package/dist/components/display/Icon.svelte.d.ts.map +1 -0
- package/dist/components/display/StatusBadge.svelte +256 -0
- package/dist/components/display/StatusBadge.svelte.d.ts +24 -0
- package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -0
- package/dist/components/display/__tests__/ConfidenceBadge.test.js +96 -0
- package/dist/components/display/__tests__/CurrencyDisplay.test.js +114 -0
- package/dist/components/display/__tests__/DateDisplay.test.js +114 -0
- package/dist/components/display/__tests__/Icon.test.js +93 -0
- package/dist/components/display/__tests__/StatusBadge.test.js +98 -0
- package/dist/components/display/index.d.ts +10 -0
- package/dist/components/display/index.d.ts.map +1 -0
- package/dist/components/display/index.js +9 -0
- package/dist/components/display/types.d.ts +5 -0
- package/dist/components/display/types.d.ts.map +1 -0
- package/dist/components/display/types.js +4 -0
- package/dist/components/feedback/ConfirmDialog.svelte +226 -0
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts +25 -0
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -0
- package/dist/components/feedback/LoadingOverlay.svelte +281 -0
- package/dist/components/feedback/LoadingOverlay.svelte.d.ts +31 -0
- package/dist/components/feedback/LoadingOverlay.svelte.d.ts.map +1 -0
- package/dist/components/feedback/Modal.svelte +393 -0
- package/dist/components/feedback/Modal.svelte.d.ts +46 -0
- package/dist/components/feedback/Modal.svelte.d.ts.map +1 -0
- package/dist/components/feedback/ProgressBar.svelte +162 -0
- package/dist/components/feedback/ProgressBar.svelte.d.ts +21 -0
- package/dist/components/feedback/ProgressBar.svelte.d.ts.map +1 -0
- package/dist/components/feedback/__tests__/ConfirmDialog.test.js +111 -0
- package/dist/components/feedback/__tests__/LoadingOverlay.test.js +99 -0
- package/dist/components/feedback/__tests__/Modal.test.js +72 -0
- package/dist/components/feedback/__tests__/ProgressBar.test.js +89 -0
- package/dist/components/feedback/index.d.ts +8 -0
- package/dist/components/feedback/index.d.ts.map +1 -0
- package/dist/components/feedback/index.js +10 -0
- package/dist/components/layout/Container.svelte +53 -0
- package/dist/components/layout/Container.svelte.d.ts +11 -0
- package/dist/components/layout/Container.svelte.d.ts.map +1 -0
- package/dist/components/layout/EmptyState.svelte +187 -0
- package/dist/components/layout/EmptyState.svelte.d.ts +28 -0
- package/dist/components/layout/EmptyState.svelte.d.ts.map +1 -0
- package/dist/components/layout/Footer.svelte +63 -0
- package/dist/components/layout/Footer.svelte.d.ts +8 -0
- package/dist/components/layout/Footer.svelte.d.ts.map +1 -0
- package/dist/components/layout/Grid.svelte +241 -0
- package/dist/components/layout/Grid.svelte.d.ts +56 -0
- package/dist/components/layout/Grid.svelte.d.ts.map +1 -0
- package/dist/components/layout/Header.svelte +86 -0
- package/dist/components/layout/Header.svelte.d.ts +9 -0
- package/dist/components/layout/Header.svelte.d.ts.map +1 -0
- package/dist/components/layout/Masthead.svelte +219 -0
- package/dist/components/layout/Masthead.svelte.d.ts +13 -0
- package/dist/components/layout/Masthead.svelte.d.ts.map +1 -0
- package/dist/components/layout/PageHeader.svelte +131 -0
- package/dist/components/layout/PageHeader.svelte.d.ts +26 -0
- package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -0
- package/dist/components/layout/SummaryCard.svelte +203 -0
- package/dist/components/layout/SummaryCard.svelte.d.ts +20 -0
- package/dist/components/layout/SummaryCard.svelte.d.ts.map +1 -0
- package/dist/components/layout/__tests__/Container.test.js +62 -0
- package/dist/components/layout/__tests__/EmptyState.test.js +83 -0
- package/dist/components/layout/__tests__/Footer.test.js +50 -0
- package/dist/components/layout/__tests__/Grid.test.js +121 -0
- package/dist/components/layout/__tests__/Header.test.js +48 -0
- package/dist/components/layout/__tests__/Masthead.test.js +93 -0
- package/dist/components/layout/__tests__/PageHeader.test.js +80 -0
- package/dist/components/layout/__tests__/SummaryCard.test.js +82 -0
- package/dist/components/layout/index.d.ts +12 -0
- package/dist/components/layout/index.d.ts.map +1 -0
- package/dist/components/layout/index.js +11 -0
- package/dist/components/memberships/MembershipCard.svelte +163 -0
- package/dist/components/memberships/MembershipCard.svelte.d.ts +12 -0
- package/dist/components/memberships/MembershipCard.svelte.d.ts.map +1 -0
- package/dist/components/memberships/MembershipList.svelte +98 -0
- package/dist/components/memberships/MembershipList.svelte.d.ts +19 -0
- package/dist/components/memberships/MembershipList.svelte.d.ts.map +1 -0
- package/dist/components/nav/FilterChips.svelte +152 -0
- package/dist/components/nav/FilterChips.svelte.d.ts +19 -0
- package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -0
- package/dist/components/nav/Tabs.svelte +252 -0
- package/dist/components/nav/Tabs.svelte.d.ts +34 -0
- package/dist/components/nav/Tabs.svelte.d.ts.map +1 -0
- package/dist/components/nav/__tests__/FilterChips.test.js +94 -0
- package/dist/components/nav/__tests__/Tabs.test.js +128 -0
- package/dist/components/nav/index.d.ts +7 -0
- package/dist/components/nav/index.d.ts.map +1 -0
- package/dist/components/nav/index.js +6 -0
- package/dist/components/nav/types.d.ts +24 -0
- package/dist/components/nav/types.d.ts.map +1 -0
- package/dist/components/nav/types.js +4 -0
- package/dist/components/permissions/PermissionCheck.svelte +45 -0
- package/dist/components/permissions/PermissionCheck.svelte.d.ts +19 -0
- package/dist/components/permissions/PermissionCheck.svelte.d.ts.map +1 -0
- package/dist/components/roles/RoleBadge.svelte +84 -0
- package/dist/components/roles/RoleBadge.svelte.d.ts +13 -0
- package/dist/components/roles/RoleBadge.svelte.d.ts.map +1 -0
- package/dist/components/roles/RoleSelector.svelte +216 -0
- package/dist/components/roles/RoleSelector.svelte.d.ts +13 -0
- package/dist/components/roles/RoleSelector.svelte.d.ts.map +1 -0
- package/dist/components/theme/ThemeProvider.svelte +71 -0
- package/dist/components/theme/ThemeProvider.svelte.d.ts +10 -0
- package/dist/components/theme/ThemeProvider.svelte.d.ts.map +1 -0
- package/dist/components/theme/context.svelte.d.ts +15 -0
- package/dist/components/theme/context.svelte.d.ts.map +1 -0
- package/dist/components/theme/context.svelte.js +42 -0
- package/dist/components/theme/index.d.ts +3 -0
- package/dist/components/theme/index.d.ts.map +1 -0
- package/dist/components/theme/index.js +2 -0
- package/dist/components/ui/Avatar.svelte +167 -0
- package/dist/components/ui/Avatar.svelte.d.ts +26 -0
- package/dist/components/ui/Avatar.svelte.d.ts.map +1 -0
- package/dist/components/ui/Badge.svelte +70 -0
- package/dist/components/ui/Badge.svelte.d.ts +12 -0
- package/dist/components/ui/Badge.svelte.d.ts.map +1 -0
- package/dist/components/ui/Button.svelte +226 -0
- package/dist/components/ui/Button.svelte.d.ts +28 -0
- package/dist/components/ui/Button.svelte.d.ts.map +1 -0
- package/dist/components/ui/Card.svelte +122 -0
- package/dist/components/ui/Card.svelte.d.ts +15 -0
- package/dist/components/ui/Card.svelte.d.ts.map +1 -0
- package/dist/components/ui/Chip.svelte +167 -0
- package/dist/components/ui/Chip.svelte.d.ts +33 -0
- package/dist/components/ui/Chip.svelte.d.ts.map +1 -0
- package/dist/components/ui/Dropdown.svelte +250 -0
- package/dist/components/ui/Dropdown.svelte.d.ts +20 -0
- package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -0
- package/dist/components/ui/Pagination.svelte +294 -0
- package/dist/components/ui/Pagination.svelte.d.ts +21 -0
- package/dist/components/ui/Pagination.svelte.d.ts.map +1 -0
- package/dist/components/ui/Skeleton.svelte +113 -0
- package/dist/components/ui/Skeleton.svelte.d.ts +24 -0
- package/dist/components/ui/Skeleton.svelte.d.ts.map +1 -0
- package/dist/components/ui/Tooltip.svelte +120 -0
- package/dist/components/ui/Tooltip.svelte.d.ts +24 -0
- package/dist/components/ui/Tooltip.svelte.d.ts.map +1 -0
- package/dist/components/ui/Tree.svelte +209 -0
- package/dist/components/ui/Tree.svelte.d.ts +17 -0
- package/dist/components/ui/Tree.svelte.d.ts.map +1 -0
- package/dist/components/ui/__tests__/Badge.test.js +76 -0
- package/dist/components/ui/__tests__/Button.test.js +69 -0
- package/dist/components/ui/__tests__/Card.test.js +103 -0
- package/dist/components/ui/__tests__/Pagination.test.js +99 -0
- package/dist/components/ui/__tests__/gap-primitives-interactive.test.js +112 -0
- package/dist/components/ui/__tests__/gap-primitives.test.js +84 -0
- package/dist/components/ui/index.d.ts +14 -0
- package/dist/components/ui/index.d.ts.map +1 -0
- package/dist/components/ui/index.js +18 -0
- package/dist/i18n/Trans.svelte +29 -0
- package/dist/i18n/Trans.svelte.d.ts +24 -0
- package/dist/i18n/Trans.svelte.d.ts.map +1 -0
- package/dist/i18n/__tests__/i18n.test.js +74 -0
- package/dist/i18n/__tests__/render-parity.spec.js +37 -0
- package/dist/i18n/context.svelte.d.ts +43 -0
- package/dist/i18n/context.svelte.d.ts.map +1 -0
- package/dist/i18n/context.svelte.js +69 -0
- package/dist/i18n/index.d.ts +17 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +24 -0
- package/dist/i18n/registry.d.ts +44 -0
- package/dist/i18n/registry.d.ts.map +1 -0
- package/dist/i18n/registry.js +60 -0
- package/dist/i18n/render.d.ts +22 -0
- package/dist/i18n/render.d.ts.map +1 -0
- package/dist/i18n/render.js +44 -0
- package/dist/i18n/strings.d.ts +7 -0
- package/dist/i18n/strings.d.ts.map +1 -0
- package/dist/i18n/strings.js +19 -0
- package/dist/i18n/strings.ui.d.ts +34 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -0
- package/dist/i18n/strings.ui.js +44 -0
- package/dist/i18n/use-i18n.d.ts +20 -0
- package/dist/i18n/use-i18n.d.ts.map +1 -0
- package/dist/i18n/use-i18n.js +21 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/registry/index.d.ts +6 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry/module-registry.d.ts +58 -0
- package/dist/registry/module-registry.d.ts.map +1 -0
- package/dist/registry/module-registry.js +94 -0
- package/dist/styles/index.d.ts +4 -0
- package/dist/styles/index.d.ts.map +1 -0
- package/dist/styles/index.js +6 -0
- package/dist/styles/tokens.css +76 -0
- package/dist/test-support/a11y.d.ts +16 -0
- package/dist/test-support/a11y.d.ts.map +1 -0
- package/dist/test-support/a11y.js +32 -0
- package/dist/test-support/setup.d.ts +11 -0
- package/dist/test-support/setup.d.ts.map +1 -0
- package/dist/test-support/setup.js +33 -0
- package/dist/theme/ThemeProvider.svelte +207 -0
- package/dist/theme/ThemeProvider.svelte.d.ts +22 -0
- package/dist/theme/ThemeProvider.svelte.d.ts.map +1 -0
- package/dist/theme/context.d.ts +49 -0
- package/dist/theme/context.d.ts.map +1 -0
- package/dist/theme/context.js +32 -0
- package/dist/theme/index.d.ts +7 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +9 -0
- package/dist/theme/tokens.d.ts +309 -0
- package/dist/theme/tokens.d.ts.map +1 -0
- package/dist/theme/tokens.js +418 -0
- package/dist/themes/CUSTOM_THEME_GUIDE.md +341 -0
- package/dist/themes/README.md +675 -0
- package/dist/themes/ThemeProvider.svelte +275 -0
- package/dist/themes/ThemeProvider.svelte.d.ts +24 -0
- package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -0
- package/dist/themes/__tests__/css-generator.test.js +32 -0
- package/dist/themes/__tests__/registry.test.js +43 -0
- package/dist/themes/__tests__/token-aliases.test.js +176 -0
- package/dist/themes/components/ColorSchemeToggle.svelte +205 -0
- package/dist/themes/components/ColorSchemeToggle.svelte.d.ts +14 -0
- package/dist/themes/components/ColorSchemeToggle.svelte.d.ts.map +1 -0
- package/dist/themes/components/ThemeSwitcher.svelte +188 -0
- package/dist/themes/components/ThemeSwitcher.svelte.d.ts +14 -0
- package/dist/themes/components/ThemeSwitcher.svelte.d.ts.map +1 -0
- package/dist/themes/components/index.d.ts +8 -0
- package/dist/themes/components/index.d.ts.map +1 -0
- package/dist/themes/components/index.js +7 -0
- package/dist/themes/context.svelte.d.ts +30 -0
- package/dist/themes/context.svelte.d.ts.map +1 -0
- package/dist/themes/context.svelte.js +42 -0
- package/dist/themes/create-theme.d.ts +99 -0
- package/dist/themes/create-theme.d.ts.map +1 -0
- package/dist/themes/create-theme.js +389 -0
- package/dist/themes/css-generator.d.ts +44 -0
- package/dist/themes/css-generator.d.ts.map +1 -0
- package/dist/themes/css-generator.js +226 -0
- package/dist/themes/glass/index.d.ts +14 -0
- package/dist/themes/glass/index.d.ts.map +1 -0
- package/dist/themes/glass/index.js +286 -0
- package/dist/themes/index.d.ts +31 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +37 -0
- package/dist/themes/material/index.d.ts +13 -0
- package/dist/themes/material/index.d.ts.map +1 -0
- package/dist/themes/material/index.js +269 -0
- package/dist/themes/registry.d.ts +64 -0
- package/dist/themes/registry.d.ts.map +1 -0
- package/dist/themes/registry.js +122 -0
- package/dist/themes/shared.d.ts +78 -0
- package/dist/themes/shared.d.ts.map +1 -0
- package/dist/themes/shared.js +179 -0
- package/dist/themes/studio/index.d.ts +14 -0
- package/dist/themes/studio/index.d.ts.map +1 -0
- package/dist/themes/studio/index.js +270 -0
- package/dist/themes/styles/all.css +12 -0
- package/dist/themes/styles/glass.css +432 -0
- package/dist/themes/styles/index.d.ts +22 -0
- package/dist/themes/styles/index.d.ts.map +1 -0
- package/dist/themes/styles/index.js +23 -0
- package/dist/themes/styles/material.css +364 -0
- package/dist/themes/styles/studio.css +416 -0
- package/dist/themes/types.d.ts +273 -0
- package/dist/themes/types.d.ts.map +1 -0
- package/dist/themes/types.js +15 -0
- package/dist/types-generic.d.ts +75 -0
- package/dist/types-generic.d.ts.map +1 -0
- package/dist/types-generic.js +1 -0
- package/dist/utils/forms/__tests__/formatters.test.js +27 -0
- package/dist/utils/forms/formatters.d.ts +14 -0
- package/dist/utils/forms/formatters.d.ts.map +1 -0
- package/dist/utils/forms/formatters.js +77 -0
- package/dist/utils/import-optional.d.ts +5 -0
- package/dist/utils/import-optional.d.ts.map +1 -0
- package/dist/utils/import-optional.js +7 -0
- package/dist/utils/theme/__tests__/color.test.js +72 -0
- package/dist/utils/theme/__tests__/typography.test.js +11 -0
- package/dist/utils/theme/color.d.ts +70 -0
- package/dist/utils/theme/color.d.ts.map +1 -0
- package/dist/utils/theme/color.js +221 -0
- package/dist/utils/theme/typography.d.ts +27 -0
- package/dist/utils/theme/typography.d.ts.map +1 -0
- package/dist/utils/theme/typography.js +30 -0
- package/package.json +143 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface Props {
|
|
2
|
+
/** Card label */
|
|
3
|
+
label: string;
|
|
4
|
+
/** Primary value to display */
|
|
5
|
+
value: string | number;
|
|
6
|
+
/** Optional count badge */
|
|
7
|
+
count?: number;
|
|
8
|
+
/** Highlight the card */
|
|
9
|
+
highlight?: boolean;
|
|
10
|
+
/** Make card clickable with href */
|
|
11
|
+
href?: string;
|
|
12
|
+
/** Optional icon (name for Icon or SVG string) */
|
|
13
|
+
icon?: string;
|
|
14
|
+
/** Value color variant */
|
|
15
|
+
variant?: 'default' | 'success' | 'warning' | 'danger';
|
|
16
|
+
}
|
|
17
|
+
declare const SummaryCard: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type SummaryCard = ReturnType<typeof SummaryCard>;
|
|
19
|
+
export default SummaryCard;
|
|
20
|
+
//# sourceMappingURL=SummaryCard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SummaryCard.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/layout/SummaryCard.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;CACxD;AAoFD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the Container layout primitive (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the Button golden pattern: render via Testing Library, assert the
|
|
5
|
+
* rendered DOM/class/attribute API, and prove axe-clean output.
|
|
6
|
+
*/
|
|
7
|
+
import { render, screen } from '@testing-library/svelte';
|
|
8
|
+
import { createRawSnippet } from 'svelte';
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
11
|
+
import Container from '../Container.svelte';
|
|
12
|
+
/** Build a text Snippet for the `children` prop. */
|
|
13
|
+
function textSnippet(text) {
|
|
14
|
+
return createRawSnippet(() => ({ render: () => `<span>${text}</span>` }));
|
|
15
|
+
}
|
|
16
|
+
describe('Container', () => {
|
|
17
|
+
it('renders slotted children', () => {
|
|
18
|
+
render(Container, { props: { children: textSnippet('Inside') } });
|
|
19
|
+
expect(screen.getByText('Inside')).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
it('applies the default lg max-width class', () => {
|
|
22
|
+
const { container } = render(Container, {
|
|
23
|
+
props: { children: textSnippet('x') },
|
|
24
|
+
});
|
|
25
|
+
const div = container.querySelector('div.container');
|
|
26
|
+
expect(div).not.toBeNull();
|
|
27
|
+
expect(div).toHaveClass('container', 'max-w-lg');
|
|
28
|
+
});
|
|
29
|
+
it.each([
|
|
30
|
+
'sm',
|
|
31
|
+
'md',
|
|
32
|
+
'lg',
|
|
33
|
+
'xl',
|
|
34
|
+
'full',
|
|
35
|
+
])('maps maxWidth=%s to the max-w-%s class', (maxWidth) => {
|
|
36
|
+
const { container } = render(Container, {
|
|
37
|
+
props: { maxWidth, children: textSnippet('x') },
|
|
38
|
+
});
|
|
39
|
+
expect(container.querySelector('div.container')).toHaveClass(`max-w-${maxWidth}`);
|
|
40
|
+
});
|
|
41
|
+
it('passes ...rest attributes through to the root element', () => {
|
|
42
|
+
const { container } = render(Container, {
|
|
43
|
+
props: {
|
|
44
|
+
children: textSnippet('x'),
|
|
45
|
+
id: 'main-region',
|
|
46
|
+
'data-testid': 'wrap',
|
|
47
|
+
role: 'region',
|
|
48
|
+
'aria-label': 'Main content',
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const div = container.querySelector('div.container');
|
|
52
|
+
expect(div).toHaveAttribute('id', 'main-region');
|
|
53
|
+
expect(div).toHaveAttribute('data-testid', 'wrap');
|
|
54
|
+
expect(screen.getByRole('region', { name: 'Main content' })).toBe(div);
|
|
55
|
+
});
|
|
56
|
+
it('is axe-clean with content', async () => {
|
|
57
|
+
const { container } = render(Container, {
|
|
58
|
+
props: { children: textSnippet('Accessible content') },
|
|
59
|
+
});
|
|
60
|
+
await expectNoA11yViolations(container);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the EmptyState layout primitive (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* Asserts the title/description render, the built-in vs. custom-snippet icon,
|
|
5
|
+
* the optional action (link vs. button + onaction callback), the size variant,
|
|
6
|
+
* and axe-clean output.
|
|
7
|
+
*/
|
|
8
|
+
import { render, screen } from '@testing-library/svelte';
|
|
9
|
+
import userEvent from '@testing-library/user-event';
|
|
10
|
+
import { createRawSnippet } from 'svelte';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
13
|
+
import EmptyState from '../EmptyState.svelte';
|
|
14
|
+
function iconSnippet(text) {
|
|
15
|
+
return createRawSnippet(() => ({
|
|
16
|
+
render: () => `<span data-testid="custom-icon">${text}</span>`,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
describe('EmptyState', () => {
|
|
20
|
+
it('renders the title as a heading', () => {
|
|
21
|
+
render(EmptyState, { props: { title: 'No results' } });
|
|
22
|
+
expect(screen.getByRole('heading', { name: 'No results' })).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
it('renders the optional description', () => {
|
|
25
|
+
render(EmptyState, {
|
|
26
|
+
props: { title: 'Empty', description: 'Try adjusting your filters.' },
|
|
27
|
+
});
|
|
28
|
+
expect(screen.getByText('Try adjusting your filters.')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
it('renders a default built-in icon when icon is a string name', () => {
|
|
31
|
+
const { container } = render(EmptyState, {
|
|
32
|
+
props: { title: 'Empty', icon: 'search' },
|
|
33
|
+
});
|
|
34
|
+
expect(container.querySelector('.icon-container svg')).not.toBeNull();
|
|
35
|
+
});
|
|
36
|
+
it('renders a custom icon snippet', () => {
|
|
37
|
+
render(EmptyState, {
|
|
38
|
+
props: { title: 'Empty', icon: iconSnippet('★') },
|
|
39
|
+
});
|
|
40
|
+
expect(screen.getByTestId('custom-icon')).toHaveTextContent('★');
|
|
41
|
+
});
|
|
42
|
+
it('renders the action as a link when actionHref is set', () => {
|
|
43
|
+
render(EmptyState, {
|
|
44
|
+
props: {
|
|
45
|
+
title: 'Empty',
|
|
46
|
+
actionLabel: 'Create one',
|
|
47
|
+
actionHref: '/new',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const link = screen.getByRole('link', { name: 'Create one' });
|
|
51
|
+
expect(link).toHaveAttribute('href', '/new');
|
|
52
|
+
});
|
|
53
|
+
it('renders the action as a button and fires onaction when clicked', async () => {
|
|
54
|
+
const onaction = vi.fn();
|
|
55
|
+
render(EmptyState, {
|
|
56
|
+
props: { title: 'Empty', actionLabel: 'Reload', onaction },
|
|
57
|
+
});
|
|
58
|
+
await userEvent.click(screen.getByRole('button', { name: 'Reload' }));
|
|
59
|
+
expect(onaction).toHaveBeenCalledTimes(1);
|
|
60
|
+
});
|
|
61
|
+
it('renders no action control when no actionLabel is provided', () => {
|
|
62
|
+
render(EmptyState, { props: { title: 'Empty' } });
|
|
63
|
+
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
64
|
+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
it('applies the size variant class', () => {
|
|
67
|
+
const { container } = render(EmptyState, {
|
|
68
|
+
props: { title: 'Empty', size: 'sm' },
|
|
69
|
+
});
|
|
70
|
+
expect(container.querySelector('.empty-state')).toHaveClass('sm');
|
|
71
|
+
});
|
|
72
|
+
it('is axe-clean with a description and button action', async () => {
|
|
73
|
+
const { container } = render(EmptyState, {
|
|
74
|
+
props: {
|
|
75
|
+
title: 'Nothing here yet',
|
|
76
|
+
description: 'Add your first item to get started.',
|
|
77
|
+
actionLabel: 'Add item',
|
|
78
|
+
onaction: () => { },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
await expectNoA11yViolations(container);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the Footer layout primitive (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* Footer renders a top-level <footer> (contentinfo landmark) with an
|
|
5
|
+
* i18n-resolved copyright line and optional children (footer links).
|
|
6
|
+
* useI18n() falls back to registered English defaults outside a Provider.
|
|
7
|
+
*/
|
|
8
|
+
import { render, screen } from '@testing-library/svelte';
|
|
9
|
+
import { createRawSnippet } from 'svelte';
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
12
|
+
import Footer from '../Footer.svelte';
|
|
13
|
+
function snippet(html) {
|
|
14
|
+
return createRawSnippet(() => ({ render: () => html }));
|
|
15
|
+
}
|
|
16
|
+
describe('Footer', () => {
|
|
17
|
+
it('renders a top-level contentinfo landmark', () => {
|
|
18
|
+
render(Footer, { props: {} });
|
|
19
|
+
expect(screen.getByRole('contentinfo')).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
it('renders the current year and i18n-resolved rights notice', () => {
|
|
22
|
+
render(Footer, { props: {} });
|
|
23
|
+
const year = String(new Date().getFullYear());
|
|
24
|
+
const copyright = screen.getByText((_, el) => el?.classList.contains('copyright') === true &&
|
|
25
|
+
el.textContent?.includes(year) === true &&
|
|
26
|
+
el.textContent?.includes('All rights reserved.') === true);
|
|
27
|
+
expect(copyright).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
it('renders the children (footer links)', () => {
|
|
30
|
+
render(Footer, {
|
|
31
|
+
props: {
|
|
32
|
+
children: snippet('<a href="/privacy">Privacy</a>'),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const link = screen.getByRole('link', { name: 'Privacy' });
|
|
36
|
+
expect(link).toHaveAttribute('href', '/privacy');
|
|
37
|
+
});
|
|
38
|
+
it('omits the footer-links container when no children are provided', () => {
|
|
39
|
+
const { container } = render(Footer, { props: {} });
|
|
40
|
+
expect(container.querySelector('.footer-links')).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
it('is axe-clean with footer links', async () => {
|
|
43
|
+
const { container } = render(Footer, {
|
|
44
|
+
props: {
|
|
45
|
+
children: snippet('<a href="/privacy">Privacy</a>'),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
await expectNoA11yViolations(container);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the Grid layout primitive (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* Asserts the column/gap/alignment prop API maps to the expected
|
|
5
|
+
* classes/inline styles, snippet rendering, attribute pass-through, and
|
|
6
|
+
* axe-clean output.
|
|
7
|
+
*/
|
|
8
|
+
import { render, screen } from '@testing-library/svelte';
|
|
9
|
+
import { createRawSnippet } from 'svelte';
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
12
|
+
import Grid from '../Grid.svelte';
|
|
13
|
+
function textSnippet(text) {
|
|
14
|
+
return createRawSnippet(() => ({ render: () => `<span>${text}</span>` }));
|
|
15
|
+
}
|
|
16
|
+
/** The grid element is the `.grid` div (the header lives in a sibling div). */
|
|
17
|
+
function gridEl(container) {
|
|
18
|
+
const el = container.querySelector('div.grid');
|
|
19
|
+
if (!el)
|
|
20
|
+
throw new Error('grid element not found');
|
|
21
|
+
return el;
|
|
22
|
+
}
|
|
23
|
+
describe('Grid', () => {
|
|
24
|
+
it('renders slotted children', () => {
|
|
25
|
+
render(Grid, { props: { children: textSnippet('Cell') } });
|
|
26
|
+
expect(screen.getByText('Cell')).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
it('uses auto-fill grid-template-columns by default (columns=auto)', () => {
|
|
29
|
+
const { container } = render(Grid, {
|
|
30
|
+
props: { children: textSnippet('x') },
|
|
31
|
+
});
|
|
32
|
+
expect(gridEl(container).getAttribute('style')).toContain('grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))');
|
|
33
|
+
});
|
|
34
|
+
it('maps a numeric column count to a fixed-column template', () => {
|
|
35
|
+
const { container } = render(Grid, {
|
|
36
|
+
props: { columns: 3, children: textSnippet('x') },
|
|
37
|
+
});
|
|
38
|
+
expect(gridEl(container).getAttribute('style')).toContain('grid-template-columns: repeat(3, 1fr)');
|
|
39
|
+
});
|
|
40
|
+
it('emits responsive CSS variables and the grid-responsive class for a responsive columns object', () => {
|
|
41
|
+
const { container } = render(Grid, {
|
|
42
|
+
props: { columns: { md: 2, xl: 4 }, children: textSnippet('x') },
|
|
43
|
+
});
|
|
44
|
+
const el = gridEl(container);
|
|
45
|
+
expect(el).toHaveClass('grid-responsive');
|
|
46
|
+
const style = el.getAttribute('style') ?? '';
|
|
47
|
+
expect(style).toContain('--grid-columns-md: 2');
|
|
48
|
+
expect(style).toContain('--grid-columns-xl: 4');
|
|
49
|
+
// Unspecified breakpoints are not emitted (CSS cascade handles inheritance).
|
|
50
|
+
expect(style).not.toContain('--grid-columns-sm');
|
|
51
|
+
});
|
|
52
|
+
it('applies a uniform gap class when row and column gaps match (default md)', () => {
|
|
53
|
+
const { container } = render(Grid, {
|
|
54
|
+
props: { children: textSnippet('x') },
|
|
55
|
+
});
|
|
56
|
+
expect(gridEl(container)).toHaveClass('gap-md');
|
|
57
|
+
});
|
|
58
|
+
it.each([
|
|
59
|
+
'sm',
|
|
60
|
+
'md',
|
|
61
|
+
'lg',
|
|
62
|
+
'xl',
|
|
63
|
+
])('maps a uniform gap=%s to gap-%s', (gap) => {
|
|
64
|
+
const { container } = render(Grid, {
|
|
65
|
+
props: { gap, children: textSnippet('x') },
|
|
66
|
+
});
|
|
67
|
+
expect(gridEl(container)).toHaveClass(`gap-${gap}`);
|
|
68
|
+
});
|
|
69
|
+
it('splits row/column gap classes when they differ', () => {
|
|
70
|
+
const { container } = render(Grid, {
|
|
71
|
+
props: { gap: { row: 'sm', column: 'lg' }, children: textSnippet('x') },
|
|
72
|
+
});
|
|
73
|
+
const el = gridEl(container);
|
|
74
|
+
expect(el).toHaveClass('row-gap-sm', 'col-gap-lg');
|
|
75
|
+
expect(el).not.toHaveClass('gap-sm');
|
|
76
|
+
});
|
|
77
|
+
it('maps alignItems / justifyItems / autoFlow to inline styles', () => {
|
|
78
|
+
const { container } = render(Grid, {
|
|
79
|
+
props: {
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
justifyItems: 'end',
|
|
82
|
+
autoFlow: 'column',
|
|
83
|
+
children: textSnippet('x'),
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
const style = gridEl(container).getAttribute('style') ?? '';
|
|
87
|
+
expect(style).toContain('align-items: center');
|
|
88
|
+
expect(style).toContain('justify-items: end');
|
|
89
|
+
expect(style).toContain('grid-auto-flow: column');
|
|
90
|
+
});
|
|
91
|
+
it('renders the header snippet above the grid', () => {
|
|
92
|
+
render(Grid, {
|
|
93
|
+
props: {
|
|
94
|
+
header: textSnippet('Grid Heading'),
|
|
95
|
+
children: textSnippet('Cell'),
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
expect(screen.getByText('Grid Heading')).toBeInTheDocument();
|
|
99
|
+
});
|
|
100
|
+
it('passes ...rest attributes through to the grid element', () => {
|
|
101
|
+
const { container } = render(Grid, {
|
|
102
|
+
props: {
|
|
103
|
+
children: textSnippet('x'),
|
|
104
|
+
id: 'gallery',
|
|
105
|
+
'data-testid': 'grid',
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
const el = gridEl(container);
|
|
109
|
+
expect(el).toHaveAttribute('id', 'gallery');
|
|
110
|
+
expect(el).toHaveAttribute('data-testid', 'grid');
|
|
111
|
+
});
|
|
112
|
+
it('is axe-clean with content', async () => {
|
|
113
|
+
const { container } = render(Grid, {
|
|
114
|
+
props: {
|
|
115
|
+
header: textSnippet('Items'),
|
|
116
|
+
children: textSnippet('Accessible cell'),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
await expectNoA11yViolations(container);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the Header (site header) layout primitive (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* Header renders a top-level <header> (banner landmark) wrapping a Container,
|
|
5
|
+
* with optional logo and nav snippets.
|
|
6
|
+
*/
|
|
7
|
+
import { render, screen } from '@testing-library/svelte';
|
|
8
|
+
import { createRawSnippet } from 'svelte';
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
11
|
+
import Header from '../Header.svelte';
|
|
12
|
+
function snippet(html) {
|
|
13
|
+
return createRawSnippet(() => ({ render: () => html }));
|
|
14
|
+
}
|
|
15
|
+
describe('Header', () => {
|
|
16
|
+
it('renders a top-level banner landmark', () => {
|
|
17
|
+
render(Header, { props: {} });
|
|
18
|
+
expect(screen.getByRole('banner')).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
it('renders the logo snippet', () => {
|
|
21
|
+
render(Header, {
|
|
22
|
+
props: { logo: snippet('<h1>Acme</h1>') },
|
|
23
|
+
});
|
|
24
|
+
expect(screen.getByRole('heading', { name: 'Acme' })).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
it('renders the nav snippet inside a navigation landmark', () => {
|
|
27
|
+
render(Header, {
|
|
28
|
+
props: { nav: snippet('<a href="/about">About</a>') },
|
|
29
|
+
});
|
|
30
|
+
const nav = screen.getByRole('navigation');
|
|
31
|
+
expect(nav).toBeInTheDocument();
|
|
32
|
+
expect(screen.getByRole('link', { name: 'About' })).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
it('omits logo and nav when their snippets are not provided', () => {
|
|
35
|
+
const { container } = render(Header, { props: {} });
|
|
36
|
+
expect(container.querySelector('.logo')).toBeNull();
|
|
37
|
+
expect(container.querySelector('.nav')).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
it('is axe-clean with logo and nav', async () => {
|
|
40
|
+
const { container } = render(Header, {
|
|
41
|
+
props: {
|
|
42
|
+
logo: snippet('<h1><a href="/">Acme</a></h1>'),
|
|
43
|
+
nav: snippet('<a href="/about">About</a>'),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
await expectNoA11yViolations(container);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the Masthead layout primitive (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* Masthead renders a date (as a <time>), an optional location link, and
|
|
5
|
+
* nav/mobileNav snippets inside <nav> landmarks. The root is a plain <div>
|
|
6
|
+
* (no banner landmark). The mobile home icon's aria-label is i18n-resolved;
|
|
7
|
+
* useI18n() falls back to registered English defaults outside a Provider.
|
|
8
|
+
*/
|
|
9
|
+
import { render, screen } from '@testing-library/svelte';
|
|
10
|
+
import { createRawSnippet } from 'svelte';
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
13
|
+
import Masthead from '../Masthead.svelte';
|
|
14
|
+
function snippet(html) {
|
|
15
|
+
return createRawSnippet(() => ({ render: () => html }));
|
|
16
|
+
}
|
|
17
|
+
describe('Masthead', () => {
|
|
18
|
+
it('renders a provided date string', () => {
|
|
19
|
+
render(Masthead, { props: { date: 'Monday, June 1, 2026' } });
|
|
20
|
+
expect(screen.getAllByText('Monday, June 1, 2026').length).toBeGreaterThan(0);
|
|
21
|
+
});
|
|
22
|
+
it('renders the date inside a <time> element', () => {
|
|
23
|
+
const { container } = render(Masthead, {
|
|
24
|
+
props: { date: 'June 1, 2026' },
|
|
25
|
+
});
|
|
26
|
+
const time = container.querySelector('time');
|
|
27
|
+
expect(time).not.toBeNull();
|
|
28
|
+
expect(time).toHaveTextContent('June 1, 2026');
|
|
29
|
+
});
|
|
30
|
+
it('wraps the date in a link when dateHref is provided', () => {
|
|
31
|
+
render(Masthead, {
|
|
32
|
+
props: { date: 'June 1, 2026', dateHref: '/archive/2026-06-01' },
|
|
33
|
+
});
|
|
34
|
+
const link = screen.getByRole('link', { name: 'June 1, 2026' });
|
|
35
|
+
expect(link).toHaveAttribute('href', '/archive/2026-06-01');
|
|
36
|
+
});
|
|
37
|
+
it('renders the location as a link to locationHref', () => {
|
|
38
|
+
render(Masthead, {
|
|
39
|
+
props: { location: 'New York', locationHref: '/ny' },
|
|
40
|
+
});
|
|
41
|
+
const link = screen.getByRole('link', { name: 'New York' });
|
|
42
|
+
expect(link).toHaveAttribute('href', '/ny');
|
|
43
|
+
});
|
|
44
|
+
it('omits the location link when location is empty', () => {
|
|
45
|
+
const { container } = render(Masthead, { props: { location: '' } });
|
|
46
|
+
expect(container.querySelector('.location')).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
it('renders the nav snippet inside a navigation landmark', () => {
|
|
49
|
+
render(Masthead, {
|
|
50
|
+
props: { nav: snippet('<a href="/sections">Sections</a>') },
|
|
51
|
+
});
|
|
52
|
+
// Masthead renders both a desktop and a mobile layout in the DOM (CSS hides
|
|
53
|
+
// one); with no `mobileNav`, the mobile layout falls back to `nav`, so the
|
|
54
|
+
// link appears in both. Assert at least one navigation landmark + link.
|
|
55
|
+
expect(screen.getAllByRole('navigation').length).toBeGreaterThan(0);
|
|
56
|
+
expect(screen.getAllByRole('link', { name: 'Sections' }).length).toBeGreaterThan(0);
|
|
57
|
+
});
|
|
58
|
+
it('renders a distinct mobileNav snippet when provided', () => {
|
|
59
|
+
render(Masthead, {
|
|
60
|
+
props: {
|
|
61
|
+
nav: snippet('<a href="/sections">Sections</a>'),
|
|
62
|
+
mobileNav: snippet('<a href="/menu">Menu</a>'),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
expect(screen.getByRole('link', { name: 'Menu' })).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
it('exposes a home icon link with an accessible (i18n-resolved) label', () => {
|
|
68
|
+
render(Masthead, { props: { locationHref: '/home' } });
|
|
69
|
+
const link = screen.getByRole('link', { name: 'Home' });
|
|
70
|
+
expect(link).toHaveAttribute('href', '/home');
|
|
71
|
+
});
|
|
72
|
+
it('labels the desktop and mobile nav landmarks distinctly', () => {
|
|
73
|
+
// Masthead emits both a desktop and a mobile layout (one CSS-hidden). Each
|
|
74
|
+
// <nav> carries a distinct aria-label so the two landmarks stay unique.
|
|
75
|
+
render(Masthead, {
|
|
76
|
+
props: { nav: snippet('<a href="/sections">Sections</a>') },
|
|
77
|
+
});
|
|
78
|
+
expect(screen.getByRole('navigation', { name: 'Primary' })).toBeInTheDocument();
|
|
79
|
+
expect(screen.getByRole('navigation', { name: 'Mobile' })).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
it('is axe-clean with date, location, and nav', async () => {
|
|
82
|
+
const { container } = render(Masthead, {
|
|
83
|
+
props: {
|
|
84
|
+
date: 'June 1, 2026',
|
|
85
|
+
dateHref: '/archive',
|
|
86
|
+
location: 'New York',
|
|
87
|
+
locationHref: '/ny',
|
|
88
|
+
nav: snippet('<a href="/sections">Sections</a>'),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
await expectNoA11yViolations(container);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the PageHeader layout primitive (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* PageHeader renders a top-level <header> (banner landmark) with an <h1>
|
|
5
|
+
* title, optional subtitle, optional back link, and actions/children snippets.
|
|
6
|
+
*/
|
|
7
|
+
import { render, screen } from '@testing-library/svelte';
|
|
8
|
+
import { createRawSnippet } from 'svelte';
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
11
|
+
import PageHeader from '../PageHeader.svelte';
|
|
12
|
+
function snippet(html) {
|
|
13
|
+
return createRawSnippet(() => ({ render: () => html }));
|
|
14
|
+
}
|
|
15
|
+
describe('PageHeader', () => {
|
|
16
|
+
it('renders the title as a level-1 heading', () => {
|
|
17
|
+
render(PageHeader, { props: { title: 'Dashboard' } });
|
|
18
|
+
const heading = screen.getByRole('heading', {
|
|
19
|
+
name: 'Dashboard',
|
|
20
|
+
level: 1,
|
|
21
|
+
});
|
|
22
|
+
expect(heading).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
it('renders a top-level banner landmark', () => {
|
|
25
|
+
render(PageHeader, { props: { title: 'Dashboard' } });
|
|
26
|
+
expect(screen.getByRole('banner')).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
it('renders the optional subtitle', () => {
|
|
29
|
+
render(PageHeader, {
|
|
30
|
+
props: { title: 'Dashboard', subtitle: 'Overview of your account' },
|
|
31
|
+
});
|
|
32
|
+
expect(screen.getByText('Overview of your account')).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
it('renders a back link with the default label when backHref is set', () => {
|
|
35
|
+
render(PageHeader, {
|
|
36
|
+
props: { title: 'Detail', backHref: '/list' },
|
|
37
|
+
});
|
|
38
|
+
const link = screen.getByRole('link', { name: 'Back' });
|
|
39
|
+
expect(link).toHaveAttribute('href', '/list');
|
|
40
|
+
});
|
|
41
|
+
it('uses a custom back label', () => {
|
|
42
|
+
render(PageHeader, {
|
|
43
|
+
props: { title: 'Detail', backHref: '/list', backLabel: 'All items' },
|
|
44
|
+
});
|
|
45
|
+
expect(screen.getByRole('link', { name: 'All items' })).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
it('renders the actions snippet', () => {
|
|
48
|
+
render(PageHeader, {
|
|
49
|
+
props: {
|
|
50
|
+
title: 'Detail',
|
|
51
|
+
actions: snippet('<button type="button">Edit</button>'),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
expect(screen.getByRole('button', { name: 'Edit' })).toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
it('renders extra children below the title', () => {
|
|
57
|
+
render(PageHeader, {
|
|
58
|
+
props: {
|
|
59
|
+
title: 'Detail',
|
|
60
|
+
children: snippet('<p>Extra content</p>'),
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
expect(screen.getByText('Extra content')).toBeInTheDocument();
|
|
64
|
+
});
|
|
65
|
+
it('omits the back link when backHref is not provided', () => {
|
|
66
|
+
render(PageHeader, { props: { title: 'Detail' } });
|
|
67
|
+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
it('is axe-clean with subtitle, back link, and actions', async () => {
|
|
70
|
+
const { container } = render(PageHeader, {
|
|
71
|
+
props: {
|
|
72
|
+
title: 'Settings',
|
|
73
|
+
subtitle: 'Manage your preferences',
|
|
74
|
+
backHref: '/home',
|
|
75
|
+
actions: snippet('<button type="button">Save</button>'),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
await expectNoA11yViolations(container);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the SummaryCard layout primitive (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* Asserts the label/value render, the optional count badge and icon, the
|
|
5
|
+
* variant→value-color-class mapping, the clickable link variant, and
|
|
6
|
+
* axe-clean output across a couple of states.
|
|
7
|
+
*/
|
|
8
|
+
import { render, screen } from '@testing-library/svelte';
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
11
|
+
import SummaryCard from '../SummaryCard.svelte';
|
|
12
|
+
describe('SummaryCard', () => {
|
|
13
|
+
it('renders the label and value', () => {
|
|
14
|
+
render(SummaryCard, { props: { label: 'Revenue', value: '$1,200' } });
|
|
15
|
+
expect(screen.getByText('Revenue')).toBeInTheDocument();
|
|
16
|
+
expect(screen.getByText('$1,200')).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
it('renders a numeric value', () => {
|
|
19
|
+
render(SummaryCard, { props: { label: 'Orders', value: 42 } });
|
|
20
|
+
expect(screen.getByText('42')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
it('renders the optional count badge', () => {
|
|
23
|
+
const { container } = render(SummaryCard, {
|
|
24
|
+
props: { label: 'Tasks', value: 'Open', count: 7 },
|
|
25
|
+
});
|
|
26
|
+
expect(container.querySelector('.card-count')).toHaveTextContent('7');
|
|
27
|
+
});
|
|
28
|
+
it('renders a count badge of 0 (undefined check, not falsy)', () => {
|
|
29
|
+
const { container } = render(SummaryCard, {
|
|
30
|
+
props: { label: 'Tasks', value: 'Done', count: 0 },
|
|
31
|
+
});
|
|
32
|
+
expect(container.querySelector('.card-count')).toHaveTextContent('0');
|
|
33
|
+
});
|
|
34
|
+
it('omits the count badge when count is undefined', () => {
|
|
35
|
+
const { container } = render(SummaryCard, {
|
|
36
|
+
props: { label: 'Tasks', value: 'Open' },
|
|
37
|
+
});
|
|
38
|
+
expect(container.querySelector('.card-count')).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
it('renders a raw SVG icon when icon starts with <svg', () => {
|
|
41
|
+
const { container } = render(SummaryCard, {
|
|
42
|
+
props: {
|
|
43
|
+
label: 'Visitors',
|
|
44
|
+
value: 99,
|
|
45
|
+
icon: '<svg data-testid="svg-icon" viewBox="0 0 24 24"></svg>',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
expect(container.querySelector('.card-icon svg')).not.toBeNull();
|
|
49
|
+
});
|
|
50
|
+
it('renders as a clickable link when href is provided', () => {
|
|
51
|
+
render(SummaryCard, {
|
|
52
|
+
props: { label: 'Reports', value: 12, href: '/reports' },
|
|
53
|
+
});
|
|
54
|
+
const link = screen.getByRole('link');
|
|
55
|
+
expect(link).toHaveAttribute('href', '/reports');
|
|
56
|
+
expect(link).toHaveClass('clickable');
|
|
57
|
+
});
|
|
58
|
+
it('applies the highlight class when highlight is set', () => {
|
|
59
|
+
const { container } = render(SummaryCard, {
|
|
60
|
+
props: { label: 'Featured', value: 1, highlight: true },
|
|
61
|
+
});
|
|
62
|
+
expect(container.querySelector('.summary-card')).toHaveClass('highlight');
|
|
63
|
+
});
|
|
64
|
+
it.each([
|
|
65
|
+
['default', 'color-default'],
|
|
66
|
+
['success', 'color-success'],
|
|
67
|
+
['warning', 'color-warning'],
|
|
68
|
+
['danger', 'color-error'],
|
|
69
|
+
])('maps variant=%s to the %s value class', (variant, expected) => {
|
|
70
|
+
const { container } = render(SummaryCard, {
|
|
71
|
+
props: { label: 'Metric', value: 5, variant },
|
|
72
|
+
});
|
|
73
|
+
expect(container.querySelector('.card-value')).toHaveClass(expected);
|
|
74
|
+
});
|
|
75
|
+
it.each([
|
|
76
|
+
{ label: 'Static', value: '$10' },
|
|
77
|
+
{ label: 'Linked', value: 3, href: '/go', count: 2 },
|
|
78
|
+
])('is axe-clean (%o)', async (props) => {
|
|
79
|
+
const { container } = render(SummaryCard, { props });
|
|
80
|
+
await expectNoA11yViolations(container);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout components - Page structure and containers
|
|
3
|
+
*/
|
|
4
|
+
export { default as Container } from './Container.svelte';
|
|
5
|
+
export { default as EmptyState } from './EmptyState.svelte';
|
|
6
|
+
export { default as Footer } from './Footer.svelte';
|
|
7
|
+
export { default as Grid } from './Grid.svelte';
|
|
8
|
+
export { default as Header } from './Header.svelte';
|
|
9
|
+
export { default as Masthead } from './Masthead.svelte';
|
|
10
|
+
export { default as PageHeader } from './PageHeader.svelte';
|
|
11
|
+
export { default as SummaryCard } from './SummaryCard.svelte';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/layout/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
|