@sabrenski/spire-ui 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/dist/index.d.ts +4981 -0
- package/dist/spire-ui.css +1 -0
- package/dist/spire-ui.es.js +18403 -0
- package/dist/spire-ui.umd.js +45 -0
- package/package.json +83 -0
- package/src/components/Accordion/Accordion.test.ts +218 -0
- package/src/components/Accordion/AccordionContent.vue +112 -0
- package/src/components/Accordion/AccordionItem.vue +87 -0
- package/src/components/Accordion/AccordionRoot.vue +111 -0
- package/src/components/Accordion/AccordionTrigger.vue +125 -0
- package/src/components/Accordion/index.ts +11 -0
- package/src/components/Accordion/keys.ts +23 -0
- package/src/components/Avatar/Avatar.test.ts +181 -0
- package/src/components/Avatar/Avatar.vue +150 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Badge/Badge.test.ts +141 -0
- package/src/components/Badge/Badge.vue +133 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/BadgeContainer/BadgeContainer.test.ts +150 -0
- package/src/components/BadgeContainer/BadgeContainer.vue +90 -0
- package/src/components/BadgeContainer/index.ts +2 -0
- package/src/components/Breadcrumb/Breadcrumb.test.ts +342 -0
- package/src/components/Breadcrumb/BreadcrumbEllipsis.vue +96 -0
- package/src/components/Breadcrumb/BreadcrumbItem.vue +16 -0
- package/src/components/Breadcrumb/BreadcrumbLink.vue +67 -0
- package/src/components/Breadcrumb/BreadcrumbList.vue +20 -0
- package/src/components/Breadcrumb/BreadcrumbPage.vue +25 -0
- package/src/components/Breadcrumb/BreadcrumbRoot.vue +41 -0
- package/src/components/Breadcrumb/BreadcrumbSeparator.vue +63 -0
- package/src/components/Breadcrumb/index.ts +13 -0
- package/src/components/Breadcrumb/keys.ts +7 -0
- package/src/components/Button/Button.test.ts +231 -0
- package/src/components/Button/Button.vue +349 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Callout/Callout.test.ts +260 -0
- package/src/components/Callout/Callout.vue +341 -0
- package/src/components/Callout/index.ts +2 -0
- package/src/components/Card/Card.test.ts +565 -0
- package/src/components/Card/Card.vue +209 -0
- package/src/components/Card/CardContent.vue +57 -0
- package/src/components/Card/CardFooter.vue +72 -0
- package/src/components/Card/CardHeader.vue +111 -0
- package/src/components/Card/CardImage.vue +124 -0
- package/src/components/Card/index.ts +14 -0
- package/src/components/Chart/BarChart.vue +208 -0
- package/src/components/Chart/BaseChart.vue +444 -0
- package/src/components/Chart/Chart.test.ts +359 -0
- package/src/components/Chart/DonutChart.vue +283 -0
- package/src/components/Chart/LineChart.vue +211 -0
- package/src/components/Chart/index.ts +20 -0
- package/src/components/Chart/useChartTheme.ts +192 -0
- package/src/components/Checkbox/Checkbox.test.ts +209 -0
- package/src/components/Checkbox/Checkbox.vue +285 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/ChoiceChip/ChoiceChip.test.ts +142 -0
- package/src/components/ChoiceChip/ChoiceChip.vue +218 -0
- package/src/components/ChoiceChip/index.ts +2 -0
- package/src/components/ChoiceChipGroup/ChoiceChipGroup.test.ts +151 -0
- package/src/components/ChoiceChipGroup/ChoiceChipGroup.vue +70 -0
- package/src/components/ChoiceChipGroup/index.ts +2 -0
- package/src/components/ColorPicker/ColorArea.vue +159 -0
- package/src/components/ColorPicker/ColorPicker.test.ts +250 -0
- package/src/components/ColorPicker/ColorPicker.vue +339 -0
- package/src/components/ColorPicker/ColorSlider.vue +191 -0
- package/src/components/ColorPicker/index.ts +7 -0
- package/src/components/Combobox/Combobox.test.ts +891 -0
- package/src/components/Combobox/Combobox.vue +934 -0
- package/src/components/Combobox/index.ts +2 -0
- package/src/components/DataTable/DataTable.test.ts +1221 -0
- package/src/components/DataTable/DataTable.vue +1415 -0
- package/src/components/DataTable/index.ts +10 -0
- package/src/components/DatePicker/DatePicker.test.ts +625 -0
- package/src/components/DatePicker/DatePicker.vue +1586 -0
- package/src/components/DatePicker/index.ts +2 -0
- package/src/components/Drawer/Drawer.test.ts +336 -0
- package/src/components/Drawer/Drawer.vue +466 -0
- package/src/components/Drawer/index.ts +2 -0
- package/src/components/Dropdown/Dropdown.test.ts +607 -0
- package/src/components/Dropdown/Dropdown.vue +807 -0
- package/src/components/Dropdown/DropdownItem.vue +227 -0
- package/src/components/Dropdown/DropdownSeparator.vue +14 -0
- package/src/components/Dropdown/DropdownSub.vue +104 -0
- package/src/components/Dropdown/DropdownSubContent.vue +187 -0
- package/src/components/Dropdown/DropdownSubTrigger.vue +151 -0
- package/src/components/Dropdown/index.ts +14 -0
- package/src/components/EmptyState/EmptyState.test.ts +180 -0
- package/src/components/EmptyState/EmptyState.vue +137 -0
- package/src/components/EmptyState/index.ts +2 -0
- package/src/components/FileUpload/FileUpload.test.ts +1151 -0
- package/src/components/FileUpload/FileUpload.vue +1042 -0
- package/src/components/FileUpload/index.ts +2 -0
- package/src/components/Heading/Heading.test.ts +107 -0
- package/src/components/Heading/Heading.vue +67 -0
- package/src/components/Heading/index.ts +2 -0
- package/src/components/Icon/Icon.test.ts +157 -0
- package/src/components/Icon/Icon.vue +86 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Input/Input.test.ts +273 -0
- package/src/components/Input/Input.vue +388 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Layout/Container.vue +67 -0
- package/src/components/Layout/Grid.vue +159 -0
- package/src/components/Layout/GridItem.vue +154 -0
- package/src/components/Layout/Layout.test.ts +202 -0
- package/src/components/Layout/Stack.vue +128 -0
- package/src/components/Layout/index.ts +9 -0
- package/src/components/Layout/keys.ts +7 -0
- package/src/components/Modal/Modal.test.ts +311 -0
- package/src/components/Modal/Modal.vue +336 -0
- package/src/components/Modal/index.ts +2 -0
- package/src/components/Pagination/Pagination.test.ts +303 -0
- package/src/components/Pagination/Pagination.vue +212 -0
- package/src/components/Pagination/index.ts +3 -0
- package/src/components/Pagination/utils.ts +86 -0
- package/src/components/Popover/Popover.test.ts +285 -0
- package/src/components/Popover/Popover.vue +441 -0
- package/src/components/Popover/index.ts +2 -0
- package/src/components/Progress/Progress.test.ts +361 -0
- package/src/components/Progress/Progress.vue +363 -0
- package/src/components/Progress/index.ts +7 -0
- package/src/components/Radio/Radio.test.ts +216 -0
- package/src/components/Radio/Radio.vue +214 -0
- package/src/components/Radio/index.ts +2 -0
- package/src/components/Rating/Rating.test.ts +319 -0
- package/src/components/Rating/Rating.vue +247 -0
- package/src/components/Rating/index.ts +2 -0
- package/src/components/SegmentedControl/SegmentedControl.test.ts +292 -0
- package/src/components/SegmentedControl/SegmentedControl.vue +288 -0
- package/src/components/SegmentedControl/index.ts +2 -0
- package/src/components/Select/Select.test.ts +589 -0
- package/src/components/Select/Select.vue +666 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/Sidebar/Sidebar.test.ts +301 -0
- package/src/components/Sidebar/SidebarGroup.vue +103 -0
- package/src/components/Sidebar/SidebarItem.vue +196 -0
- package/src/components/Sidebar/SidebarLayout.vue +42 -0
- package/src/components/Sidebar/SidebarRoot.vue +122 -0
- package/src/components/Sidebar/index.ts +11 -0
- package/src/components/Sidebar/keys.ts +14 -0
- package/src/components/Skeleton/Skeleton.test.ts +130 -0
- package/src/components/Skeleton/Skeleton.vue +104 -0
- package/src/components/Skeleton/index.ts +2 -0
- package/src/components/Slider/Slider.test.ts +416 -0
- package/src/components/Slider/Slider.vue +435 -0
- package/src/components/Slider/index.ts +2 -0
- package/src/components/Slider/utils.ts +91 -0
- package/src/components/Spinner/Spinner.test.ts +79 -0
- package/src/components/Spinner/Spinner.vue +159 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/SpireProvider/SpireProvider.vue +71 -0
- package/src/components/SpireProvider/index.ts +11 -0
- package/src/components/Stepper/Stepper.test.ts +221 -0
- package/src/components/Stepper/StepperContent.vue +51 -0
- package/src/components/Stepper/StepperItem.vue +89 -0
- package/src/components/Stepper/StepperRoot.vue +101 -0
- package/src/components/Stepper/StepperSeparator.vue +52 -0
- package/src/components/Stepper/StepperTrigger.vue +144 -0
- package/src/components/Stepper/index.ts +11 -0
- package/src/components/Stepper/keys.ts +27 -0
- package/src/components/Switch/Switch.test.ts +214 -0
- package/src/components/Switch/Switch.vue +235 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Tabs/Tabs.test.ts +363 -0
- package/src/components/Tabs/Tabs.vue +318 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Text/Text.test.ts +154 -0
- package/src/components/Text/Text.vue +100 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Textarea/Textarea.test.ts +432 -0
- package/src/components/Textarea/Textarea.vue +411 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/TimePicker/TimePicker.test.ts +352 -0
- package/src/components/TimePicker/TimePicker.vue +569 -0
- package/src/components/TimePicker/index.ts +2 -0
- package/src/components/Timeline/Timeline.test.ts +193 -0
- package/src/components/Timeline/Timeline.vue +111 -0
- package/src/components/Timeline/TimelineItem.vue +167 -0
- package/src/components/Timeline/index.ts +13 -0
- package/src/components/Timeline/keys.ts +21 -0
- package/src/components/Toast/ToastItem.test.ts +289 -0
- package/src/components/Toast/ToastItem.vue +370 -0
- package/src/components/Toast/ToastProvider.test.ts +158 -0
- package/src/components/Toast/ToastProvider.vue +181 -0
- package/src/components/Toast/index.ts +83 -0
- package/src/components/Toast/toastState.test.ts +165 -0
- package/src/components/Toast/toastState.ts +161 -0
- package/src/components/ToggleButton/ToggleButton.test.ts +166 -0
- package/src/components/ToggleButton/ToggleButton.vue +197 -0
- package/src/components/ToggleButton/index.ts +2 -0
- package/src/components/ToggleGroup/ToggleGroup.test.ts +181 -0
- package/src/components/ToggleGroup/ToggleGroup.vue +130 -0
- package/src/components/ToggleGroup/index.ts +2 -0
- package/src/components/Tooltip/Tooltip.test.ts +238 -0
- package/src/components/Tooltip/Tooltip.vue +217 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/TreeView/TreeView.test.ts +357 -0
- package/src/components/TreeView/TreeView.vue +251 -0
- package/src/components/TreeView/TreeViewItem.vue +288 -0
- package/src/components/TreeView/index.ts +11 -0
- package/src/components/TreeView/keys.ts +35 -0
- package/src/composables/index.ts +12 -0
- package/src/composables/useClickOutside.ts +36 -0
- package/src/composables/useClipboard.ts +35 -0
- package/src/composables/useEventListener.ts +48 -0
- package/src/composables/useFocusTrap.ts +58 -0
- package/src/composables/useHoverReveal.ts +98 -0
- package/src/composables/useId.ts +10 -0
- package/src/composables/useMagnetic.ts +171 -0
- package/src/composables/useRelativePosition.ts +127 -0
- package/src/composables/useRipple.ts +146 -0
- package/src/composables/useScrollLock.ts +25 -0
- package/src/composables/useSpireConfig.ts +27 -0
- package/src/composables/useStagger.ts +224 -0
- package/src/config/icons.test.ts +115 -0
- package/src/config/icons.ts +170 -0
- package/src/index.ts +361 -0
- package/src/styles/depth.css +129 -0
- package/src/styles/effects.css +169 -0
- package/src/styles/fallback.css +152 -0
- package/src/styles/main.css +25 -0
- package/src/styles/mood.css +211 -0
- package/src/styles/motion.css +159 -0
- package/src/styles/reset.css +97 -0
- package/src/styles/theme.css +708 -0
- package/src/styles/tokens.css +183 -0
- package/src/utils/.gitkeep +0 -0
- package/src/utils/color.ts +277 -0
- package/src/utils/date.test.ts +522 -0
- package/src/utils/date.ts +380 -0
- package/src/utils/index.ts +23 -0
- package/src/utils/object.test.ts +80 -0
- package/src/utils/object.ts +25 -0
- package/src/utils/string.test.ts +64 -0
- package/src/utils/string.ts +32 -0
- package/src/utils/time.ts +156 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import { h } from 'vue'
|
|
4
|
+
import Card from './Card.vue'
|
|
5
|
+
import CardHeader from './CardHeader.vue'
|
|
6
|
+
import CardContent from './CardContent.vue'
|
|
7
|
+
import CardFooter from './CardFooter.vue'
|
|
8
|
+
import CardImage from './CardImage.vue'
|
|
9
|
+
|
|
10
|
+
describe('Card', () => {
|
|
11
|
+
describe('Rendering', () => {
|
|
12
|
+
it('renders with default props', () => {
|
|
13
|
+
const wrapper = mount(Card)
|
|
14
|
+
expect(wrapper.find('.ui-card').exists()).toBe(true)
|
|
15
|
+
expect(wrapper.find('.ui-card--elevated').exists()).toBe(true)
|
|
16
|
+
expect(wrapper.find('.ui-card--padding-md').exists()).toBe(true)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('renders slot content', () => {
|
|
20
|
+
const wrapper = mount(Card, {
|
|
21
|
+
slots: {
|
|
22
|
+
default: '<p>Card content</p>'
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
expect(wrapper.text()).toContain('Card content')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('renders as different element via as prop', () => {
|
|
29
|
+
const wrapper = mount(Card, {
|
|
30
|
+
props: { as: 'article' }
|
|
31
|
+
})
|
|
32
|
+
expect(wrapper.element.tagName).toBe('ARTICLE')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('renders as section element', () => {
|
|
36
|
+
const wrapper = mount(Card, {
|
|
37
|
+
props: { as: 'section' }
|
|
38
|
+
})
|
|
39
|
+
expect(wrapper.element.tagName).toBe('SECTION')
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('Variants', () => {
|
|
44
|
+
it('applies elevated variant by default', () => {
|
|
45
|
+
const wrapper = mount(Card)
|
|
46
|
+
expect(wrapper.find('.ui-card--elevated').exists()).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('applies outline variant', () => {
|
|
50
|
+
const wrapper = mount(Card, {
|
|
51
|
+
props: { variant: 'outline' }
|
|
52
|
+
})
|
|
53
|
+
expect(wrapper.find('.ui-card--outline').exists()).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('applies ghost variant', () => {
|
|
57
|
+
const wrapper = mount(Card, {
|
|
58
|
+
props: { variant: 'ghost' }
|
|
59
|
+
})
|
|
60
|
+
expect(wrapper.find('.ui-card--ghost').exists()).toBe(true)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('Padding', () => {
|
|
65
|
+
it('applies medium padding by default', () => {
|
|
66
|
+
const wrapper = mount(Card)
|
|
67
|
+
expect(wrapper.find('.ui-card--padding-md').exists()).toBe(true)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('applies small padding', () => {
|
|
71
|
+
const wrapper = mount(Card, {
|
|
72
|
+
props: { padding: 'sm' }
|
|
73
|
+
})
|
|
74
|
+
expect(wrapper.find('.ui-card--padding-sm').exists()).toBe(true)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('applies large padding', () => {
|
|
78
|
+
const wrapper = mount(Card, {
|
|
79
|
+
props: { padding: 'lg' }
|
|
80
|
+
})
|
|
81
|
+
expect(wrapper.find('.ui-card--padding-lg').exists()).toBe(true)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('applies no padding', () => {
|
|
85
|
+
const wrapper = mount(Card, {
|
|
86
|
+
props: { padding: 'none' }
|
|
87
|
+
})
|
|
88
|
+
expect(wrapper.find('.ui-card--padding-none').exists()).toBe(true)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('Interactive', () => {
|
|
93
|
+
it('applies interactive class when interactive', () => {
|
|
94
|
+
const wrapper = mount(Card, {
|
|
95
|
+
props: { interactive: true }
|
|
96
|
+
})
|
|
97
|
+
expect(wrapper.find('.ui-card--interactive').exists()).toBe(true)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('emits click event when interactive and clicked', async () => {
|
|
101
|
+
const wrapper = mount(Card, {
|
|
102
|
+
props: { interactive: true }
|
|
103
|
+
})
|
|
104
|
+
await wrapper.trigger('click')
|
|
105
|
+
expect(wrapper.emitted('click')).toBeTruthy()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('does not emit click when not interactive', async () => {
|
|
109
|
+
const wrapper = mount(Card)
|
|
110
|
+
await wrapper.trigger('click')
|
|
111
|
+
expect(wrapper.emitted('click')).toBeFalsy()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('does not emit click when disabled', async () => {
|
|
115
|
+
const wrapper = mount(Card, {
|
|
116
|
+
props: { interactive: true, disabled: true }
|
|
117
|
+
})
|
|
118
|
+
await wrapper.trigger('click')
|
|
119
|
+
expect(wrapper.emitted('click')).toBeFalsy()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('does not emit click when loading', async () => {
|
|
123
|
+
const wrapper = mount(Card, {
|
|
124
|
+
props: { interactive: true, loading: true }
|
|
125
|
+
})
|
|
126
|
+
await wrapper.trigger('click')
|
|
127
|
+
expect(wrapper.emitted('click')).toBeFalsy()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('sets tabindex when interactive', () => {
|
|
131
|
+
const wrapper = mount(Card, {
|
|
132
|
+
props: { interactive: true }
|
|
133
|
+
})
|
|
134
|
+
expect(wrapper.attributes('tabindex')).toBe('0')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('sets role button when interactive', () => {
|
|
138
|
+
const wrapper = mount(Card, {
|
|
139
|
+
props: { interactive: true }
|
|
140
|
+
})
|
|
141
|
+
expect(wrapper.attributes('role')).toBe('button')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('handles Enter key when interactive', async () => {
|
|
145
|
+
const wrapper = mount(Card, {
|
|
146
|
+
props: { interactive: true }
|
|
147
|
+
})
|
|
148
|
+
await wrapper.trigger('keydown', { key: 'Enter' })
|
|
149
|
+
expect(wrapper.emitted('click')).toBeTruthy()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('handles Space key when interactive', async () => {
|
|
153
|
+
const wrapper = mount(Card, {
|
|
154
|
+
props: { interactive: true }
|
|
155
|
+
})
|
|
156
|
+
await wrapper.trigger('keydown', { key: ' ' })
|
|
157
|
+
expect(wrapper.emitted('click')).toBeTruthy()
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe('Horizontal Layout', () => {
|
|
162
|
+
it('applies horizontal class', () => {
|
|
163
|
+
const wrapper = mount(Card, {
|
|
164
|
+
props: { horizontal: true }
|
|
165
|
+
})
|
|
166
|
+
expect(wrapper.find('.ui-card--horizontal').exists()).toBe(true)
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('States', () => {
|
|
171
|
+
it('applies disabled class', () => {
|
|
172
|
+
const wrapper = mount(Card, {
|
|
173
|
+
props: { disabled: true }
|
|
174
|
+
})
|
|
175
|
+
expect(wrapper.find('.ui-card--disabled').exists()).toBe(true)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('sets aria-disabled when disabled', () => {
|
|
179
|
+
const wrapper = mount(Card, {
|
|
180
|
+
props: { disabled: true }
|
|
181
|
+
})
|
|
182
|
+
expect(wrapper.attributes('aria-disabled')).toBe('true')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('applies loading class', () => {
|
|
186
|
+
const wrapper = mount(Card, {
|
|
187
|
+
props: { loading: true }
|
|
188
|
+
})
|
|
189
|
+
expect(wrapper.find('.ui-card--loading').exists()).toBe(true)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('shows skeleton overlay when loading', () => {
|
|
193
|
+
const wrapper = mount(Card, {
|
|
194
|
+
props: { loading: true }
|
|
195
|
+
})
|
|
196
|
+
expect(wrapper.find('.ui-card__skeleton').exists()).toBe(true)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('sets aria-busy when loading', () => {
|
|
200
|
+
const wrapper = mount(Card, {
|
|
201
|
+
props: { loading: true }
|
|
202
|
+
})
|
|
203
|
+
expect(wrapper.attributes('aria-busy')).toBe('true')
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('CardHeader', () => {
|
|
209
|
+
describe('Rendering', () => {
|
|
210
|
+
it('renders header element', () => {
|
|
211
|
+
const wrapper = mount(CardHeader)
|
|
212
|
+
expect(wrapper.find('.ui-card__header').exists()).toBe(true)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('renders title from prop', () => {
|
|
216
|
+
const wrapper = mount(CardHeader, {
|
|
217
|
+
props: { title: 'Test Title' }
|
|
218
|
+
})
|
|
219
|
+
expect(wrapper.find('.ui-card__title').text()).toBe('Test Title')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('renders subtitle from prop', () => {
|
|
223
|
+
const wrapper = mount(CardHeader, {
|
|
224
|
+
props: { subtitle: 'Test Subtitle' }
|
|
225
|
+
})
|
|
226
|
+
expect(wrapper.find('.ui-card__subtitle').text()).toBe('Test Subtitle')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('renders title slot', () => {
|
|
230
|
+
const wrapper = mount(CardHeader, {
|
|
231
|
+
slots: {
|
|
232
|
+
title: '<span class="custom-title">Custom Title</span>'
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
expect(wrapper.find('.custom-title').exists()).toBe(true)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('renders subtitle slot', () => {
|
|
239
|
+
const wrapper = mount(CardHeader, {
|
|
240
|
+
slots: {
|
|
241
|
+
subtitle: '<span class="custom-subtitle">Custom Subtitle</span>'
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
expect(wrapper.find('.custom-subtitle').exists()).toBe(true)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('renders actions slot', () => {
|
|
248
|
+
const wrapper = mount(CardHeader, {
|
|
249
|
+
slots: {
|
|
250
|
+
actions: '<button class="action-btn">Action</button>'
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
expect(wrapper.find('.action-btn').exists()).toBe(true)
|
|
254
|
+
expect(wrapper.find('.ui-card__header-actions').exists()).toBe(true)
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
describe('Alignment', () => {
|
|
259
|
+
it('applies start alignment by default', () => {
|
|
260
|
+
const wrapper = mount(CardHeader)
|
|
261
|
+
expect(wrapper.find('.ui-card__header--start').exists()).toBe(true)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('applies center alignment', () => {
|
|
265
|
+
const wrapper = mount(CardHeader, {
|
|
266
|
+
props: { align: 'center' }
|
|
267
|
+
})
|
|
268
|
+
expect(wrapper.find('.ui-card__header--center').exists()).toBe(true)
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
describe('CardContent', () => {
|
|
274
|
+
describe('Rendering', () => {
|
|
275
|
+
it('renders content element', () => {
|
|
276
|
+
const wrapper = mount(CardContent)
|
|
277
|
+
expect(wrapper.find('.ui-card__content').exists()).toBe(true)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('renders slot content', () => {
|
|
281
|
+
const wrapper = mount(CardContent, {
|
|
282
|
+
slots: {
|
|
283
|
+
default: '<p>Content here</p>'
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
expect(wrapper.text()).toContain('Content here')
|
|
287
|
+
})
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
describe('Flush mode', () => {
|
|
291
|
+
it('applies flush class when flush prop is true', () => {
|
|
292
|
+
const wrapper = mount(CardContent, {
|
|
293
|
+
props: { flush: true }
|
|
294
|
+
})
|
|
295
|
+
expect(wrapper.find('.ui-card__content--flush').exists()).toBe(true)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('does not apply flush class by default', () => {
|
|
299
|
+
const wrapper = mount(CardContent)
|
|
300
|
+
expect(wrapper.find('.ui-card__content--flush').exists()).toBe(false)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
describe('Context-aware spacing', () => {
|
|
305
|
+
it('removes top padding when header exists in card context', async () => {
|
|
306
|
+
const wrapper = mount({
|
|
307
|
+
components: { Card, CardHeader, CardContent },
|
|
308
|
+
template: `
|
|
309
|
+
<Card>
|
|
310
|
+
<CardHeader title="Title" />
|
|
311
|
+
<CardContent>Content</CardContent>
|
|
312
|
+
</Card>
|
|
313
|
+
`
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
await wrapper.vm.$nextTick()
|
|
317
|
+
expect(wrapper.find('.ui-card__content--no-top-padding').exists()).toBe(true)
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
describe('CardFooter', () => {
|
|
323
|
+
describe('Rendering', () => {
|
|
324
|
+
it('renders footer element', () => {
|
|
325
|
+
const wrapper = mount(CardFooter)
|
|
326
|
+
expect(wrapper.find('.ui-card__footer').exists()).toBe(true)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('renders slot content', () => {
|
|
330
|
+
const wrapper = mount(CardFooter, {
|
|
331
|
+
slots: {
|
|
332
|
+
default: '<button>Submit</button>'
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
expect(wrapper.text()).toContain('Submit')
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
describe('Alignment', () => {
|
|
340
|
+
it('applies end alignment by default', () => {
|
|
341
|
+
const wrapper = mount(CardFooter)
|
|
342
|
+
expect(wrapper.find('.ui-card__footer--end').exists()).toBe(true)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('applies start alignment', () => {
|
|
346
|
+
const wrapper = mount(CardFooter, {
|
|
347
|
+
props: { align: 'start' }
|
|
348
|
+
})
|
|
349
|
+
expect(wrapper.find('.ui-card__footer--start').exists()).toBe(true)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it('applies center alignment', () => {
|
|
353
|
+
const wrapper = mount(CardFooter, {
|
|
354
|
+
props: { align: 'center' }
|
|
355
|
+
})
|
|
356
|
+
expect(wrapper.find('.ui-card__footer--center').exists()).toBe(true)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('applies between alignment', () => {
|
|
360
|
+
const wrapper = mount(CardFooter, {
|
|
361
|
+
props: { align: 'between' }
|
|
362
|
+
})
|
|
363
|
+
expect(wrapper.find('.ui-card__footer--between').exists()).toBe(true)
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
describe('Borderless', () => {
|
|
368
|
+
it('does not apply borderless class by default', () => {
|
|
369
|
+
const wrapper = mount(CardFooter)
|
|
370
|
+
expect(wrapper.find('.ui-card__footer--borderless').exists()).toBe(false)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('applies borderless class when prop is true', () => {
|
|
374
|
+
const wrapper = mount(CardFooter, {
|
|
375
|
+
props: { borderless: true }
|
|
376
|
+
})
|
|
377
|
+
expect(wrapper.find('.ui-card__footer--borderless').exists()).toBe(true)
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
describe('CardImage', () => {
|
|
383
|
+
const defaultProps = {
|
|
384
|
+
src: '/test-image.jpg',
|
|
385
|
+
alt: 'Test image'
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
describe('Rendering', () => {
|
|
389
|
+
it('renders image element', () => {
|
|
390
|
+
const wrapper = mount(CardImage, {
|
|
391
|
+
props: defaultProps
|
|
392
|
+
})
|
|
393
|
+
expect(wrapper.find('.ui-card__image').exists()).toBe(true)
|
|
394
|
+
expect(wrapper.find('img').exists()).toBe(true)
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('sets src attribute', () => {
|
|
398
|
+
const wrapper = mount(CardImage, {
|
|
399
|
+
props: defaultProps
|
|
400
|
+
})
|
|
401
|
+
expect(wrapper.find('img').attributes('src')).toBe('/test-image.jpg')
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('sets alt attribute', () => {
|
|
405
|
+
const wrapper = mount(CardImage, {
|
|
406
|
+
props: defaultProps
|
|
407
|
+
})
|
|
408
|
+
expect(wrapper.find('img').attributes('alt')).toBe('Test image')
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('sets loading attribute to lazy by default', () => {
|
|
412
|
+
const wrapper = mount(CardImage, {
|
|
413
|
+
props: defaultProps
|
|
414
|
+
})
|
|
415
|
+
expect(wrapper.find('img').attributes('loading')).toBe('lazy')
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
it('sets loading attribute to eager when specified', () => {
|
|
419
|
+
const wrapper = mount(CardImage, {
|
|
420
|
+
props: { ...defaultProps, loading: 'eager' }
|
|
421
|
+
})
|
|
422
|
+
expect(wrapper.find('img').attributes('loading')).toBe('eager')
|
|
423
|
+
})
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
describe('Position', () => {
|
|
427
|
+
it('applies top position by default', () => {
|
|
428
|
+
const wrapper = mount(CardImage, {
|
|
429
|
+
props: defaultProps
|
|
430
|
+
})
|
|
431
|
+
expect(wrapper.find('.ui-card__image--top').exists()).toBe(true)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
it('applies bottom position', () => {
|
|
435
|
+
const wrapper = mount(CardImage, {
|
|
436
|
+
props: { ...defaultProps, position: 'bottom' }
|
|
437
|
+
})
|
|
438
|
+
expect(wrapper.find('.ui-card__image--bottom').exists()).toBe(true)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('applies left position', () => {
|
|
442
|
+
const wrapper = mount(CardImage, {
|
|
443
|
+
props: { ...defaultProps, position: 'left' }
|
|
444
|
+
})
|
|
445
|
+
expect(wrapper.find('.ui-card__image--left').exists()).toBe(true)
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it('applies right position', () => {
|
|
449
|
+
const wrapper = mount(CardImage, {
|
|
450
|
+
props: { ...defaultProps, position: 'right' }
|
|
451
|
+
})
|
|
452
|
+
expect(wrapper.find('.ui-card__image--right').exists()).toBe(true)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('applies background position', () => {
|
|
456
|
+
const wrapper = mount(CardImage, {
|
|
457
|
+
props: { ...defaultProps, position: 'background' }
|
|
458
|
+
})
|
|
459
|
+
expect(wrapper.find('.ui-card__image--background').exists()).toBe(true)
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
describe('Aspect Ratio', () => {
|
|
464
|
+
it('does not set aspect ratio style when auto', () => {
|
|
465
|
+
const wrapper = mount(CardImage, {
|
|
466
|
+
props: { ...defaultProps, aspectRatio: 'auto' }
|
|
467
|
+
})
|
|
468
|
+
expect(wrapper.find('.ui-card__image').attributes('style')).toBeUndefined()
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
it('sets 16:9 aspect ratio', () => {
|
|
472
|
+
const wrapper = mount(CardImage, {
|
|
473
|
+
props: { ...defaultProps, aspectRatio: '16:9' }
|
|
474
|
+
})
|
|
475
|
+
expect(wrapper.find('.ui-card__image').attributes('style')).toContain('--aspect-ratio: 16 / 9')
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
it('sets 4:3 aspect ratio', () => {
|
|
479
|
+
const wrapper = mount(CardImage, {
|
|
480
|
+
props: { ...defaultProps, aspectRatio: '4:3' }
|
|
481
|
+
})
|
|
482
|
+
expect(wrapper.find('.ui-card__image').attributes('style')).toContain('--aspect-ratio: 4 / 3')
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
it('sets 1:1 aspect ratio', () => {
|
|
486
|
+
const wrapper = mount(CardImage, {
|
|
487
|
+
props: { ...defaultProps, aspectRatio: '1:1' }
|
|
488
|
+
})
|
|
489
|
+
expect(wrapper.find('.ui-card__image').attributes('style')).toContain('--aspect-ratio: 1 / 1')
|
|
490
|
+
})
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
describe('Object Fit', () => {
|
|
494
|
+
it('applies cover fit by default', () => {
|
|
495
|
+
const wrapper = mount(CardImage, {
|
|
496
|
+
props: defaultProps
|
|
497
|
+
})
|
|
498
|
+
expect(wrapper.find('.ui-card__image-img--cover').exists()).toBe(true)
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('applies contain fit', () => {
|
|
502
|
+
const wrapper = mount(CardImage, {
|
|
503
|
+
props: { ...defaultProps, fit: 'contain' }
|
|
504
|
+
})
|
|
505
|
+
expect(wrapper.find('.ui-card__image-img--contain').exists()).toBe(true)
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
it('applies fill fit', () => {
|
|
509
|
+
const wrapper = mount(CardImage, {
|
|
510
|
+
props: { ...defaultProps, fit: 'fill' }
|
|
511
|
+
})
|
|
512
|
+
expect(wrapper.find('.ui-card__image-img--fill').exists()).toBe(true)
|
|
513
|
+
})
|
|
514
|
+
})
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
describe('Card Composition', () => {
|
|
518
|
+
it('renders complete card with all sub-components', () => {
|
|
519
|
+
const wrapper = mount({
|
|
520
|
+
components: { Card, CardHeader, CardContent, CardFooter },
|
|
521
|
+
template: `
|
|
522
|
+
<Card>
|
|
523
|
+
<CardHeader title="Title" subtitle="Subtitle" />
|
|
524
|
+
<CardContent>Content</CardContent>
|
|
525
|
+
<CardFooter>Footer</CardFooter>
|
|
526
|
+
</Card>
|
|
527
|
+
`
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
expect(wrapper.find('.ui-card').exists()).toBe(true)
|
|
531
|
+
expect(wrapper.find('.ui-card__header').exists()).toBe(true)
|
|
532
|
+
expect(wrapper.find('.ui-card__content').exists()).toBe(true)
|
|
533
|
+
expect(wrapper.find('.ui-card__footer').exists()).toBe(true)
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
it('renders card with image', () => {
|
|
537
|
+
const wrapper = mount({
|
|
538
|
+
components: { Card, CardImage, CardContent },
|
|
539
|
+
template: `
|
|
540
|
+
<Card>
|
|
541
|
+
<CardImage src="/test.jpg" alt="Test" />
|
|
542
|
+
<CardContent>Content</CardContent>
|
|
543
|
+
</Card>
|
|
544
|
+
`
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
expect(wrapper.find('.ui-card__image').exists()).toBe(true)
|
|
548
|
+
expect(wrapper.find('.ui-card__content').exists()).toBe(true)
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
it('renders horizontal card', () => {
|
|
552
|
+
const wrapper = mount({
|
|
553
|
+
components: { Card, CardImage, CardContent },
|
|
554
|
+
template: `
|
|
555
|
+
<Card horizontal>
|
|
556
|
+
<CardImage src="/test.jpg" alt="Test" position="left" />
|
|
557
|
+
<CardContent>Content</CardContent>
|
|
558
|
+
</Card>
|
|
559
|
+
`
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
expect(wrapper.find('.ui-card--horizontal').exists()).toBe(true)
|
|
563
|
+
expect(wrapper.find('.ui-card__image--left').exists()).toBe(true)
|
|
564
|
+
})
|
|
565
|
+
})
|