@nexxtmove/ui 0.1.31 → 0.1.32

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.
Files changed (54) hide show
  1. package/dist/index.d.ts +104 -17
  2. package/dist/index.js +132 -128
  3. package/package.json +1 -1
  4. package/src/components/Calendar/_CalendarDayView.vue +1 -1
  5. package/src/components/Chip/Chip.vue +9 -4
  6. package/src/components/Table/Table.vue +2 -2
  7. package/src/components/AnimatedNumber/AnimatedNumber.stories.ts +0 -15
  8. package/src/components/AnimatedNumber/AnimatedNumber.test.ts +0 -56
  9. package/src/components/Avatar/Avatar.stories.ts +0 -28
  10. package/src/components/Avatar/Avatar.test.ts +0 -55
  11. package/src/components/Button/Button.stories.ts +0 -212
  12. package/src/components/Button/Button.test.ts +0 -318
  13. package/src/components/Calendar/Calendar.stories.ts +0 -91
  14. package/src/components/Calendar/Calendar.test.ts +0 -279
  15. package/src/components/Calendar/_CalendarDayView.test.ts +0 -104
  16. package/src/components/Calendar/_CalendarHeader.test.ts +0 -86
  17. package/src/components/Calendar/_CalendarMonthView.test.ts +0 -52
  18. package/src/components/Calendar/_CalendarYearView.test.ts +0 -56
  19. package/src/components/Checkbox/Checkbox.stories.ts +0 -35
  20. package/src/components/Checkbox/Checkbox.test.ts +0 -55
  21. package/src/components/Chip/Chip.stories.ts +0 -42
  22. package/src/components/Chip/Chip.test.ts +0 -51
  23. package/src/components/CustomerJourneyTile/CustomerJourneyTile.stories.ts +0 -86
  24. package/src/components/CustomerJourneyTile/CustomerJourneyTile.test.ts +0 -178
  25. package/src/components/DatePicker/DatePicker.stories.ts +0 -149
  26. package/src/components/DatePicker/DatePicker.test.ts +0 -191
  27. package/src/components/DropdownButton/DropdownButton.stories.ts +0 -68
  28. package/src/components/DropdownButton/DropdownButton.test.ts +0 -124
  29. package/src/components/Header/Header.stories.ts +0 -54
  30. package/src/components/Header/Header.test.ts +0 -183
  31. package/src/components/Icon/Icon.stories.ts +0 -50
  32. package/src/components/Icon/Icon.test.ts +0 -73
  33. package/src/components/InfoBlock/InfoBlock.stories.ts +0 -90
  34. package/src/components/InfoBlock/InfoBlock.test.ts +0 -101
  35. package/src/components/ProgressBar/ProgressBar.stories.ts +0 -30
  36. package/src/components/ProgressBar/ProgressBar.test.ts +0 -314
  37. package/src/components/SocialIcons/SocialIcons.stories.ts +0 -34
  38. package/src/components/SocialIcons/SocialIcons.test.ts +0 -58
  39. package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +0 -11
  40. package/src/components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +0 -131
  41. package/src/components/SocialMediaTemplate/SocialMediaTemplate.stories.ts +0 -71
  42. package/src/components/SocialMediaTemplate/SocialMediaTemplate.test.ts +0 -466
  43. package/src/components/SocialMediaType/SocialMediaType.stories.ts +0 -43
  44. package/src/components/SocialMediaType/SocialMediaType.test.ts +0 -126
  45. package/src/components/StepperHeader/StepperHeader.stories.ts +0 -47
  46. package/src/components/StepperHeader/StepperHeader.test.ts +0 -244
  47. package/src/components/Table/Table.stories.ts +0 -283
  48. package/src/components/Table/Table.test.ts +0 -220
  49. package/src/components/TimelineEvent/TimelineEvent.stories.ts +0 -291
  50. package/src/components/TimelineEvent/TimelineEvent.test.ts +0 -166
  51. package/src/components/TimelinePhaseblock/TimelinePhaseblock.stories.ts +0 -198
  52. package/src/components/TimelinePhaseblock/TimelinePhaseblock.test.ts +0 -283
  53. package/src/components/Tooltip/Tooltip.stories.ts +0 -146
  54. package/src/components/Tooltip/Tooltip.test.ts +0 -122
@@ -1,220 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { mount } from '@vue/test-utils'
3
- import Table from './Table.vue'
4
-
5
- const columns = [
6
- { key: 'name', label: 'Name', sortable: true },
7
- { key: 'email', label: 'Email' },
8
- ]
9
-
10
- const rows = [
11
- { name: 'Alice', email: 'alice@example.com' },
12
- { name: 'Bob', email: 'bob@example.com' },
13
- { name: 'Carol', email: 'carol@example.com' },
14
- ]
15
-
16
- describe('Table', () => {
17
- describe('rendering', () => {
18
- it('renders column headers', () => {
19
- const wrapper = mount(Table, { props: { columns, rows } })
20
- expect(wrapper.text()).toContain('Name')
21
- expect(wrapper.text()).toContain('Email')
22
- })
23
-
24
- it('renders row data', () => {
25
- const wrapper = mount(Table, { props: { columns, rows } })
26
- expect(wrapper.text()).toContain('Alice')
27
- expect(wrapper.text()).toContain('bob@example.com')
28
- })
29
-
30
- it('renders empty slot when rows is empty', () => {
31
- const wrapper = mount(Table, { props: { columns, rows: [] } })
32
- expect(wrapper.text()).toContain('Geen data beschikbaar')
33
- })
34
-
35
- it('renders custom empty slot content', () => {
36
- const wrapper = mount(Table, {
37
- props: { columns, rows: [] },
38
- slots: { empty: 'Nothing here' },
39
- })
40
- expect(wrapper.text()).toContain('Nothing here')
41
- })
42
-
43
- it('renders cell slot content', () => {
44
- const wrapper = mount(Table, {
45
- props: { columns, rows },
46
- slots: { 'cell-name': '<strong>custom</strong>' },
47
- })
48
- expect(wrapper.html()).toContain('<strong>custom</strong>')
49
- })
50
- })
51
-
52
- describe('sorting', () => {
53
- it('renders sort icon on sortable columns', () => {
54
- const wrapper = mount(Table, { props: { columns, rows } })
55
- const headers = wrapper.findAll('[role="columnheader"]')
56
- const nameHeader = headers[0]
57
- expect(nameHeader.find('i').exists()).toBe(true)
58
- })
59
-
60
- it('does not render sort icon on non-sortable columns', () => {
61
- const wrapper = mount(Table, { props: { columns, rows } })
62
- const headers = wrapper.findAll('[role="columnheader"]')
63
- const emailHeader = headers[1]
64
- expect(emailHeader.find('i').exists()).toBe(false)
65
- })
66
-
67
- it('emits sort event when clicking a sortable header', async () => {
68
- const wrapper = mount(Table, { props: { columns, rows } })
69
- const nameHeader = wrapper.findAll('[role="columnheader"]')[0]
70
- await nameHeader.trigger('click')
71
- expect(wrapper.emitted('sort')).toBeTruthy()
72
- expect(wrapper.emitted('sort')![0]).toEqual(['name', 'asc'])
73
- })
74
-
75
- it('emits desc direction when clicking an already sorted column', async () => {
76
- const wrapper = mount(Table, {
77
- props: { columns, rows, sortKey: 'name', sortDirection: 'asc' },
78
- })
79
- const nameHeader = wrapper.findAll('[role="columnheader"]')[0]
80
- await nameHeader.trigger('click')
81
- expect(wrapper.emitted('sort')![0]).toEqual(['name', 'desc'])
82
- })
83
-
84
- it('does not emit sort when clicking a non-sortable header', async () => {
85
- const wrapper = mount(Table, { props: { columns, rows } })
86
- const emailHeader = wrapper.findAll('[role="columnheader"]')[1]
87
- await emailHeader.trigger('click')
88
- expect(wrapper.emitted('sort')).toBeFalsy()
89
- })
90
-
91
- it('sets aria-sort to ascending on the active sort column', () => {
92
- const wrapper = mount(Table, {
93
- props: { columns, rows, sortKey: 'name', sortDirection: 'asc' },
94
- })
95
- const nameHeader = wrapper.findAll('[role="columnheader"]')[0]
96
- expect(nameHeader.attributes('aria-sort')).toBe('ascending')
97
- })
98
-
99
- it('sets aria-sort to descending on the active sort column', () => {
100
- const wrapper = mount(Table, {
101
- props: { columns, rows, sortKey: 'name', sortDirection: 'desc' },
102
- })
103
- const nameHeader = wrapper.findAll('[role="columnheader"]')[0]
104
- expect(nameHeader.attributes('aria-sort')).toBe('descending')
105
- })
106
-
107
- it('sets aria-sort to none on an inactive sortable column', () => {
108
- const wrapper = mount(Table, { props: { columns, rows } })
109
- const nameHeader = wrapper.findAll('[role="columnheader"]')[0]
110
- expect(nameHeader.attributes('aria-sort')).toBe('none')
111
- })
112
- })
113
-
114
- describe('selection', () => {
115
- it('does not render checkboxes when selectable is false', () => {
116
- const wrapper = mount(Table, { props: { columns, rows } })
117
- expect(wrapper.find('input[type="checkbox"]').exists()).toBe(false)
118
- })
119
-
120
- it('renders a checkbox per row and one in the header when selectable', () => {
121
- const wrapper = mount(Table, {
122
- props: { columns, rows, selectable: true, modelValue: [] },
123
- })
124
- const checkboxes = wrapper.findAll('input[type="checkbox"]')
125
- expect(checkboxes).toHaveLength(rows.length + 1)
126
- })
127
-
128
- it('emits update:modelValue with all indices when select-all is clicked', async () => {
129
- const wrapper = mount(Table, {
130
- props: { columns, rows, selectable: true, modelValue: [] },
131
- })
132
- const headerCheckbox = wrapper.find('input[type="checkbox"]')
133
- await headerCheckbox.setValue(true)
134
- expect(wrapper.emitted('update:modelValue')![0]).toEqual([[0, 1, 2]])
135
- })
136
-
137
- it('emits update:modelValue with empty array when deselecting all', async () => {
138
- const wrapper = mount(Table, {
139
- props: { columns, rows, selectable: true, modelValue: [0, 1, 2] },
140
- })
141
- const headerCheckbox = wrapper.find('input[type="checkbox"]')
142
- await headerCheckbox.setValue(false)
143
- expect(wrapper.emitted('update:modelValue')![0]).toEqual([[]])
144
- })
145
-
146
- it('emits update:modelValue when a row checkbox is changed', async () => {
147
- const wrapper = mount(Table, {
148
- props: { columns, rows, selectable: true, modelValue: [] },
149
- })
150
- const rowCheckboxes = wrapper.findAll('input[type="checkbox"]').slice(1)
151
- await rowCheckboxes[0].setValue(true)
152
- expect(wrapper.emitted('update:modelValue')![0]).toEqual([[0]])
153
- })
154
-
155
- it('removes index from selection when an already-selected row checkbox is changed', async () => {
156
- const wrapper = mount(Table, {
157
- props: { columns, rows, selectable: true, modelValue: [0, 1] },
158
- })
159
- const rowCheckboxes = wrapper.findAll('input[type="checkbox"]').slice(1)
160
- await rowCheckboxes[0].setValue(false)
161
- expect(wrapper.emitted('update:modelValue')![0]).toEqual([[1]])
162
- })
163
- })
164
-
165
- describe('row click selection', () => {
166
- it('selects a row when clicking on it', async () => {
167
- const wrapper = mount(Table, {
168
- props: { columns, rows, selectable: true, modelValue: [] },
169
- })
170
- const dataRows = wrapper.findAll('[role="row"]').slice(1)
171
- await dataRows[0].trigger('click')
172
- expect(wrapper.emitted('update:modelValue')![0]).toEqual([[0]])
173
- })
174
-
175
- it('does not emit selection when clicking a button inside a row', async () => {
176
- const wrapper = mount(Table, {
177
- props: { columns, rows, selectable: true, modelValue: [] },
178
- slots: { 'cell-name': '<button>action</button>' },
179
- })
180
- const button = wrapper.find('button')
181
- await button.trigger('click')
182
- expect(wrapper.emitted('update:modelValue')).toBeFalsy()
183
- })
184
-
185
- it('does not emit selection when clicking a link inside a row', async () => {
186
- const wrapper = mount(Table, {
187
- props: { columns, rows, selectable: true, modelValue: [] },
188
- slots: { 'cell-name': '<a href="#">link</a>' },
189
- })
190
- const link = wrapper.find('a')
191
- await link.trigger('click')
192
- expect(wrapper.emitted('update:modelValue')).toBeFalsy()
193
- })
194
-
195
- it('does not select when selectable is false', async () => {
196
- const wrapper = mount(Table, { props: { columns, rows } })
197
- const dataRows = wrapper.findAll('[role="row"]').slice(1)
198
- await dataRows[0].trigger('click')
199
- expect(wrapper.emitted('update:modelValue')).toBeFalsy()
200
- })
201
- })
202
-
203
- describe('keyboard navigation', () => {
204
- it('moves focus to next row on ArrowDown', async () => {
205
- const wrapper = mount(Table, { props: { columns, rows }, attachTo: document.body })
206
- const dataRows = wrapper.findAll('[role="row"]').slice(1)
207
- await dataRows[0].trigger('keydown', { key: 'ArrowDown' })
208
- expect(document.activeElement).toBe(dataRows[1].element)
209
- wrapper.unmount()
210
- })
211
-
212
- it('moves focus to previous row on ArrowUp', async () => {
213
- const wrapper = mount(Table, { props: { columns, rows }, attachTo: document.body })
214
- const dataRows = wrapper.findAll('[role="row"]').slice(1)
215
- await dataRows[1].trigger('keydown', { key: 'ArrowUp' })
216
- expect(document.activeElement).toBe(dataRows[0].element)
217
- wrapper.unmount()
218
- })
219
- })
220
- })
@@ -1,291 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
- import TimelineEvent from './TimelineEvent.vue'
3
-
4
- export default {
5
- title: 'Components/Atoms/Timeline Event',
6
- component: TimelineEvent,
7
- } satisfies Meta<typeof TimelineEvent>
8
-
9
- type Story = StoryObj<typeof TimelineEvent>
10
-
11
- const baseEvent = {
12
- id: 1,
13
- happened_at: '2024-06-15T10:30:00.000Z',
14
- medium_type: null,
15
- url: null,
16
- page_title: null,
17
- request_reason: null,
18
- agenda_item_type: null,
19
- source: 'system',
20
- direction: 'outbound',
21
- description: null,
22
- }
23
-
24
- export const ContactCreated: Story = {
25
- args: {
26
- event: {
27
- ...baseEvent,
28
- type: 'contact_created',
29
- },
30
- splitView: true,
31
- },
32
- }
33
-
34
- export const InboundEvent: Story = {
35
- args: {
36
- event: {
37
- ...baseEvent,
38
- type: 'contact_requested',
39
- direction: 'inbound',
40
- },
41
- splitView: true,
42
- },
43
- }
44
-
45
- export const PageViewed: Story = {
46
- args: {
47
- event: {
48
- ...baseEvent,
49
- type: 'page_viewed',
50
- url: 'https://example.com/page',
51
- page_title: 'Example Page',
52
- description: 'Example Page',
53
- },
54
- splitView: true,
55
- },
56
- }
57
-
58
- export const WhatsAppMessageReceived: Story = {
59
- args: {
60
- event: {
61
- ...baseEvent,
62
- type: 'whatsapp_message_received',
63
- },
64
- splitView: true,
65
- },
66
- }
67
-
68
- export const WhatsAppMessageSent: Story = {
69
- args: {
70
- event: {
71
- ...baseEvent,
72
- type: 'whatsapp_message_sent',
73
- direction: 'outbound',
74
- },
75
- splitView: true,
76
- },
77
- }
78
-
79
- export const WithUrl: Story = {
80
- args: {
81
- event: {
82
- ...baseEvent,
83
- type: 'proposal_sent',
84
- url: 'https://example.com/proposal/123',
85
- description: 'Voorstel Q2 2024',
86
- },
87
- splitView: true,
88
- },
89
- }
90
-
91
- export const WithRequestReason: Story = {
92
- args: {
93
- event: {
94
- ...baseEvent,
95
- type: 'house_viewing_requested',
96
- request_reason: 'Interesse in de woning na open dag',
97
- },
98
- splitView: true,
99
- },
100
- }
101
-
102
- export const SplitViewDisabled: Story = {
103
- args: {
104
- event: {
105
- ...baseEvent,
106
- type: 'contact_created',
107
- direction: 'inbound',
108
- },
109
- splitView: false,
110
- },
111
- }
112
-
113
- export const ViewingAgendaItemScheduled: Story = {
114
- args: {
115
- event: {
116
- ...baseEvent,
117
- type: 'viewing_agenda_item_scheduled',
118
- },
119
- splitView: true,
120
- },
121
- }
122
-
123
- export const ProposalAccepted: Story = {
124
- args: {
125
- event: {
126
- ...baseEvent,
127
- type: 'proposal_accepted',
128
- },
129
- splitView: true,
130
- },
131
- }
132
-
133
- export const KeyHandoverScheduled: Story = {
134
- args: {
135
- event: {
136
- ...baseEvent,
137
- type: 'key_handover_agenda_item_scheduled',
138
- },
139
- splitView: true,
140
- },
141
- }
142
-
143
- export const AutomationPhaseOpened: Story = {
144
- args: {
145
- event: { ...baseEvent, type: 'automation_phase_opened' },
146
- splitView: true,
147
- },
148
- }
149
-
150
- export const AutomationWhatsAppButtonClicked: Story = {
151
- args: {
152
- event: { ...baseEvent, type: 'automation_whatsapp_button_clicked' },
153
- splitView: true,
154
- },
155
- }
156
-
157
- export const AutomationButtonMailAnswered: Story = {
158
- args: {
159
- event: { ...baseEvent, type: 'automation_button_mail_answered' },
160
- splitView: true,
161
- },
162
- }
163
-
164
- export const WaitingListSignup: Story = {
165
- args: {
166
- event: { ...baseEvent, type: 'waiting_list_signup' },
167
- splitView: true,
168
- },
169
- }
170
-
171
- export const ViewingCreated: Story = {
172
- args: {
173
- event: { ...baseEvent, type: 'viewing_created' },
174
- splitView: true,
175
- },
176
- }
177
-
178
- export const GeneralAgendaItemScheduled: Story = {
179
- args: {
180
- event: { ...baseEvent, type: 'general_agenda_item_scheduled' },
181
- splitView: true,
182
- },
183
- }
184
-
185
- export const SalesAgendaItemScheduled: Story = {
186
- args: {
187
- event: { ...baseEvent, type: 'sales_agenda_item_scheduled' },
188
- splitView: true,
189
- },
190
- }
191
-
192
- export const AgendaItemScheduled: Story = {
193
- args: {
194
- event: { ...baseEvent, type: 'agenda_item_scheduled' },
195
- splitView: true,
196
- },
197
- }
198
-
199
- export const AppraisalAgendaItemScheduled: Story = {
200
- args: {
201
- event: { ...baseEvent, type: 'appraisal_agenda_item_scheduled' },
202
- splitView: true,
203
- },
204
- }
205
-
206
- export const PurchaseAgendaItemScheduled: Story = {
207
- args: {
208
- event: { ...baseEvent, type: 'purchase_agenda_item_scheduled' },
209
- splitView: true,
210
- },
211
- }
212
-
213
- export const StructuralInspectionAgendaItemScheduled: Story = {
214
- args: {
215
- event: { ...baseEvent, type: 'structural_inspection_agenda_item_scheduled' },
216
- splitView: true,
217
- },
218
- }
219
-
220
- export const ValuationAgendaItemScheduled: Story = {
221
- args: {
222
- event: { ...baseEvent, type: 'valuation_agenda_item_scheduled' },
223
- splitView: true,
224
- },
225
- }
226
-
227
- export const MortgageAgendaItemScheduled: Story = {
228
- args: {
229
- event: { ...baseEvent, type: 'mortgage_agenda_item_scheduled' },
230
- splitView: true,
231
- },
232
- }
233
-
234
- export const PurchaseDeedSigningAgendaItemScheduled: Story = {
235
- args: {
236
- event: { ...baseEvent, type: 'purchase_deed_signing_agenda_item_scheduled' },
237
- splitView: true,
238
- },
239
- }
240
-
241
- export const ProposalOpened: Story = {
242
- args: {
243
- event: { ...baseEvent, type: 'proposal_opened' },
244
- splitView: true,
245
- },
246
- }
247
-
248
- export const FinalInspectionAgendaItemScheduled: Story = {
249
- args: {
250
- event: { ...baseEvent, type: 'final_inspection_agenda_item_scheduled' },
251
- splitView: true,
252
- },
253
- }
254
-
255
- export const TransportAgendaItemScheduled: Story = {
256
- args: {
257
- event: { ...baseEvent, type: 'transport_agenda_item_scheduled' },
258
- splitView: true,
259
- },
260
- }
261
-
262
- export const NewsletterOpened: Story = {
263
- args: {
264
- event: { ...baseEvent, type: 'newsletter_opened' },
265
- splitView: true,
266
- },
267
- }
268
-
269
- export const HouseValuationRequested: Story = {
270
- args: {
271
- event: { ...baseEvent, type: 'house_valuation_requested' },
272
- splitView: true,
273
- },
274
- }
275
-
276
- export const Unsubscribed: Story = {
277
- args: {
278
- event: { ...baseEvent, type: 'unsubscribed' },
279
- splitView: true,
280
- },
281
- }
282
-
283
- export const UnknownEventType: Story = {
284
- args: {
285
- event: {
286
- ...baseEvent,
287
- type: 'custom_event_type',
288
- },
289
- splitView: true,
290
- },
291
- }
@@ -1,166 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { mount } from '@vue/test-utils'
3
- import TimelineEvent from './TimelineEvent.vue'
4
-
5
- const baseEvent = {
6
- id: 1,
7
- happened_at: '2024-06-15T10:30:00.000Z',
8
- medium_type: null,
9
- url: null,
10
- page_title: null,
11
- request_reason: null,
12
- agenda_item_type: null,
13
- source: 'system',
14
- direction: 'outbound',
15
- description: null,
16
- }
17
-
18
- const mountEvent = (eventOverrides = {}, props = {}) =>
19
- mount(TimelineEvent, {
20
- props: {
21
- event: { ...baseEvent, type: 'contact_created', ...eventOverrides },
22
- ...props,
23
- },
24
- global: {
25
- stubs: {
26
- NexxtIcon: true,
27
- },
28
- },
29
- })
30
-
31
- describe('TimelineEvent', () => {
32
- it('renders the formatted date', () => {
33
- const wrapper = mountEvent()
34
- const dateText = wrapper.find('.extra-small-normal').text()
35
- expect(dateText).toContain('om')
36
- })
37
-
38
- it('sets data-event-source attribute from direction', () => {
39
- const wrapper = mountEvent({ direction: 'inbound' })
40
- expect(wrapper.attributes('data-event-source')).toBe('inbound')
41
- })
42
-
43
- it('sets data-event-date attribute from happened_at', () => {
44
- const wrapper = mountEvent({ happened_at: '2024-06-15T10:30:00.000Z' })
45
- expect(wrapper.attributes('data-event-date')).toBe('2024-06-15T10:30:00.000Z')
46
- })
47
-
48
- it('sets date-event-type attribute from type', () => {
49
- const wrapper = mountEvent({ type: 'page_viewed' })
50
- expect(wrapper.attributes('date-event-type')).toBe('page_viewed')
51
- })
52
-
53
- it('renders the event label for a known event type', () => {
54
- const wrapper = mountEvent({ type: 'contact_created' })
55
- expect(wrapper.text()).toContain('Contact aangemaakt')
56
- })
57
-
58
- it('renders the event type as label for an unknown event type', () => {
59
- const wrapper = mountEvent({ type: 'custom_unknown_type' })
60
- expect(wrapper.text()).toContain('custom_unknown_type')
61
- })
62
-
63
- it('does not render a link when event.url is null', () => {
64
- const wrapper = mountEvent({ url: null })
65
- expect(wrapper.find('a').exists()).toBe(false)
66
- })
67
-
68
- it('renders a link when event.url is set', () => {
69
- const wrapper = mountEvent({ url: 'https://example.com' })
70
- const link = wrapper.find('a')
71
- expect(link.exists()).toBe(true)
72
- expect(link.attributes('href')).toBe('https://example.com')
73
- expect(link.attributes('target')).toBe('_blank')
74
- })
75
-
76
- it('renders event.description as link text when set', () => {
77
- const wrapper = mountEvent({ url: 'https://example.com', description: 'Mijn voorstel' })
78
- expect(wrapper.find('a').text()).toContain('Mijn voorstel')
79
- })
80
-
81
- it('renders event.url as link text when description is null', () => {
82
- const wrapper = mountEvent({ url: 'https://example.com', description: null })
83
- expect(wrapper.find('a').text()).toContain('https://example.com')
84
- })
85
-
86
- it('does not render request_reason when not set', () => {
87
- const wrapper = mountEvent({ request_reason: null })
88
- expect(wrapper.text()).not.toContain('Reden:')
89
- })
90
-
91
- it('renders request_reason when set', () => {
92
- const wrapper = mountEvent({ request_reason: 'Interesse in de woning' })
93
- expect(wrapper.text()).toContain('Reden: Interesse in de woning')
94
- })
95
-
96
- it('aligns outbound events to the left by default (no justify-end)', () => {
97
- const wrapper = mountEvent({ direction: 'outbound' }, { splitView: true })
98
- expect(wrapper.classes()).not.toContain('justify-end')
99
- })
100
-
101
- it('aligns inbound events to the right when splitView is true', () => {
102
- const wrapper = mountEvent({ direction: 'inbound' }, { splitView: true })
103
- expect(wrapper.classes()).toContain('justify-end')
104
- })
105
-
106
- it('does not align inbound events to the right when splitView is false', () => {
107
- const wrapper = mountEvent({ direction: 'inbound' }, { splitView: false })
108
- expect(wrapper.classes()).not.toContain('justify-end')
109
- })
110
-
111
- it('reverses icon and content order for inbound events in splitView', () => {
112
- const wrapper = mountEvent({ direction: 'inbound' }, { splitView: true })
113
- const innerRow = wrapper.find('.flex.w-full.shrink-0')
114
- expect(innerRow.classes()).toContain('flex-row-reverse')
115
- })
116
-
117
- it('does not reverse order for outbound events', () => {
118
- const wrapper = mountEvent({ direction: 'outbound' }, { splitView: true })
119
- const innerRow = wrapper.find('.flex.w-full.shrink-0')
120
- expect(innerRow.classes()).not.toContain('flex-row-reverse')
121
- })
122
-
123
- it('applies max-w-lg to content when splitView is true', () => {
124
- const wrapper = mountEvent({}, { splitView: true })
125
- const content = wrapper.find('.rounded-xl')
126
- expect(content.classes()).toContain('max-w-lg')
127
- })
128
-
129
- it('does not apply max-w-lg to content when splitView is false', () => {
130
- const wrapper = mountEvent({}, { splitView: false })
131
- const content = wrapper.find('.rounded-xl')
132
- expect(content.classes()).not.toContain('max-w-lg')
133
- })
134
-
135
- it('defaults splitView to true', () => {
136
- const wrapper = mount(TimelineEvent, {
137
- props: {
138
- event: { ...baseEvent, type: 'contact_created', direction: 'inbound' },
139
- },
140
- global: { stubs: { NexxtIcon: true } },
141
- })
142
- expect(wrapper.classes()).toContain('justify-end')
143
- })
144
-
145
- it('applies correct background color class for known event type', () => {
146
- const wrapper = mountEvent({ type: 'contact_created' })
147
- const iconWrapper = wrapper.find('.rounded-full')
148
- expect(iconWrapper.classes()).toContain('bg-pink-500')
149
- })
150
-
151
- it('applies default background color for unknown event type', () => {
152
- const wrapper = mountEvent({ type: 'unknown_type' })
153
- const iconWrapper = wrapper.find('.rounded-full')
154
- expect(iconWrapper.classes()).toContain('bg-gray-500')
155
- })
156
-
157
- it('aligns date text to the right for inbound events in splitView', () => {
158
- const wrapper = mountEvent({ direction: 'inbound' }, { splitView: true })
159
- expect(wrapper.find('.extra-small-normal').classes()).toContain('text-right')
160
- })
161
-
162
- it('does not align date text to the right for outbound events', () => {
163
- const wrapper = mountEvent({ direction: 'outbound' }, { splitView: true })
164
- expect(wrapper.find('.extra-small-normal').classes()).not.toContain('text-right')
165
- })
166
- })