@pequity/squirrel 10.0.2 → 10.1.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/README.md +20 -1
- package/dist/cjs/chunks/p-action-bar.js +17 -14
- package/dist/cjs/chunks/p-date-picker.js +3 -1
- package/dist/cjs/chunks/p-dropdown-select.js +27 -26
- package/dist/cjs/chunks/p-inline-date-picker.js +3 -1
- package/dist/cjs/chunks/p-pagination-info.js +2 -2
- package/dist/cjs/chunks/p-pagination.js +13 -11
- package/dist/cjs/chunks/p-tabs-pills.js +8 -8
- package/dist/cjs/index.js +101 -48
- package/dist/cjs/p-drawer.js +4 -4
- package/dist/cjs/p-icon.js +1 -0
- package/dist/cjs/p-input-search.js +5 -4
- package/dist/cjs/p-modal.js +3 -3
- package/dist/es/chunks/p-action-bar.js +18 -15
- package/dist/es/chunks/p-date-picker.js +3 -1
- package/dist/es/chunks/p-dropdown-select.js +27 -26
- package/dist/es/chunks/p-inline-date-picker.js +3 -1
- package/dist/es/chunks/p-pagination-info.js +2 -2
- package/dist/es/chunks/p-pagination.js +13 -11
- package/dist/es/chunks/p-tabs-pills.js +8 -8
- package/dist/es/index.js +102 -49
- package/dist/es/p-drawer.js +4 -4
- package/dist/es/p-icon.js +1 -0
- package/dist/es/p-input-search.js +5 -4
- package/dist/es/p-modal.js +3 -3
- package/dist/squirrel/components/p-btn/p-btn.vue.d.ts +2 -2
- package/dist/squirrel/components/p-icon/p-icon.types.d.ts +1 -0
- package/dist/squirrel/index.d.ts +1 -0
- package/dist/squirrel/plugin/index.d.ts +11 -0
- package/dist/squirrel.css +40 -40
- package/package.json +28 -25
- package/squirrel/components/p-action-bar/p-action-bar.vue +4 -1
- package/squirrel/components/p-btn/p-btn.spec.js +0 -1
- package/squirrel/components/p-checkbox/p-checkbox.stories.js +2 -2
- package/squirrel/components/p-date-picker/p-date-picker.vue +3 -2
- package/squirrel/components/p-drawer/p-drawer.spec.js +364 -0
- package/squirrel/components/p-drawer/p-drawer.vue +8 -2
- package/squirrel/components/p-dropdown/p-dropdown.spec.js +252 -55
- package/squirrel/components/p-dropdown-select/p-dropdown-select.vue +16 -12
- package/squirrel/components/p-file-upload/p-file-upload.spec.js +0 -1
- package/squirrel/components/p-file-upload/p-file-upload.vue +26 -9
- package/squirrel/components/p-icon/p-icon.types.ts +1 -0
- package/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue +3 -1
- package/squirrel/components/p-input-search/p-input-search.vue +2 -2
- package/squirrel/components/p-modal/p-modal-features.spec.js +10 -10
- package/squirrel/components/p-modal/p-modal.vue +1 -1
- package/squirrel/components/p-pagination/p-pagination.vue +3 -3
- package/squirrel/components/p-pagination-info/p-pagination-info.vue +2 -2
- package/squirrel/components/p-progress-bar/{p-progess-bar.spec.js → p-progress-bar.spec.js} +7 -5
- package/squirrel/components/p-select-btn/p-select-btn.spec.js +104 -0
- package/squirrel/components/p-select-list/p-select-list.vue +7 -5
- package/squirrel/components/p-select-pill/p-select-pill.spec.js +114 -0
- package/squirrel/components/p-table/usePTableColResize.spec.js +123 -11
- package/squirrel/components/p-table/usePTableHeaderWrap.spec.js +1 -1
- package/squirrel/components/p-table/usePTableRowVirtualizer.spec.js +207 -0
- package/squirrel/components/p-table-header-cell/p-table-header-cell.stories.js +3 -0
- package/squirrel/components/p-table-sort/p-table-sort.vue +4 -4
- package/squirrel/components/p-tabs-pills/p-tabs-pills.vue +1 -1
- package/squirrel/index.spec.js +5 -0
- package/squirrel/index.ts +1 -0
- package/squirrel/locales/en-US.json +47 -0
- package/squirrel/locales/fr-CA.json +47 -0
- package/squirrel/plugin/index.spec.ts +140 -0
- package/squirrel/plugin/index.ts +54 -0
- package/squirrel/utils/listKeyboardNavigation.spec.js +58 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div v-if="!loading && pages.length > 1" class="flex text-p-gray-40">
|
|
4
4
|
<div
|
|
5
5
|
:class="[BTN_CLASS, Number(modelValue) <= 1 ? ARROW_INACTIVE_CLASS : ARROW_ACTIVE_CLASS]"
|
|
6
|
-
aria-label="
|
|
6
|
+
:aria-label="$t('squirrel.pagination_go_to_previous_page')"
|
|
7
7
|
@click="setPage(Number(modelValue) - 1)"
|
|
8
8
|
>
|
|
9
9
|
<PIcon icon="chevron-left" width="24px" />
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<div
|
|
14
14
|
v-if="page !== DOTS"
|
|
15
15
|
:class="[BTN_CLASS, page === modelValue ? BTN_ACTIVE_CLASS : BTN_INACTIVE_CLASS]"
|
|
16
|
-
:aria-label="
|
|
16
|
+
:aria-label="$t('squirrel.pagination_go_to_page', { page })"
|
|
17
17
|
>
|
|
18
18
|
{{ page }}
|
|
19
19
|
</div>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
<div
|
|
24
24
|
:class="[BTN_CLASS, modelValue === pageCount ? ARROW_INACTIVE_CLASS : ARROW_ACTIVE_CLASS]"
|
|
25
|
-
aria-label="
|
|
25
|
+
:aria-label="$t('squirrel.pagination_go_to_next_page')"
|
|
26
26
|
@click="setPage(Number(modelValue) + 1)"
|
|
27
27
|
>
|
|
28
28
|
<PIcon icon="chevron-right" width="24px" />
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<PSkeletonLoader v-if="loading" class="h-6 w-56" />
|
|
3
3
|
<div v-else class="text-sm font-medium text-p-gray-40">
|
|
4
|
-
<template v-if="count">
|
|
5
|
-
<slot v-else name="no-results">
|
|
4
|
+
<template v-if="count">{{ $t('squirrel.pagination_info_showing_results', { from, to, count }) }}</template>
|
|
5
|
+
<slot v-else name="no-results">{{ $t('squirrel.pagination_info_no_results_found') }}</slot>
|
|
6
6
|
</div>
|
|
7
7
|
</template>
|
|
8
8
|
|
|
@@ -2,20 +2,22 @@ import PProgressBar from '@squirrel/components/p-progress-bar/p-progress-bar.vue
|
|
|
2
2
|
import { createWrapperFor } from '@tests/vitest.helpers';
|
|
3
3
|
|
|
4
4
|
const items = [
|
|
5
|
-
{
|
|
6
|
-
{
|
|
5
|
+
{ value: 50, label: 'bar1', color: 'rgb(204, 204, 204)' },
|
|
6
|
+
{ value: 30, label: 'bar2', color: 'rgb(221, 221, 221)' },
|
|
7
7
|
];
|
|
8
8
|
|
|
9
9
|
describe('PProgressBar.vue', () => {
|
|
10
10
|
it('renders correctly', async () => {
|
|
11
|
-
const wrapper = createWrapperFor(PProgressBar, { props: { total:
|
|
11
|
+
const wrapper = createWrapperFor(PProgressBar, { props: { total: 100, items } });
|
|
12
12
|
|
|
13
13
|
const div = wrapper.find('div[role="progressbar"]');
|
|
14
14
|
|
|
15
15
|
const bars = div.findAll('div.h-full');
|
|
16
16
|
|
|
17
17
|
bars.forEach((bar, i) => {
|
|
18
|
-
|
|
18
|
+
const expectedWidth = `${(items[i].value / 100) * 100}%`;
|
|
19
|
+
expect(bar.attributes('style')).toContain(`width: ${expectedWidth}`);
|
|
20
|
+
expect(bar.attributes('style')).toContain(`background: ${items[i].color}`);
|
|
19
21
|
});
|
|
20
22
|
|
|
21
23
|
expect(div.classes()).toEqual(['flex', 'justify-start', 'overflow-hidden', 'rounded', 'bg-p-blue-20']);
|
|
@@ -24,7 +26,7 @@ describe('PProgressBar.vue', () => {
|
|
|
24
26
|
it('attrs fall through', async () => {
|
|
25
27
|
const ParentComponent = {
|
|
26
28
|
template: `
|
|
27
|
-
<PProgressBar :total="
|
|
29
|
+
<PProgressBar :total="100" :items="items" class="test-class" data-testattr="test attribute" />
|
|
28
30
|
`,
|
|
29
31
|
data() {
|
|
30
32
|
return {
|
|
@@ -283,4 +283,108 @@ describe('PSelectBtn.vue', () => {
|
|
|
283
283
|
|
|
284
284
|
expect(true).toBe(true);
|
|
285
285
|
});
|
|
286
|
+
|
|
287
|
+
describe('Multiple selection', () => {
|
|
288
|
+
it('allows multiple items to be selected', async () => {
|
|
289
|
+
const wrapper = createWrapperFor(PSelectBtn, {
|
|
290
|
+
props: {
|
|
291
|
+
items,
|
|
292
|
+
itemValue: 'valueCustom',
|
|
293
|
+
itemText: 'textCustom',
|
|
294
|
+
modelValue: [items[0], items[1]], // Select first two items
|
|
295
|
+
multiple: true,
|
|
296
|
+
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Check that first two buttons are selected
|
|
301
|
+
const buttons = await wrapper.findAll('button');
|
|
302
|
+
expect(buttons[0].attributes()['aria-selected']).toBe('true');
|
|
303
|
+
expect(buttons[1].attributes()['aria-selected']).toBe('true');
|
|
304
|
+
expect(buttons[2].attributes()['aria-selected']).toBe('false');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('adds items to selection when clicked in multiple mode', async () => {
|
|
308
|
+
const wrapper = createWrapperFor(PSelectBtn, {
|
|
309
|
+
props: {
|
|
310
|
+
items,
|
|
311
|
+
itemValue: 'valueCustom',
|
|
312
|
+
itemText: 'textCustom',
|
|
313
|
+
modelValue: [items[0]], // Start with first item selected
|
|
314
|
+
multiple: true,
|
|
315
|
+
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Click second item to add it to selection
|
|
320
|
+
await wrapper.findByText('Option 2', 'button').trigger('click');
|
|
321
|
+
|
|
322
|
+
const newModelValue = wrapper.props('modelValue');
|
|
323
|
+
expect(Array.isArray(newModelValue)).toBe(true);
|
|
324
|
+
expect(newModelValue).toHaveLength(2);
|
|
325
|
+
expect(newModelValue[0].valueCustom).toBe(1);
|
|
326
|
+
expect(newModelValue[1].valueCustom).toBe(2);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('removes items from selection when clicked again in multiple mode', async () => {
|
|
330
|
+
const wrapper = createWrapperFor(PSelectBtn, {
|
|
331
|
+
props: {
|
|
332
|
+
items,
|
|
333
|
+
itemValue: 'valueCustom',
|
|
334
|
+
itemText: 'textCustom',
|
|
335
|
+
modelValue: [items[0], items[1]], // Start with first two selected
|
|
336
|
+
multiple: true,
|
|
337
|
+
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Click first item to remove it from selection
|
|
342
|
+
await wrapper.findByText('Option 1', 'button').trigger('click');
|
|
343
|
+
|
|
344
|
+
const newModelValue = wrapper.props('modelValue');
|
|
345
|
+
expect(Array.isArray(newModelValue)).toBe(true);
|
|
346
|
+
expect(newModelValue).toHaveLength(1);
|
|
347
|
+
expect(newModelValue[0].valueCustom).toBe(2);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('creates new array when starting with non-array value in multiple mode', async () => {
|
|
351
|
+
const wrapper = createWrapperFor(PSelectBtn, {
|
|
352
|
+
props: {
|
|
353
|
+
items,
|
|
354
|
+
itemValue: 'valueCustom',
|
|
355
|
+
itemText: 'textCustom',
|
|
356
|
+
modelValue: null, // Start with null
|
|
357
|
+
multiple: true,
|
|
358
|
+
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Click first item
|
|
363
|
+
await wrapper.findByText('Option 1', 'button').trigger('click');
|
|
364
|
+
|
|
365
|
+
const newModelValue = wrapper.props('modelValue');
|
|
366
|
+
expect(Array.isArray(newModelValue)).toBe(true);
|
|
367
|
+
expect(newModelValue).toHaveLength(1);
|
|
368
|
+
expect(newModelValue[0].valueCustom).toBe(1);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('handles isSelected correctly with non-array modelValue in multiple mode', async () => {
|
|
372
|
+
const wrapper = createWrapperFor(PSelectBtn, {
|
|
373
|
+
props: {
|
|
374
|
+
items,
|
|
375
|
+
itemValue: 'valueCustom',
|
|
376
|
+
itemText: 'textCustom',
|
|
377
|
+
modelValue: null, // Non-array value
|
|
378
|
+
multiple: true,
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const buttons = await wrapper.findAll('button');
|
|
383
|
+
|
|
384
|
+
// All buttons should be unselected when modelValue is not an array
|
|
385
|
+
buttons.forEach((button) => {
|
|
386
|
+
expect(button.attributes()['aria-selected']).toBe('false');
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
});
|
|
286
390
|
});
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
ref="actionsContainer"
|
|
20
20
|
class="flex flex-row justify-between text-xs font-semibold text-primary"
|
|
21
21
|
>
|
|
22
|
-
<p class="text-p-purple-60">{{ computedItems.length }}
|
|
22
|
+
<p class="text-p-purple-60">{{ $t('squirrel.select_list_items', computedItems.length) }}</p>
|
|
23
23
|
<div class="flex flex-row">
|
|
24
24
|
<a
|
|
25
25
|
v-if="computedItems.length === internalItems.length"
|
|
@@ -28,18 +28,18 @@
|
|
|
28
28
|
]"
|
|
29
29
|
@click="selectAll"
|
|
30
30
|
>
|
|
31
|
-
|
|
31
|
+
{{ $t('squirrel.select_list_select_all') }}
|
|
32
32
|
</a>
|
|
33
33
|
<a
|
|
34
34
|
v-else
|
|
35
35
|
:class="[computedInsideSelected ? 'pointer-events-none opacity-50' : 'cursor-pointer']"
|
|
36
36
|
@click="selectAll"
|
|
37
37
|
>
|
|
38
|
-
|
|
38
|
+
{{ $t('squirrel.select_list_select_all_filtered') }}
|
|
39
39
|
</a>
|
|
40
40
|
<span class="px-1 leading-none">.</span>
|
|
41
41
|
<a :class="[selectedItems.length ? 'cursor-pointer' : 'pointer-events-none opacity-50']" @click="clearAll">
|
|
42
|
-
|
|
42
|
+
{{ $t('squirrel.select_list_clear_all') }}
|
|
43
43
|
</a>
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
@@ -110,7 +110,9 @@
|
|
|
110
110
|
</div>
|
|
111
111
|
</div>
|
|
112
112
|
<slot v-if="!computedItems.length" name="no-items">
|
|
113
|
-
<div :class="['flex items-center justify-center', SIZES[size]]">
|
|
113
|
+
<div :class="['flex items-center justify-center', SIZES[size]]">
|
|
114
|
+
{{ $t('squirrel.select_list_no_items_found') }}
|
|
115
|
+
</div>
|
|
114
116
|
</slot>
|
|
115
117
|
</div>
|
|
116
118
|
</div>
|
|
@@ -136,4 +136,118 @@ describe('PSelectPill.vue', () => {
|
|
|
136
136
|
expect(button.text()).toContain(`subtext for ${items[i].text}`);
|
|
137
137
|
});
|
|
138
138
|
});
|
|
139
|
+
|
|
140
|
+
it('handles empty/null items array with default', async () => {
|
|
141
|
+
const wrapper = createWrapperFor(PSelectPill, {
|
|
142
|
+
props: {
|
|
143
|
+
items: null, // Test default items prop
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const buttons = await wrapper.findAll('button');
|
|
148
|
+
expect(buttons).toHaveLength(0);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('updates pill style when selection changes', async () => {
|
|
152
|
+
const wrapper = createWrapperFor(PSelectPill, {
|
|
153
|
+
props: {
|
|
154
|
+
items: selectItems,
|
|
155
|
+
modelValue: 1,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Spy on the setPillStyle method
|
|
160
|
+
const setPillStyleSpy = vi.spyOn(wrapper.vm, 'setPillStyle');
|
|
161
|
+
|
|
162
|
+
// Trigger a prop change to test setPillStyle
|
|
163
|
+
await wrapper.setProps({ modelValue: 2 });
|
|
164
|
+
|
|
165
|
+
// Wait for the setTimeout delay (60ms + buffer)
|
|
166
|
+
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
167
|
+
|
|
168
|
+
expect(setPillStyleSpy).toHaveBeenCalled();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('handles setPillStyle when no active element exists', async () => {
|
|
172
|
+
const wrapper = createWrapperFor(PSelectPill, {
|
|
173
|
+
props: {
|
|
174
|
+
items: selectItems,
|
|
175
|
+
modelValue: 999, // Non-existent value
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Should not throw error when no active element exists
|
|
180
|
+
expect(() => wrapper.vm.setPillStyle()).not.toThrow();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('handles responsive sizing with getOffsetValues fallback', async () => {
|
|
184
|
+
const wrapper = createWrapperFor(PSelectPill, {
|
|
185
|
+
props: {
|
|
186
|
+
items: selectItems,
|
|
187
|
+
modelValue: 1,
|
|
188
|
+
},
|
|
189
|
+
attachTo: document.body, // Ensure proper DOM attachment
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Mock offsetWidth to be 0 to trigger getOffsetValues fallback
|
|
193
|
+
const activeButton = wrapper.find('button.text-p-purple-60');
|
|
194
|
+
expect(activeButton.exists()).toBe(true);
|
|
195
|
+
|
|
196
|
+
// Mock the element's offsetWidth to be 0
|
|
197
|
+
Object.defineProperty(activeButton.element, 'offsetWidth', {
|
|
198
|
+
get: () => 0,
|
|
199
|
+
});
|
|
200
|
+
Object.defineProperty(activeButton.element, 'offsetLeft', {
|
|
201
|
+
get: () => 0,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// This should trigger the getOffsetValues fallback and not throw
|
|
205
|
+
expect(() => wrapper.vm.setPillStyle()).not.toThrow();
|
|
206
|
+
|
|
207
|
+
wrapper.unmount();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('handles lifecycle events correctly', async () => {
|
|
211
|
+
const wrapper = createWrapperFor(PSelectPill, {
|
|
212
|
+
props: {
|
|
213
|
+
items: selectItems,
|
|
214
|
+
modelValue: 1,
|
|
215
|
+
},
|
|
216
|
+
attachTo: document.body,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Test mounted lifecycle
|
|
220
|
+
expect(wrapper.vm.$refs.pill).toBeDefined();
|
|
221
|
+
|
|
222
|
+
// Test that pill ref exists and style can be set
|
|
223
|
+
if (wrapper.vm.$refs.pill instanceof HTMLElement) {
|
|
224
|
+
wrapper.vm.setPillStyle();
|
|
225
|
+
expect(wrapper.vm.$refs.pill.style.left).toBeDefined();
|
|
226
|
+
expect(wrapper.vm.$refs.pill.style.width).toBeDefined();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
wrapper.unmount();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('handles window resize events', async () => {
|
|
233
|
+
const wrapper = createWrapperFor(PSelectPill, {
|
|
234
|
+
props: {
|
|
235
|
+
items: selectItems,
|
|
236
|
+
modelValue: 1,
|
|
237
|
+
},
|
|
238
|
+
attachTo: document.body,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const setPillStyleSpy = vi.spyOn(wrapper.vm, 'setPillStyle');
|
|
242
|
+
|
|
243
|
+
// Simulate window resize
|
|
244
|
+
window.dispatchEvent(new Event('resize'));
|
|
245
|
+
|
|
246
|
+
// Wait for debounced resize handler
|
|
247
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
248
|
+
|
|
249
|
+
expect(setPillStyleSpy).toHaveBeenCalled();
|
|
250
|
+
|
|
251
|
+
wrapper.unmount();
|
|
252
|
+
});
|
|
139
253
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { usePTableColResize } from '@squirrel/components/p-table/usePTableColResize';
|
|
2
2
|
import { createApp, nextTick, ref } from 'vue';
|
|
3
3
|
|
|
4
|
-
const withSetup = (composable) => {
|
|
4
|
+
const withSetup = (composable, returnApp = false) => {
|
|
5
5
|
let result;
|
|
6
6
|
|
|
7
7
|
const app = createApp({
|
|
@@ -9,23 +9,36 @@ const withSetup = (composable) => {
|
|
|
9
9
|
result = composable();
|
|
10
10
|
return () => {};
|
|
11
11
|
},
|
|
12
|
+
template: '<div></div>',
|
|
12
13
|
});
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
const element = document.createElement('div');
|
|
16
|
+
app.mount(element);
|
|
15
17
|
|
|
16
|
-
return result;
|
|
18
|
+
return returnApp ? { result, app, element } : { result };
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
describe('usePTableColResize', () => {
|
|
22
|
+
let mockAddEventListener;
|
|
23
|
+
let mockRemoveEventListener;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
mockAddEventListener = vi.spyOn(document, 'addEventListener');
|
|
27
|
+
mockRemoveEventListener = vi.spyOn(document, 'removeEventListener');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
vi.restoreAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
20
34
|
it('should resize the column when colResize is called', () => {
|
|
21
35
|
const options = {
|
|
22
36
|
enabled: ref(true),
|
|
23
37
|
ths: ref([{ offsetWidth: 100, getBoundingClientRect: () => ({ width: 100 }) }]),
|
|
24
38
|
};
|
|
25
39
|
|
|
26
|
-
const {
|
|
27
|
-
|
|
28
|
-
);
|
|
40
|
+
const { result } = withSetup(() => usePTableColResize(options));
|
|
41
|
+
const { isColResizing, colResizingIndex, colResizeHandleLeft, colResizingWidth, colResize } = result;
|
|
29
42
|
|
|
30
43
|
isColResizing.value = true;
|
|
31
44
|
colResizingIndex.value = 0;
|
|
@@ -42,9 +55,8 @@ describe('usePTableColResize', () => {
|
|
|
42
55
|
enabled: ref(true),
|
|
43
56
|
ths: ref([{ offsetWidth: 100 }, { offsetWidth: 200 }]),
|
|
44
57
|
};
|
|
45
|
-
const {
|
|
46
|
-
|
|
47
|
-
);
|
|
58
|
+
const { result } = withSetup(() => usePTableColResize(options));
|
|
59
|
+
const { colResizeStart, isColResizing, colResizingWidth, colResizingIndex } = result;
|
|
48
60
|
|
|
49
61
|
const event = new MouseEvent('mousedown', { detail: 1 });
|
|
50
62
|
colResizeStart(event, 1);
|
|
@@ -59,7 +71,23 @@ describe('usePTableColResize', () => {
|
|
|
59
71
|
enabled: ref(true),
|
|
60
72
|
ths: ref([{ offsetWidth: 100 }]),
|
|
61
73
|
};
|
|
62
|
-
const {
|
|
74
|
+
const { result } = withSetup(() => usePTableColResize(options));
|
|
75
|
+
const { colResizeStop, isColResizing } = result;
|
|
76
|
+
|
|
77
|
+
colResizeStop();
|
|
78
|
+
|
|
79
|
+
expect(isColResizing.value).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should do nothing when colResizeStop is called and not resizing', () => {
|
|
83
|
+
const options = {
|
|
84
|
+
enabled: ref(true),
|
|
85
|
+
ths: ref([{ offsetWidth: 100 }]),
|
|
86
|
+
};
|
|
87
|
+
const { result } = withSetup(() => usePTableColResize(options));
|
|
88
|
+
const { colResizeStop, isColResizing } = result;
|
|
89
|
+
|
|
90
|
+
expect(isColResizing.value).toBe(false);
|
|
63
91
|
|
|
64
92
|
colResizeStop();
|
|
65
93
|
|
|
@@ -77,7 +105,8 @@ describe('usePTableColResize', () => {
|
|
|
77
105
|
},
|
|
78
106
|
]),
|
|
79
107
|
};
|
|
80
|
-
const {
|
|
108
|
+
const { result } = withSetup(() => usePTableColResize(options));
|
|
109
|
+
const { colResizeFitToData, isColResizing, colResizingWidth } = result;
|
|
81
110
|
|
|
82
111
|
colResizeFitToData(0);
|
|
83
112
|
|
|
@@ -86,4 +115,87 @@ describe('usePTableColResize', () => {
|
|
|
86
115
|
expect(isColResizing.value).toBe(false);
|
|
87
116
|
expect(colResizingWidth.value).toBe(150);
|
|
88
117
|
});
|
|
118
|
+
|
|
119
|
+
it('should return early when colResizeFitToData cannot find cells', () => {
|
|
120
|
+
const options = {
|
|
121
|
+
enabled: ref(true),
|
|
122
|
+
ths: ref([
|
|
123
|
+
{
|
|
124
|
+
closest: () => null,
|
|
125
|
+
},
|
|
126
|
+
]),
|
|
127
|
+
};
|
|
128
|
+
const { result } = withSetup(() => usePTableColResize(options));
|
|
129
|
+
const { colResizeFitToData, colResizingWidth } = result;
|
|
130
|
+
|
|
131
|
+
const initialWidth = colResizingWidth.value;
|
|
132
|
+
|
|
133
|
+
colResizeFitToData(0);
|
|
134
|
+
|
|
135
|
+
// Should not change width since no cells were found
|
|
136
|
+
expect(colResizingWidth.value).toBe(initialWidth);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should manage event listeners based on enabled state', async () => {
|
|
140
|
+
const enabled = ref(true);
|
|
141
|
+
const options = {
|
|
142
|
+
enabled,
|
|
143
|
+
ths: ref([{ offsetWidth: 100 }]),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
withSetup(() => usePTableColResize(options));
|
|
147
|
+
|
|
148
|
+
// Should add listener on mount when enabled
|
|
149
|
+
expect(mockAddEventListener).toHaveBeenCalledWith('mouseup', expect.any(Function));
|
|
150
|
+
|
|
151
|
+
// Change enabled to false
|
|
152
|
+
enabled.value = false;
|
|
153
|
+
await nextTick();
|
|
154
|
+
expect(mockRemoveEventListener).toHaveBeenCalledWith('mouseup', expect.any(Function));
|
|
155
|
+
|
|
156
|
+
// Change enabled back to true
|
|
157
|
+
enabled.value = true;
|
|
158
|
+
await nextTick();
|
|
159
|
+
expect(mockAddEventListener).toHaveBeenCalledTimes(2);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should cleanup event listeners on unmount when enabled', () => {
|
|
163
|
+
const options = {
|
|
164
|
+
enabled: ref(true),
|
|
165
|
+
ths: ref([{ offsetWidth: 100 }]),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const { app } = withSetup(() => usePTableColResize(options), true);
|
|
169
|
+
|
|
170
|
+
// Trigger unmount
|
|
171
|
+
app.unmount();
|
|
172
|
+
|
|
173
|
+
expect(mockRemoveEventListener).toHaveBeenCalledWith('mouseup', expect.any(Function));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should not manage event listeners when disabled on mount', () => {
|
|
177
|
+
const options = {
|
|
178
|
+
enabled: ref(false),
|
|
179
|
+
ths: ref([{ offsetWidth: 100 }]),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
withSetup(() => usePTableColResize(options));
|
|
183
|
+
|
|
184
|
+
// Should not add listener when disabled
|
|
185
|
+
expect(mockAddEventListener).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should not start resizing on double click', () => {
|
|
189
|
+
const options = {
|
|
190
|
+
enabled: ref(true),
|
|
191
|
+
ths: ref([{ offsetWidth: 100 }]),
|
|
192
|
+
};
|
|
193
|
+
const { result } = withSetup(() => usePTableColResize(options));
|
|
194
|
+
const { colResizeStart, isColResizing } = result;
|
|
195
|
+
|
|
196
|
+
const event = new MouseEvent('mousedown', { detail: 2 }); // Double click
|
|
197
|
+
colResizeStart(event, 0);
|
|
198
|
+
|
|
199
|
+
expect(isColResizing.value).toBe(false);
|
|
200
|
+
});
|
|
89
201
|
});
|
|
@@ -6,7 +6,7 @@ import { defineComponent, useTemplateRef } from 'vue';
|
|
|
6
6
|
|
|
7
7
|
// Mock ResizeObserver to capture the composable's callback
|
|
8
8
|
let composableResizeCallback;
|
|
9
|
-
const mockResizeObserver = vi.fn((callback)
|
|
9
|
+
const mockResizeObserver = vi.fn().mockImplementation(function (callback) {
|
|
10
10
|
composableResizeCallback = callback;
|
|
11
11
|
return {
|
|
12
12
|
observe: vi.fn(),
|