@nexxtmove/ui 2.6.0 → 2.7.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/dist/index.js +49 -126
- package/dist/nuxt.js +0 -1
- package/package.json +38 -36
- package/src/attribute-contract.test.ts +151 -0
- package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
- package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
- package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
- package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
- package/src/components/atoms/Button/Button.stories.ts +290 -0
- package/src/components/atoms/Button/Button.test.ts +406 -0
- package/src/components/atoms/Card/Card.stories.ts +45 -0
- package/src/components/atoms/Card/Card.test.ts +46 -0
- package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
- package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
- package/src/components/atoms/Chip/Chip.stories.ts +274 -0
- package/src/components/atoms/Chip/Chip.test.ts +230 -0
- package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
- package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
- package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
- package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
- package/src/components/atoms/Icon/Icon.stories.ts +63 -0
- package/src/components/atoms/Icon/Icon.test.ts +89 -0
- package/src/components/atoms/Loader/Loader.stories.ts +36 -0
- package/src/components/atoms/Loader/Loader.test.ts +40 -0
- package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
- package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
- package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
- package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
- package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
- package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
- package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
- package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
- package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
- package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
- package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
- package/src/components/atoms/Slider/Slider.stories.ts +151 -0
- package/src/components/atoms/Slider/Slider.test.ts +191 -0
- package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
- package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
- package/src/components/atoms/Switch/Switch.stories.ts +91 -0
- package/src/components/atoms/Switch/Switch.test.ts +183 -0
- package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
- package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
- package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
- package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
- package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
- package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
- package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
- package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
- package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
- package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
- package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
- package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
- package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
- package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
- package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
- package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
- package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +563 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +689 -0
- package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +793 -0
- package/src/components/editor/molecules/EditorColor/gradientClass.test.ts +303 -0
- package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.stories.ts +72 -0
- package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.test.ts +124 -0
- package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.stories.ts +46 -0
- package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.test.ts +97 -0
- package/src/components/editor/molecules/EditorFontSize/EditorFontSize.stories.ts +248 -0
- package/src/components/editor/molecules/EditorFontSize/EditorFontSize.test.ts +288 -0
- package/src/components/editor/molecules/EditorGap/EditorGap.stories.ts +129 -0
- package/src/components/editor/molecules/EditorGap/EditorGap.test.ts +253 -0
- package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.stories.ts +68 -0
- package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.test.ts +150 -0
- package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.stories.ts +71 -0
- package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.test.ts +161 -0
- package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.stories.ts +204 -0
- package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.test.ts +238 -0
- package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.stories.ts +257 -0
- package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.test.ts +286 -0
- package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.stories.ts +135 -0
- package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.test.ts +230 -0
- package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.stories.ts +154 -0
- package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.test.ts +112 -0
- package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.stories.ts +206 -0
- package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.test.ts +332 -0
- package/src/components/editor/molecules/EditorPadding/EditorPadding.stories.ts +374 -0
- package/src/components/editor/molecules/EditorPadding/EditorPadding.test.ts +712 -0
- package/src/components/editor/molecules/EditorRadius/EditorRadius.stories.ts +64 -0
- package/src/components/editor/molecules/EditorRadius/EditorRadius.test.ts +137 -0
- package/src/components/editor/molecules/EditorShadow/EditorShadow.stories.ts +103 -0
- package/src/components/editor/molecules/EditorShadow/EditorShadow.test.ts +168 -0
- package/src/components/editor/molecules/EditorSize/EditorSize.stories.ts +84 -0
- package/src/components/editor/molecules/EditorSize/EditorSize.test.ts +253 -0
- package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.stories.ts +49 -0
- package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.test.ts +43 -0
- package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.stories.ts +69 -0
- package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.test.ts +155 -0
- package/src/components/molecules/Announcement/Announcement.stories.ts +224 -0
- package/src/components/molecules/Announcement/Announcement.test.ts +161 -0
- package/src/components/molecules/ContactInfoCard/ContactInfoCard.stories.ts +99 -0
- package/src/components/molecules/ContactInfoCard/ContactInfoCard.test.ts +70 -0
- package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.stories.ts +148 -0
- package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.test.ts +271 -0
- package/src/components/molecules/DoughnutChart/DoughnutChart.stories.ts +45 -0
- package/src/components/molecules/DoughnutChart/DoughnutChart.test.ts +205 -0
- package/src/components/molecules/InfoBlock/InfoBlock.stories.ts +84 -0
- package/src/components/molecules/InfoBlock/InfoBlock.test.ts +101 -0
- package/src/components/molecules/InputField/InputField.stories.ts +117 -0
- package/src/components/molecules/InputField/InputField.test.ts +138 -0
- package/src/components/molecules/InputSelect/InputSelect.stories.ts +116 -0
- package/src/components/molecules/InputSelect/InputSelect.test.ts +127 -0
- package/src/components/molecules/ModalFooter/ModalFooter.stories.ts +204 -0
- package/src/components/molecules/ModalFooter/ModalFooter.test.ts +276 -0
- package/src/components/molecules/PageCard/PageCard.stories.ts +100 -0
- package/src/components/molecules/PageCard/PageCard.test.ts +91 -0
- package/src/components/molecules/PageContainer/PageContainer.stories.ts +90 -0
- package/src/components/molecules/PageContainer/PageContainer.test.ts +50 -0
- package/src/components/molecules/PageMockup/PageMockup.stories.ts +38 -0
- package/src/components/molecules/PageMockup/PageMockup.test.ts +20 -0
- package/src/components/molecules/PageTypeTile/PageTypeTile.stories.ts +63 -0
- package/src/components/molecules/PageTypeTile/PageTypeTile.test.ts +41 -0
- package/src/components/molecules/Pagination/Pagination.stories.ts +72 -0
- package/src/components/molecules/Pagination/Pagination.test.ts +130 -0
- package/src/components/molecules/PickerGroup/PickerGroup.stories.ts +90 -0
- package/src/components/molecules/PickerGroup/PickerGroup.test.ts +145 -0
- package/src/components/molecules/PickerList/PickerList.stories.ts +436 -0
- package/src/components/molecules/PickerList/PickerList.test.ts +870 -0
- package/src/components/molecules/PlaceholderInput/PlaceholderInput.stories.ts +48 -0
- package/src/components/molecules/PlaceholderInput/PlaceholderInput.test.ts +107 -0
- package/src/components/molecules/PropertyCard/PropertyCard.stories.ts +46 -0
- package/src/components/molecules/PropertyCard/PropertyCard.test.ts +59 -0
- package/src/components/molecules/Select/Select.stories.ts +264 -0
- package/src/components/molecules/Select/Select.test.ts +315 -0
- package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.stories.ts +84 -0
- package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.test.ts +103 -0
- package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.stories.ts +11 -0
- package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.test.ts +38 -0
- package/src/components/molecules/SocialIcons/SocialIcons.stories.ts +28 -0
- package/src/components/molecules/SocialIcons/SocialIcons.test.ts +58 -0
- package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
- package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +116 -0
- package/src/components/molecules/SocialMediaType/SocialMediaType.stories.ts +37 -0
- package/src/components/molecules/SocialMediaType/SocialMediaType.test.ts +126 -0
- package/src/components/molecules/Source/Source.stories.ts +35 -0
- package/src/components/molecules/Source/Source.test.ts +45 -0
- package/src/components/molecules/TabbedContent/TabbedContent.stories.ts +96 -0
- package/src/components/molecules/TabbedContent/TabbedContent.test.ts +109 -0
- package/src/components/molecules/Tabs/Tabs.stories.ts +230 -0
- package/src/components/molecules/Tabs/Tabs.test.ts +280 -0
- package/src/components/molecules/TemplateCard/TemplateCard.stories.ts +327 -0
- package/src/components/molecules/TemplateCard/TemplateCard.test.ts +339 -0
- package/src/components/molecules/Textarea/Textarea.stories.ts +216 -0
- package/src/components/molecules/Textarea/Textarea.test.ts +191 -0
- package/src/components/molecules/TimelineEvent/TimelineEvent.stories.ts +319 -0
- package/src/components/molecules/TimelineEvent/TimelineEvent.test.ts +224 -0
- package/src/components/molecules/TimelineLanes/TimelineLanes.stories.ts +29 -0
- package/src/components/molecules/TimelineLanes/TimelineLanes.test.ts +30 -0
- package/src/components/molecules/Toast/Toast.stories.ts +110 -0
- package/src/components/molecules/Toast/Toast.test.ts +103 -0
- package/src/components/molecules/ToggleGroup/ToggleGroup.stories.ts +297 -0
- package/src/components/molecules/ToggleGroup/ToggleGroup.test.ts +578 -0
- package/src/components/molecules/Tree/Tree.stories.ts +884 -0
- package/src/components/molecules/Tree/Tree.test.ts +1247 -0
- package/src/components/molecules/Tree/tree-drop.test.ts +223 -0
- package/src/components/organisms/Calendar/Calendar.stories.ts +108 -0
- package/src/components/organisms/Calendar/Calendar.test.ts +279 -0
- package/src/components/organisms/Calendar/_CalendarDayView.test.ts +104 -0
- package/src/components/organisms/Calendar/_CalendarHeader.test.ts +86 -0
- package/src/components/organisms/Calendar/_CalendarMonthView.test.ts +52 -0
- package/src/components/organisms/Calendar/_CalendarTimePicker.test.ts +89 -0
- package/src/components/organisms/Calendar/_CalendarYearView.test.ts +56 -0
- package/src/components/organisms/ColorPicker/ColorPicker.stories.ts +229 -0
- package/src/components/organisms/ColorPicker/ColorPicker.test.ts +638 -0
- package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
- package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
- package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
- package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
- package/src/components/organisms/Combobox/Combobox.vue +1 -1
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
- package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
- package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
- package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
- package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
- package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
- package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
- package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
- package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
- package/src/components/organisms/Header/Header.stories.ts +75 -0
- package/src/components/organisms/Header/Header.test.ts +233 -0
- package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
- package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
- package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
- package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
- package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
- package/src/components/organisms/Modal/Modal.stories.ts +350 -0
- package/src/components/organisms/Modal/Modal.test.ts +275 -0
- package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
- package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
- package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
- package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
- package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
- package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
- package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
- package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
- package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
- package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
- package/src/components/organisms/Table/Table.stories.ts +332 -0
- package/src/components/organisms/Table/Table.test.ts +320 -0
- package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
- package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
- package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
- package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
- package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
- package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
- package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
- package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
- package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
- package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import { defineComponent } from 'vue'
|
|
4
|
+
import TimelinePhaseblock from './TimelinePhaseblock.vue'
|
|
5
|
+
import type { TimelinePhase } from '../../molecules/TimelineEvent/TimelineTypes'
|
|
6
|
+
|
|
7
|
+
const TimelineEventStub = defineComponent({
|
|
8
|
+
name: 'NexxtTimelineEvent',
|
|
9
|
+
props: ['event', 'splitView'],
|
|
10
|
+
template: '<div class="timeline-event-stub" />',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const makePhase = (
|
|
14
|
+
value: number,
|
|
15
|
+
entered_at: string | null = '2024-06-01T09:00:00.000Z',
|
|
16
|
+
exited_at: string | null = '2024-07-01T09:00:00.000Z',
|
|
17
|
+
events: TimelinePhase['events'] = [],
|
|
18
|
+
): TimelinePhase => ({
|
|
19
|
+
phase: { value },
|
|
20
|
+
entered_at,
|
|
21
|
+
exited_at,
|
|
22
|
+
events,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const baseEvent = {
|
|
26
|
+
id: 1,
|
|
27
|
+
type: 'contact_created',
|
|
28
|
+
happened_at: '2024-06-10T10:30:00.000Z',
|
|
29
|
+
medium_type: null,
|
|
30
|
+
url: null,
|
|
31
|
+
page_title: null,
|
|
32
|
+
request_reason: null,
|
|
33
|
+
agenda_item_type: null,
|
|
34
|
+
source: 'system',
|
|
35
|
+
direction: 'outbound',
|
|
36
|
+
description: null,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const stubs = {
|
|
40
|
+
NexxtIcon: true,
|
|
41
|
+
NexxtTimelineEvent: TimelineEventStub,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const mountBlock = (overrides: Partial<InstanceType<typeof TimelinePhaseblock>['$props']> = {}) => {
|
|
45
|
+
const phases = [makePhase(4), makePhase(2)]
|
|
46
|
+
return mount(TimelinePhaseblock, {
|
|
47
|
+
props: {
|
|
48
|
+
phase: phases[0],
|
|
49
|
+
index: 0,
|
|
50
|
+
phases,
|
|
51
|
+
isSplitView: true,
|
|
52
|
+
isLast: false,
|
|
53
|
+
...overrides,
|
|
54
|
+
},
|
|
55
|
+
global: { stubs },
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('TimelinePhaseblock', () => {
|
|
60
|
+
describe('data attribute', () => {
|
|
61
|
+
it('sets data-phase to the phase value', () => {
|
|
62
|
+
const wrapper = mountBlock()
|
|
63
|
+
expect(wrapper.attributes('data-phase')).toBe('4')
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('outbound/inbound header', () => {
|
|
68
|
+
it('shows the header when index is 0 and isSplitView is true', () => {
|
|
69
|
+
const wrapper = mountBlock({ index: 0, isSplitView: true })
|
|
70
|
+
expect(wrapper.text()).toContain('Outbound')
|
|
71
|
+
expect(wrapper.text()).toContain('Inbound')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('does not show the header when index is not 0', () => {
|
|
75
|
+
const phases = [makePhase(4), makePhase(2)]
|
|
76
|
+
const wrapper = mountBlock({ phase: phases[1], index: 1, phases, isSplitView: true })
|
|
77
|
+
expect(wrapper.text()).not.toContain('Outbound')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('does not show the header when isSplitView is false', () => {
|
|
81
|
+
const wrapper = mountBlock({ index: 0, isSplitView: false })
|
|
82
|
+
expect(wrapper.text()).not.toContain('Outbound')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('timeline lines', () => {
|
|
87
|
+
it('always renders the left timeline line', () => {
|
|
88
|
+
const wrapper = mountBlock({ isSplitView: false })
|
|
89
|
+
expect(wrapper.find('.border-l.left-5').exists()).toBe(true)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('renders the right timeline line when isSplitView is true', () => {
|
|
93
|
+
const wrapper = mountBlock({ isSplitView: true })
|
|
94
|
+
expect(wrapper.find('.border-l.right-5').exists()).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('does not render the right timeline line when isSplitView is false', () => {
|
|
98
|
+
const wrapper = mountBlock({ isSplitView: false })
|
|
99
|
+
expect(wrapper.find('.border-l.right-5').exists()).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('applies the phase color to the left timeline line', () => {
|
|
103
|
+
// phase value 4 → border-blue-500
|
|
104
|
+
const wrapper = mountBlock()
|
|
105
|
+
expect(wrapper.find('.border-l.left-5').classes()).toContain('border-blue-500')
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('events', () => {
|
|
110
|
+
it('renders a NexxtTimelineEvent for each event in the phase', () => {
|
|
111
|
+
const phases = [
|
|
112
|
+
makePhase(4, '2024-06-01T09:00:00.000Z', '2024-07-01T09:00:00.000Z', [
|
|
113
|
+
baseEvent,
|
|
114
|
+
{ ...baseEvent, id: 2 },
|
|
115
|
+
]),
|
|
116
|
+
]
|
|
117
|
+
const wrapper = mount(TimelinePhaseblock, {
|
|
118
|
+
props: { phase: phases[0], index: 0, phases, isSplitView: true, isLast: true },
|
|
119
|
+
global: { stubs },
|
|
120
|
+
})
|
|
121
|
+
expect(wrapper.findAllComponents(TimelineEventStub).length).toBe(2)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('renders no events when the phase has an empty events array', () => {
|
|
125
|
+
const wrapper = mountBlock()
|
|
126
|
+
expect(wrapper.findAllComponents(TimelineEventStub).length).toBe(0)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('passes isSplitView to each event', () => {
|
|
130
|
+
const phases = [
|
|
131
|
+
makePhase(4, '2024-06-01T09:00:00.000Z', '2024-07-01T09:00:00.000Z', [baseEvent]),
|
|
132
|
+
]
|
|
133
|
+
const wrapper = mount(TimelinePhaseblock, {
|
|
134
|
+
props: { phase: phases[0], index: 0, phases, isSplitView: false, isLast: true },
|
|
135
|
+
global: { stubs },
|
|
136
|
+
})
|
|
137
|
+
expect(wrapper.findComponent(TimelineEventStub).props('splitView')).toBe(false)
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe('phase separator badge', () => {
|
|
142
|
+
it('shows the separator when index is not the last in the phases array', () => {
|
|
143
|
+
// phases has 2 items, index 0 → 0 !== 1 → shows separator
|
|
144
|
+
const wrapper = mountBlock({ index: 0 })
|
|
145
|
+
expect(wrapper.find('.rounded-full').exists()).toBe(true)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('does not show the separator when index equals phases.length - 1', () => {
|
|
149
|
+
const phases = [makePhase(4), makePhase(2)]
|
|
150
|
+
const wrapper = mountBlock({ phase: phases[1], index: 1, phases })
|
|
151
|
+
expect(wrapper.find('.rounded-full').exists()).toBe(false)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('displays the phase label in the separator', () => {
|
|
155
|
+
// phase value 4 → 'Selectie'
|
|
156
|
+
const wrapper = mountBlock()
|
|
157
|
+
expect(wrapper.find('.rounded-full').text()).toContain('Selectie')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('applies the correct background color class to the separator badge', () => {
|
|
161
|
+
// phase value 4 → bg-blue-500
|
|
162
|
+
const wrapper = mountBlock()
|
|
163
|
+
expect(wrapper.find('.rounded-full').classes()).toContain('bg-blue-500')
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('applies the correct text color class to the separator badge', () => {
|
|
167
|
+
// phase value 4 → text-white
|
|
168
|
+
const wrapper = mountBlock()
|
|
169
|
+
expect(wrapper.find('.rounded-full').classes()).toContain('text-white')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('shows an up arrow when the current phase value is higher than the previous phase', () => {
|
|
173
|
+
// index 0 = value 4, index 1 = value 2 → 4 > 2 → up
|
|
174
|
+
const wrapper = mountBlock()
|
|
175
|
+
const arrowStub = wrapper.find('nexxt-icon-stub[name="arrow-up"]')
|
|
176
|
+
expect(arrowStub.exists()).toBe(true)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('shows a down arrow when the current phase value is lower than the previous phase', () => {
|
|
180
|
+
// index 0 = value 2, index 1 = value 4 → 2 < 4 → down
|
|
181
|
+
const phases = [makePhase(2), makePhase(4)]
|
|
182
|
+
const wrapper = mountBlock({ phase: phases[0], index: 0, phases })
|
|
183
|
+
const arrowStub = wrapper.find('nexxt-icon-stub[name="arrow-down"]')
|
|
184
|
+
expect(arrowStub.exists()).toBe(true)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('shows no direction arrow when there is no previous phase', () => {
|
|
188
|
+
const phases = [makePhase(4)]
|
|
189
|
+
// With only one phase, index 0 === phases.length - 1, so separator is hidden anyway.
|
|
190
|
+
// Use a 2-item list but check the last item's arrow (no prevPhase for last)
|
|
191
|
+
// Actually let's test: if index === phases.length -1, separator is hidden.
|
|
192
|
+
// The arrow is inside the separator, so let's just check getPhaseDirection returns null
|
|
193
|
+
// by having no phase at index+1. We can't show separator unless index !== phases.length-1,
|
|
194
|
+
// so create a dummy scenario: index=0, phases=[phase0] would hide separator.
|
|
195
|
+
// Instead verify: no arrow-up or arrow-down when phases only have one entry.
|
|
196
|
+
const wrapper = mount(TimelinePhaseblock, {
|
|
197
|
+
props: { phase: phases[0], index: 0, phases, isSplitView: true, isLast: true },
|
|
198
|
+
global: { stubs },
|
|
199
|
+
})
|
|
200
|
+
expect(wrapper.find('nexxt-icon-stub[name="arrow-up"]').exists()).toBe(false)
|
|
201
|
+
expect(wrapper.find('nexxt-icon-stub[name="arrow-down"]').exists()).toBe(false)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('shows entered_at date when phase progressed upward', () => {
|
|
205
|
+
// phase value 4 > prev value 2 → show entered_at
|
|
206
|
+
const phase = makePhase(4, '2024-06-01T09:00:00.000Z', '2024-07-01T09:00:00.000Z')
|
|
207
|
+
const phases = [phase, makePhase(2)]
|
|
208
|
+
const wrapper = mountBlock({ phase, phases })
|
|
209
|
+
// entered_at = 2024-06-01 → formatted in nl-NL → contains "jun"
|
|
210
|
+
const dateEl = wrapper.find('.text-gray-500')
|
|
211
|
+
expect(dateEl.text()).toContain('jun')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('shows exited_at date when phase regressed downward', () => {
|
|
215
|
+
// phase value 2 < prev value 4 → show exited_at
|
|
216
|
+
const phase = makePhase(2, '2024-06-01T09:00:00.000Z', '2024-07-01T09:00:00.000Z')
|
|
217
|
+
const phases = [phase, makePhase(4)]
|
|
218
|
+
const wrapper = mountBlock({ phase, phases })
|
|
219
|
+
// exited_at = 2024-07-01 → contains "jul"
|
|
220
|
+
const dateEl = wrapper.find('.text-gray-500')
|
|
221
|
+
expect(dateEl.text()).toContain('jul')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('shows empty string when relevant date is null', () => {
|
|
225
|
+
const phase = makePhase(4, null, null)
|
|
226
|
+
const phases = [phase, makePhase(2)]
|
|
227
|
+
const wrapper = mountBlock({ phase, phases })
|
|
228
|
+
expect(wrapper.find('.text-gray-500').text()).toBe('')
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('renders the horizontal divider line in split view', () => {
|
|
232
|
+
const wrapper = mountBlock({ isSplitView: true })
|
|
233
|
+
expect(wrapper.find('.h-px.w-\\[75\\%\\]').exists()).toBe(true)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('does not render the horizontal divider line outside split view', () => {
|
|
237
|
+
const wrapper = mountBlock({ isSplitView: false })
|
|
238
|
+
expect(wrapper.find('.h-px.w-\\[75\\%\\]').exists()).toBe(false)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('applies pl-16 to the separator container when not in split view', () => {
|
|
242
|
+
const wrapper = mountBlock({ isSplitView: false })
|
|
243
|
+
const separator = wrapper.find('.relative.flex.flex-col.items-center')
|
|
244
|
+
expect(separator.classes()).toContain('pl-16')
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('does not apply pl-16 to the separator container in split view', () => {
|
|
248
|
+
const wrapper = mountBlock({ isSplitView: true })
|
|
249
|
+
const separator = wrapper.find('.relative.flex.flex-col.items-center')
|
|
250
|
+
expect(separator.classes()).not.toContain('pl-16')
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
describe('gradient', () => {
|
|
255
|
+
it('applies gradient classes to the vertical gradient bar when there is a previous phase', () => {
|
|
256
|
+
// phase value 4 → fromColor: from-blue-500; prev phase value 2 → toColor: to-purple-500
|
|
257
|
+
const wrapper = mountBlock()
|
|
258
|
+
const gradientBar = wrapper.find('.bg-linear-to-b.left-5')
|
|
259
|
+
expect(gradientBar.classes()).toContain('from-blue-500')
|
|
260
|
+
expect(gradientBar.classes()).toContain('to-purple-500')
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('applies no gradient classes when there is no previous phase', () => {
|
|
264
|
+
const phases = [makePhase(4)]
|
|
265
|
+
const wrapper = mount(TimelinePhaseblock, {
|
|
266
|
+
props: { phase: phases[0], index: 0, phases, isSplitView: true, isLast: true },
|
|
267
|
+
global: { stubs },
|
|
268
|
+
})
|
|
269
|
+
// No separator rendered, so no gradient bar inside separator
|
|
270
|
+
expect(wrapper.find('.bg-linear-to-b').exists()).toBe(false)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('renders the right-side gradient bar in split view', () => {
|
|
274
|
+
const wrapper = mountBlock({ isSplitView: true })
|
|
275
|
+
expect(wrapper.find('.bg-linear-to-b.right-5').exists()).toBe(true)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('does not render the right-side gradient bar outside split view', () => {
|
|
279
|
+
const wrapper = mountBlock({ isSplitView: false })
|
|
280
|
+
expect(wrapper.find('.bg-linear-to-b.right-5').exists()).toBe(false)
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import TimelineTrack from './TimelineTrack.vue'
|
|
3
|
+
import type { TimelineEvent } from '../../molecules/TimelineEvent/TimelineTypes'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Components/Organisms/Timeline Track',
|
|
7
|
+
component: TimelineTrack,
|
|
8
|
+
} satisfies Meta<typeof TimelineTrack>
|
|
9
|
+
|
|
10
|
+
type Story = StoryObj<typeof TimelineTrack>
|
|
11
|
+
|
|
12
|
+
// A proposal timeline: no journey phases, events composed by the consumer.
|
|
13
|
+
const proposalEvents: TimelineEvent[] = [
|
|
14
|
+
{
|
|
15
|
+
id: 'accepted-1',
|
|
16
|
+
type: 'proposal_accepted',
|
|
17
|
+
happened_at: '2024-06-18T14:12:00.000Z',
|
|
18
|
+
direction: 'inbound',
|
|
19
|
+
description: 'Door Sophie de Vries',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'opened-1',
|
|
23
|
+
type: 'proposal_opened',
|
|
24
|
+
happened_at: '2024-06-17T09:41:00.000Z',
|
|
25
|
+
direction: 'inbound',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'mail-opened-1',
|
|
29
|
+
type: 'proposal_mail_opened',
|
|
30
|
+
happened_at: '2024-06-16T20:03:00.000Z',
|
|
31
|
+
direction: 'inbound',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'failed-1',
|
|
35
|
+
type: 'proposal_send_failed',
|
|
36
|
+
happened_at: '2024-06-16T12:05:00.000Z',
|
|
37
|
+
direction: 'outbound',
|
|
38
|
+
description: 'Naar Kees Jansen',
|
|
39
|
+
request_reason: 'Mailbox bestaat niet',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'delivered-1',
|
|
43
|
+
type: 'proposal_delivered',
|
|
44
|
+
happened_at: '2024-06-16T12:01:00.000Z',
|
|
45
|
+
direction: 'outbound',
|
|
46
|
+
description: 'Naar Jan de Vries',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'sent-1',
|
|
50
|
+
type: 'proposal_sent',
|
|
51
|
+
happened_at: '2024-06-16T12:00:00.000Z',
|
|
52
|
+
direction: 'outbound',
|
|
53
|
+
description: 'Naar Jan de Vries',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'status-1',
|
|
57
|
+
type: 'proposal_status_changed',
|
|
58
|
+
happened_at: '2024-06-16T11:59:00.000Z',
|
|
59
|
+
direction: 'outbound',
|
|
60
|
+
// Dynamic label: overrides the map, icon and color still come from it.
|
|
61
|
+
label: 'Status gewijzigd van Gepland naar Verzonden',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'scheduled-1',
|
|
65
|
+
type: 'proposal_scheduled',
|
|
66
|
+
happened_at: '2024-06-15T16:20:00.000Z',
|
|
67
|
+
direction: 'outbound',
|
|
68
|
+
description: 'Door Sophie de Vries',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'created-1',
|
|
72
|
+
type: 'proposal_created',
|
|
73
|
+
happened_at: '2024-06-15T16:18:00.000Z',
|
|
74
|
+
direction: 'outbound',
|
|
75
|
+
description: 'Door Sophie de Vries',
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
export const ProposalTimeline: Story = {
|
|
80
|
+
args: {
|
|
81
|
+
events: proposalEvents,
|
|
82
|
+
splitView: true,
|
|
83
|
+
leftLabel: 'Nexxtmove',
|
|
84
|
+
rightLabel: 'Contact',
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const SingleColumn: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
events: proposalEvents,
|
|
91
|
+
splitView: false,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const CustomLineColor: Story = {
|
|
96
|
+
args: {
|
|
97
|
+
events: proposalEvents,
|
|
98
|
+
splitView: true,
|
|
99
|
+
leftLabel: 'Nexxtmove',
|
|
100
|
+
rightLabel: 'Contact',
|
|
101
|
+
color: 'border-purple-500',
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const Expired: Story = {
|
|
106
|
+
args: {
|
|
107
|
+
splitView: true,
|
|
108
|
+
leftLabel: 'Nexxtmove',
|
|
109
|
+
rightLabel: 'Contact',
|
|
110
|
+
events: [
|
|
111
|
+
{
|
|
112
|
+
id: 'expired-1',
|
|
113
|
+
type: 'proposal_expired',
|
|
114
|
+
happened_at: '2024-06-22T00:00:00.000Z',
|
|
115
|
+
direction: 'outbound',
|
|
116
|
+
},
|
|
117
|
+
...proposalEvents.slice(3),
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import TimelineTrack from './TimelineTrack.vue'
|
|
4
|
+
import type { TimelineEvent } from '../../molecules/TimelineEvent/TimelineTypes'
|
|
5
|
+
|
|
6
|
+
const events: TimelineEvent[] = [
|
|
7
|
+
{ id: 'created-1', type: 'proposal_created', happened_at: '2024-06-15T10:30:00.000Z' },
|
|
8
|
+
{
|
|
9
|
+
id: 'sent-1',
|
|
10
|
+
type: 'proposal_sent',
|
|
11
|
+
happened_at: '2024-06-15T11:00:00.000Z',
|
|
12
|
+
direction: 'outbound',
|
|
13
|
+
description: 'Naar Jan de Vries',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'opened-1',
|
|
17
|
+
type: 'proposal_opened',
|
|
18
|
+
happened_at: '2024-06-15T12:00:00.000Z',
|
|
19
|
+
direction: 'inbound',
|
|
20
|
+
},
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const mountTrack = (props = {}, slots = {}) =>
|
|
24
|
+
mount(TimelineTrack, { props: { events, ...props }, slots })
|
|
25
|
+
|
|
26
|
+
describe('TimelineTrack', () => {
|
|
27
|
+
it('renders an event for each item', () => {
|
|
28
|
+
const wrapper = mountTrack()
|
|
29
|
+
expect(wrapper.findAll('[data-event-date]')).toHaveLength(3)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('renders lane headers in split view', () => {
|
|
33
|
+
const wrapper = mountTrack({ splitView: true })
|
|
34
|
+
expect(wrapper.text()).toContain('Outbound')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('passes lane labels through', () => {
|
|
38
|
+
const wrapper = mountTrack({ leftLabel: 'Nexxtmove', rightLabel: 'Contact' })
|
|
39
|
+
expect(wrapper.text()).toContain('Nexxtmove')
|
|
40
|
+
expect(wrapper.text()).toContain('Contact')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('does not render lane headers when splitView is false', () => {
|
|
44
|
+
const wrapper = mountTrack({ splitView: false })
|
|
45
|
+
expect(wrapper.text()).not.toContain('Outbound')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('does not render lane headers when showLanes is false', () => {
|
|
49
|
+
const wrapper = mountTrack({ showLanes: false })
|
|
50
|
+
expect(wrapper.text()).not.toContain('Outbound')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('renders both timeline lines in split view', () => {
|
|
54
|
+
const wrapper = mountTrack({ splitView: true })
|
|
55
|
+
expect(wrapper.find('.left-5').exists()).toBe(true)
|
|
56
|
+
expect(wrapper.find('.right-5').exists()).toBe(true)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('renders only the left timeline line when splitView is false', () => {
|
|
60
|
+
const wrapper = mountTrack({ splitView: false })
|
|
61
|
+
expect(wrapper.find('.left-5').exists()).toBe(true)
|
|
62
|
+
expect(wrapper.find('.right-5').exists()).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('offsets the lines below the lane headers when they are shown', () => {
|
|
66
|
+
const wrapper = mountTrack({ splitView: true })
|
|
67
|
+
expect(wrapper.find('.left-5').classes()).toContain('top-10')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('starts the lines at the top when there are no lane headers', () => {
|
|
71
|
+
const wrapper = mountTrack({ splitView: false })
|
|
72
|
+
expect(wrapper.find('.left-5').classes()).toContain('top-0')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('applies the color prop to the timeline lines', () => {
|
|
76
|
+
const wrapper = mountTrack({ color: 'border-purple-500' })
|
|
77
|
+
expect(wrapper.find('.left-5').classes()).toContain('border-purple-500')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('passes splitView down so inbound events align right', () => {
|
|
81
|
+
const wrapper = mountTrack({ splitView: true })
|
|
82
|
+
const inbound = wrapper.find('[data-event-source="inbound"]')
|
|
83
|
+
expect(inbound.classes()).toContain('justify-end')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('renders slot content instead of events when the default slot is used', () => {
|
|
87
|
+
const wrapper = mountTrack({}, { default: '<div class="custom">eigen inhoud</div>' })
|
|
88
|
+
expect(wrapper.find('.custom').exists()).toBe(true)
|
|
89
|
+
expect(wrapper.findAll('[data-event-date]')).toHaveLength(0)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import { ref, readonly } from 'vue'
|
|
3
|
+
import NexxtToaster from './Toaster.vue'
|
|
4
|
+
import NexxtButton from '../../atoms/Button/Button.vue'
|
|
5
|
+
import type { ToastItem } from '../../molecules/Toast/Toast.vue'
|
|
6
|
+
import type { ToasterPosition } from './Toaster.vue'
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
title: 'Components/Organisms/Toaster',
|
|
10
|
+
component: NexxtToaster,
|
|
11
|
+
} satisfies Meta<typeof NexxtToaster>
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof NexxtToaster>
|
|
14
|
+
|
|
15
|
+
const makeRender =
|
|
16
|
+
(position: ToasterPosition = 'bottom-right') =>
|
|
17
|
+
() => ({
|
|
18
|
+
components: { NexxtToaster, NexxtButton },
|
|
19
|
+
setup() {
|
|
20
|
+
const toasts = ref<ToastItem[]>([])
|
|
21
|
+
|
|
22
|
+
const add = (toast: Omit<ToastItem, 'id'>) =>
|
|
23
|
+
toasts.value.push({ ...toast, id: crypto.randomUUID() })
|
|
24
|
+
|
|
25
|
+
const remove = (id: string) => (toasts.value = toasts.value.filter((t) => t.id !== id))
|
|
26
|
+
|
|
27
|
+
return { toasts: readonly(toasts), add, remove, position }
|
|
28
|
+
},
|
|
29
|
+
template: `
|
|
30
|
+
<div style="height: 300px; display: flex; flex-wrap: wrap; gap: 8px; align-content: flex-start; padding: 16px;">
|
|
31
|
+
<NexxtButton variant="success" @click="add({ variant: 'success', title: 'Opgeslagen', message: 'Je wijzigingen zijn succesvol opgeslagen.', autoClose: 4000 })">
|
|
32
|
+
Success toast
|
|
33
|
+
</NexxtButton>
|
|
34
|
+
<NexxtButton variant="primary" @click="add({ variant: 'info', title: 'Informatie', message: 'Er is een nieuwe versie beschikbaar.', autoClose: 4000 })">
|
|
35
|
+
Info toast
|
|
36
|
+
</NexxtButton>
|
|
37
|
+
<NexxtButton variant="warning" @click="add({ variant: 'warning', title: 'Let op', message: 'Je sessie verloopt over 5 minuten.', autoClose: 4000 })">
|
|
38
|
+
Warning toast
|
|
39
|
+
</NexxtButton>
|
|
40
|
+
<NexxtButton variant="danger" @click="add({ variant: 'error', title: 'Fout opgetreden', message: 'De actie kon niet worden voltooid.' })">
|
|
41
|
+
Error toast
|
|
42
|
+
</NexxtButton>
|
|
43
|
+
</div>
|
|
44
|
+
<NexxtToaster :toasts="toasts" :position="position" @dismiss="remove" />
|
|
45
|
+
`,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
export const BottomRight: Story = { render: makeRender('bottom-right') }
|
|
49
|
+
export const BottomLeft: Story = { render: makeRender('bottom-left') }
|
|
50
|
+
export const TopRight: Story = { render: makeRender('top-right') }
|
|
51
|
+
export const TopLeft: Story = { render: makeRender('top-left') }
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils'
|
|
3
|
+
import NexxtToaster from './Toaster.vue'
|
|
4
|
+
import type { ToastItem } from '../../molecules/Toast/Toast.vue'
|
|
5
|
+
|
|
6
|
+
const toasts: ToastItem[] = [
|
|
7
|
+
{ id: '1', variant: 'success', title: 'Opgeslagen', message: 'Wijzigingen opgeslagen.' },
|
|
8
|
+
{ id: '2', variant: 'error', title: 'Fout', message: 'Er is iets misgegaan.' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
describe('NexxtToaster', () => {
|
|
12
|
+
it('renders a toast for each item in the list', () => {
|
|
13
|
+
const wrapper = mount(NexxtToaster, {
|
|
14
|
+
props: { toasts },
|
|
15
|
+
attachTo: document.body,
|
|
16
|
+
})
|
|
17
|
+
const rendered = document.querySelectorAll('[role="alert"], [role="status"]')
|
|
18
|
+
expect(rendered.length).toBe(2)
|
|
19
|
+
wrapper.unmount()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('renders nothing when toasts list is empty', () => {
|
|
23
|
+
const wrapper = mount(NexxtToaster, {
|
|
24
|
+
props: { toasts: [] },
|
|
25
|
+
attachTo: document.body,
|
|
26
|
+
})
|
|
27
|
+
const rendered = document.querySelectorAll('[role="alert"], [role="status"]')
|
|
28
|
+
expect(rendered.length).toBe(0)
|
|
29
|
+
wrapper.unmount()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('emits dismiss with the correct toast id when a toast is closed', async () => {
|
|
33
|
+
const wrapper = mount(NexxtToaster, {
|
|
34
|
+
props: { toasts },
|
|
35
|
+
attachTo: document.body,
|
|
36
|
+
})
|
|
37
|
+
const buttons = document.querySelectorAll('button[aria-label="Sluiten"]')
|
|
38
|
+
;(buttons[0] as HTMLElement).click()
|
|
39
|
+
await flushPromises()
|
|
40
|
+
expect(wrapper.emitted('dismiss')?.[0]).toEqual(['1'])
|
|
41
|
+
wrapper.unmount()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('applies bottom-right position classes by default', () => {
|
|
45
|
+
const wrapper = mount(NexxtToaster, {
|
|
46
|
+
props: { toasts: [] },
|
|
47
|
+
attachTo: document.body,
|
|
48
|
+
})
|
|
49
|
+
const container = document.querySelector('.fixed')
|
|
50
|
+
expect(container?.className).toContain('bottom-4')
|
|
51
|
+
expect(container?.className).toContain('right-4')
|
|
52
|
+
wrapper.unmount()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('applies top-left position classes when position is top-left', () => {
|
|
56
|
+
const wrapper = mount(NexxtToaster, {
|
|
57
|
+
props: { toasts: [], position: 'top-left' },
|
|
58
|
+
attachTo: document.body,
|
|
59
|
+
})
|
|
60
|
+
const container = document.querySelector('.fixed')
|
|
61
|
+
expect(container?.className).toContain('top-4')
|
|
62
|
+
expect(container?.className).toContain('left-4')
|
|
63
|
+
wrapper.unmount()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('teleports to body', () => {
|
|
67
|
+
const wrapper = mount(NexxtToaster, {
|
|
68
|
+
props: { toasts },
|
|
69
|
+
attachTo: document.body,
|
|
70
|
+
})
|
|
71
|
+
const container = document.body.querySelector('.fixed')
|
|
72
|
+
expect(container).not.toBeNull()
|
|
73
|
+
wrapper.unmount()
|
|
74
|
+
})
|
|
75
|
+
})
|