picocrank 1.31.1 → 1.32.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.
@@ -0,0 +1,253 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { nextTick } from 'vue'
4
+ import Table from './Table.vue'
5
+
6
+ const globalStubs = {
7
+ HugeiconsIcon: true,
8
+ Pagination: true,
9
+ TableColumnFilterPopover: true,
10
+ TableColumnOptionsPopover: true,
11
+ }
12
+
13
+ const headers = [
14
+ { key: 'name', label: 'Name', sortable: true },
15
+ { key: 'city', label: 'City' },
16
+ ]
17
+
18
+ const rows = [
19
+ { name: 'Ada', city: 'London' },
20
+ { name: 'Grace', city: 'New York' },
21
+ { name: 'Katherine', city: 'Newport News' },
22
+ ]
23
+
24
+ function mountTable(overrides = {}) {
25
+ return mount(Table, {
26
+ attachTo: document.body,
27
+ props: {
28
+ headers,
29
+ data: rows,
30
+ rowKey: 'name',
31
+ showPagination: false,
32
+ filterable: false,
33
+ columnOptions: false,
34
+ selectable: true,
35
+ ...overrides.props,
36
+ },
37
+ global: {
38
+ stubs: globalStubs,
39
+ },
40
+ })
41
+ }
42
+
43
+ describe('Table multi-select', () => {
44
+ it('renders a select column with header and row checkboxes when selectable', () => {
45
+ const wrapper = mountTable()
46
+
47
+ expect(wrapper.findAll('th.table-select-col')).toHaveLength(1)
48
+ expect(wrapper.findAll('td.table-select-col input[type="checkbox"]')).toHaveLength(rows.length)
49
+ })
50
+
51
+ it('does not render select checkboxes when selectable is false', () => {
52
+ const wrapper = mountTable({
53
+ props: {
54
+ selectable: false,
55
+ },
56
+ })
57
+
58
+ expect(wrapper.find('th.table-select-col').exists()).toBe(false)
59
+ expect(wrapper.find('td.table-select-col').exists()).toBe(false)
60
+ })
61
+
62
+ it('toggles a row via its checkbox and emits selection updates', async () => {
63
+ const wrapper = mountTable()
64
+
65
+ const firstCheckbox = wrapper.findAll('td.table-select-col input[type="checkbox"]')[0]
66
+ await firstCheckbox.setValue(true)
67
+ await nextTick()
68
+
69
+ expect(wrapper.emitted('update:selectedKeys')?.at(-1)?.[0]).toEqual(['Ada'])
70
+ expect(wrapper.emitted('selection-change')?.at(-1)?.[0]).toEqual(['Ada'])
71
+ expect(wrapper.find('tbody tr.row-selected').exists()).toBe(true)
72
+ })
73
+
74
+ it('toggles selection when a row is clicked', async () => {
75
+ const wrapper = mountTable()
76
+
77
+ await wrapper.findAll('tbody tr')[1].trigger('click')
78
+ await nextTick()
79
+
80
+ expect(wrapper.emitted('update:selectedKeys')?.at(-1)?.[0]).toEqual(['Grace'])
81
+ })
82
+
83
+ it('selects and clears the current page via the header checkbox', async () => {
84
+ const wrapper = mountTable()
85
+ const headerCheckbox = wrapper.find('th.table-select-col input[type="checkbox"]')
86
+
87
+ await headerCheckbox.setValue(true)
88
+ await nextTick()
89
+
90
+ expect(wrapper.emitted('update:selectedKeys')?.at(-1)?.[0]).toEqual([
91
+ 'Ada',
92
+ 'Grace',
93
+ 'Katherine',
94
+ ])
95
+
96
+ await headerCheckbox.setValue(false)
97
+ await nextTick()
98
+
99
+ expect(wrapper.emitted('update:selectedKeys')?.at(-1)?.[0]).toEqual([])
100
+ })
101
+
102
+ it('supports controlled selectedKeys via v-model', async () => {
103
+ const wrapper = mountTable({
104
+ props: {
105
+ selectedKeys: ['Grace'],
106
+ },
107
+ })
108
+
109
+ await nextTick()
110
+
111
+ const checkboxes = wrapper.findAll('td.table-select-col input[type="checkbox"]')
112
+ expect(checkboxes[1].element.checked).toBe(true)
113
+ expect(wrapper.findAll('tbody tr.row-selected')).toHaveLength(1)
114
+
115
+ await wrapper.setProps({ selectedKeys: ['Ada', 'Katherine'] })
116
+ await nextTick()
117
+
118
+ expect(wrapper.findAll('tbody tr.row-selected')).toHaveLength(2)
119
+ })
120
+
121
+ it('does not emit row-click while selectable; row clicks select instead', async () => {
122
+ const wrapper = mountTable({
123
+ props: {
124
+ onRowClick: () => {},
125
+ },
126
+ })
127
+
128
+ await wrapper.findAll('tbody tr')[0].trigger('click')
129
+ await nextTick()
130
+
131
+ expect(wrapper.emitted('row-click')).toBeUndefined()
132
+ expect(wrapper.emitted('update:selectedKeys')?.at(-1)?.[0]).toEqual(['Ada'])
133
+ })
134
+
135
+ it('clears selection when selectable is turned off', async () => {
136
+ const wrapper = mountTable()
137
+
138
+ await wrapper.findAll('tbody tr')[0].trigger('click')
139
+ await nextTick()
140
+ expect(wrapper.emitted('update:selectedKeys')?.at(-1)?.[0]).toEqual(['Ada'])
141
+
142
+ await wrapper.setProps({ selectable: false })
143
+ await nextTick()
144
+
145
+ expect(wrapper.emitted('update:selectedKeys')?.at(-1)?.[0]).toEqual([])
146
+ })
147
+ })
148
+
149
+ describe('Table row context menu', () => {
150
+ it('opens a menu and runs the item action for the clicked row', async () => {
151
+ const action = vi.fn()
152
+ const wrapper = mountTable({
153
+ props: {
154
+ selectable: false,
155
+ rowContextMenu: [
156
+ { id: 'export', label: 'Export', action },
157
+ ],
158
+ },
159
+ })
160
+
161
+ await wrapper.findAll('tbody tr')[0].trigger('contextmenu', {
162
+ clientX: 40,
163
+ clientY: 60,
164
+ })
165
+ await nextTick()
166
+
167
+ const menu = document.body.querySelector('.table-row-context-menu')
168
+ expect(menu).toBeTruthy()
169
+
170
+ menu.querySelector('[role="menuitem"]').click()
171
+ await nextTick()
172
+
173
+ expect(action).toHaveBeenCalledTimes(1)
174
+ expect(action.mock.calls[0][0].keys).toEqual(['Ada'])
175
+ expect(action.mock.calls[0][0].rows.map((row) => row.name)).toEqual(['Ada'])
176
+ expect(wrapper.emitted('row-context-menu-action')?.at(-1)?.[0].keys).toEqual(['Ada'])
177
+ })
178
+
179
+ it('runs the action against all selected rows when right-clicking inside the selection', async () => {
180
+ const action = vi.fn()
181
+ const wrapper = mountTable({
182
+ props: {
183
+ selectable: true,
184
+ selectedKeys: ['Ada', 'Grace'],
185
+ rowContextMenu: [
186
+ { id: 'export', label: 'Export', action },
187
+ ],
188
+ },
189
+ })
190
+
191
+ await wrapper.findAll('tbody tr')[1].trigger('contextmenu', {
192
+ clientX: 40,
193
+ clientY: 60,
194
+ })
195
+ await nextTick()
196
+
197
+ document.body.querySelector('.table-row-context-menu [role="menuitem"]').click()
198
+ await nextTick()
199
+
200
+ expect(action.mock.calls[0][0].keys).toEqual(['Ada', 'Grace'])
201
+ expect(action.mock.calls[0][0].rows.map((row) => row.name)).toEqual(['Ada', 'Grace'])
202
+ })
203
+
204
+ it('replaces selection with the clicked row when right-clicking outside the selection', async () => {
205
+ const action = vi.fn()
206
+ const wrapper = mountTable({
207
+ props: {
208
+ selectable: true,
209
+ selectedKeys: ['Ada', 'Grace'],
210
+ rowContextMenu: [
211
+ { id: 'export', label: 'Export', action },
212
+ ],
213
+ },
214
+ })
215
+
216
+ await wrapper.findAll('tbody tr')[2].trigger('contextmenu', {
217
+ clientX: 40,
218
+ clientY: 60,
219
+ })
220
+ await nextTick()
221
+
222
+ expect(wrapper.emitted('update:selectedKeys')?.at(-1)?.[0]).toEqual(['Katherine'])
223
+
224
+ document.body.querySelector('.table-row-context-menu [role="menuitem"]').click()
225
+ await nextTick()
226
+
227
+ expect(action.mock.calls[0][0].keys).toEqual(['Katherine'])
228
+ })
229
+
230
+ it('keeps a hover class on target rows while the menu is open', async () => {
231
+ const wrapper = mountTable({
232
+ props: {
233
+ selectable: false,
234
+ rowContextMenu: [
235
+ { id: 'export', label: 'Export', action: () => {} },
236
+ ],
237
+ },
238
+ })
239
+
240
+ await wrapper.findAll('tbody tr')[0].trigger('contextmenu', {
241
+ clientX: 40,
242
+ clientY: 60,
243
+ })
244
+ await nextTick()
245
+
246
+ expect(wrapper.findAll('tbody tr')[0].classes()).toContain('row-context-menu-active')
247
+
248
+ document.body.querySelector('.table-row-context-menu [role="menuitem"]').click()
249
+ await nextTick()
250
+
251
+ expect(wrapper.findAll('tbody tr')[0].classes()).not.toContain('row-context-menu-active')
252
+ })
253
+ })