vueless 1.3.9-beta.9 → 1.4.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/composables/useUI.ts +187 -155
- package/package.json +2 -2
- package/types.ts +11 -0
- package/ui.button-link/ULink.vue +18 -2
- package/ui.data-table/UTable.vue +568 -198
- package/ui.data-table/UTableRow.vue +372 -168
- package/ui.data-table/config.ts +8 -1
- package/ui.data-table/storybook/stories.ts +90 -0
- package/ui.data-table/tests/UTable.test.ts +317 -20
- package/ui.data-table/tests/UTableRow.test.ts +35 -104
- package/ui.data-table/types.ts +37 -0
- package/ui.data-table/utilTable.ts +16 -10
- package/ui.form-checkbox/UCheckbox.vue +3 -7
- package/ui.text-notify/config.ts +1 -1
- package/ui.text-number/UNumber.vue +36 -2
- package/ui.text-number/config.ts +1 -0
- package/ui.text-number/storybook/stories.ts +11 -0
- package/ui.text-number/tests/UNumber.test.ts +90 -0
- package/ui.text-number/types.ts +5 -0
- package/utils/node/mergeConfigs.d.ts +2 -2
- package/utils/node/mergeConfigs.js +240 -121
- package/utils/node/vuelessConfig.d.ts +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
|
|
1
3
|
import type { Meta, StoryFn } from "@storybook/vue3-vite";
|
|
2
4
|
|
|
3
5
|
import {
|
|
@@ -18,6 +20,8 @@ import UBadge from "../../ui.text-badge/UBadge.vue";
|
|
|
18
20
|
import URow from "../../ui.container-row/URow.vue";
|
|
19
21
|
import UIcon from "../../ui.image-icon/UIcon.vue";
|
|
20
22
|
import ULoader from "../../ui.loader/ULoader.vue";
|
|
23
|
+
import UInputSearch from "../../ui.form-input-search/UInputSearch.vue";
|
|
24
|
+
import UText from "../../ui.text-block/UText.vue";
|
|
21
25
|
|
|
22
26
|
import tooltip from "../../v.tooltip/vTooltip";
|
|
23
27
|
import type { Row, Props, ColumnObject } from "../types";
|
|
@@ -1044,3 +1048,89 @@ VirtualScroll.parameters = {
|
|
|
1044
1048
|
},
|
|
1045
1049
|
},
|
|
1046
1050
|
};
|
|
1051
|
+
|
|
1052
|
+
export const VirtualSearch: StoryFn<UTableArgs> = (args: UTableArgs) => ({
|
|
1053
|
+
components: { UTable, UInputSearch, UButton, URow, UText },
|
|
1054
|
+
setup() {
|
|
1055
|
+
const rows = generateNestedRows(100000);
|
|
1056
|
+
const search = ref("");
|
|
1057
|
+
const searchMatch = ref(-1);
|
|
1058
|
+
const totalMatches = ref(0);
|
|
1059
|
+
|
|
1060
|
+
function onPrev() {
|
|
1061
|
+
if (totalMatches.value === 0) return;
|
|
1062
|
+
|
|
1063
|
+
searchMatch.value = searchMatch.value <= 0 ? totalMatches.value - 1 : searchMatch.value - 1;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function onNext() {
|
|
1067
|
+
if (totalMatches.value === 0) return;
|
|
1068
|
+
|
|
1069
|
+
searchMatch.value = searchMatch.value >= totalMatches.value - 1 ? 0 : searchMatch.value + 1;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
return { args, rows, search, searchMatch, totalMatches, onPrev, onNext };
|
|
1073
|
+
},
|
|
1074
|
+
template: `
|
|
1075
|
+
<URow align="stretch" gap="xs" class="mb-4">
|
|
1076
|
+
<UInputSearch
|
|
1077
|
+
v-model="search"
|
|
1078
|
+
placeholder="Search in table..."
|
|
1079
|
+
size="md"
|
|
1080
|
+
@clear="searchMatch = -1"
|
|
1081
|
+
>
|
|
1082
|
+
<template #right>
|
|
1083
|
+
<UText :label="searchMatch + 1 + ' / ' + totalMatches" :wrap="false" class="ml-1" />
|
|
1084
|
+
</template>
|
|
1085
|
+
</UInputSearch>
|
|
1086
|
+
|
|
1087
|
+
<UButton
|
|
1088
|
+
square
|
|
1089
|
+
size="sm"
|
|
1090
|
+
title="Prev"
|
|
1091
|
+
variant="soft"
|
|
1092
|
+
icon="keyboard_arrow_up"
|
|
1093
|
+
:disabled="totalMatches === 0"
|
|
1094
|
+
@click="onPrev"
|
|
1095
|
+
/>
|
|
1096
|
+
|
|
1097
|
+
<UButton
|
|
1098
|
+
square
|
|
1099
|
+
size="sm"
|
|
1100
|
+
title="Next"
|
|
1101
|
+
variant="soft"
|
|
1102
|
+
icon="keyboard_arrow_down"
|
|
1103
|
+
:disabled="totalMatches === 0"
|
|
1104
|
+
@click="onNext"
|
|
1105
|
+
/>
|
|
1106
|
+
</URow>
|
|
1107
|
+
|
|
1108
|
+
<UTable
|
|
1109
|
+
:columns="[
|
|
1110
|
+
{ key: 'orderId', label: 'Order ID', thClass: 'w-1/5' },
|
|
1111
|
+
{ key: 'customerName', label: 'Customer Name' },
|
|
1112
|
+
{ key: 'status', label: 'Status' },
|
|
1113
|
+
{ key: 'totalPrice', label: 'Total Price' },
|
|
1114
|
+
]"
|
|
1115
|
+
:rows="rows"
|
|
1116
|
+
compact
|
|
1117
|
+
virtual-scroll
|
|
1118
|
+
:row-height="45"
|
|
1119
|
+
:buffer-size="10"
|
|
1120
|
+
:search="search"
|
|
1121
|
+
:search-match="searchMatch"
|
|
1122
|
+
@search="totalMatches = $event"
|
|
1123
|
+
/>
|
|
1124
|
+
`,
|
|
1125
|
+
});
|
|
1126
|
+
VirtualSearch.parameters = {
|
|
1127
|
+
docs: {
|
|
1128
|
+
description: {
|
|
1129
|
+
story:
|
|
1130
|
+
"Search functionality with virtual scrolling. " +
|
|
1131
|
+
"Use `search` prop to pass a search string and `searchMatch` prop to highlight a specific match. " +
|
|
1132
|
+
"The `@search` event emits the total number of matches found. " +
|
|
1133
|
+
"Use Prev/Next buttons to navigate between matches.",
|
|
1134
|
+
},
|
|
1135
|
+
},
|
|
1136
|
+
};
|
|
@@ -430,6 +430,141 @@ describe("UTable.vue", () => {
|
|
|
430
430
|
|
|
431
431
|
expect(tableRows.length).toBe(manyRows.length);
|
|
432
432
|
});
|
|
433
|
+
|
|
434
|
+
it("Scroll Height – accepts scrollHeight prop", () => {
|
|
435
|
+
const scrollHeight = "500px";
|
|
436
|
+
|
|
437
|
+
const component = mountUTable(
|
|
438
|
+
getDefaultProps({
|
|
439
|
+
virtualScroll: true,
|
|
440
|
+
scrollHeight,
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
expect(component.vm.$props.scrollHeight).toBe(scrollHeight);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("Search – uses default empty search value", () => {
|
|
448
|
+
const component = mountUTable(getDefaultProps());
|
|
449
|
+
|
|
450
|
+
expect(component.vm.$props.search).toBe("");
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("Search – accepts search string", () => {
|
|
454
|
+
const searchQuery = "john";
|
|
455
|
+
|
|
456
|
+
const component = mountUTable(
|
|
457
|
+
getDefaultProps({
|
|
458
|
+
search: searchQuery,
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
expect(component.vm.$props.search).toBe(searchQuery);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it("Search – emits search event with total matches count", async () => {
|
|
466
|
+
const component = mountUTable(getDefaultProps());
|
|
467
|
+
|
|
468
|
+
await component.setProps({ search: "doe" });
|
|
469
|
+
await nextTick();
|
|
470
|
+
|
|
471
|
+
expect(component.emitted("search")).toBeTruthy();
|
|
472
|
+
expect(component.emitted("search")![0][0]).toBe(1);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("Search – emits search event with zero when no matches found", async () => {
|
|
476
|
+
const component = mountUTable(getDefaultProps());
|
|
477
|
+
|
|
478
|
+
// First set a search that has matches
|
|
479
|
+
await component.setProps({ search: "doe" });
|
|
480
|
+
await nextTick();
|
|
481
|
+
|
|
482
|
+
// Then set a search with no matches
|
|
483
|
+
await component.setProps({ search: "nonexistent" });
|
|
484
|
+
await nextTick();
|
|
485
|
+
|
|
486
|
+
const emittedEvents = component.emitted("search");
|
|
487
|
+
|
|
488
|
+
expect(emittedEvents).toBeTruthy();
|
|
489
|
+
expect(emittedEvents![emittedEvents!.length - 1][0]).toBe(0);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("Search – finds multiple matches across different rows", async () => {
|
|
493
|
+
const component = mountUTable(getDefaultProps());
|
|
494
|
+
|
|
495
|
+
await component.setProps({ search: "example" });
|
|
496
|
+
await nextTick();
|
|
497
|
+
|
|
498
|
+
const emittedEvents = component.emitted("search");
|
|
499
|
+
|
|
500
|
+
expect(emittedEvents).toBeTruthy();
|
|
501
|
+
expect(emittedEvents![0][0]).toBe(3); // All 3 rows have "example.com" in email
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("Search – is case insensitive", async () => {
|
|
505
|
+
const component = mountUTable(getDefaultProps());
|
|
506
|
+
|
|
507
|
+
await component.setProps({ search: "DOE" });
|
|
508
|
+
await nextTick();
|
|
509
|
+
|
|
510
|
+
const emittedEvents = component.emitted("search");
|
|
511
|
+
|
|
512
|
+
expect(emittedEvents).toBeTruthy();
|
|
513
|
+
expect(emittedEvents![0][0]).toBe(1);
|
|
514
|
+
|
|
515
|
+
// Clear search first to trigger a new event
|
|
516
|
+
await component.setProps({ search: "" });
|
|
517
|
+
await nextTick();
|
|
518
|
+
|
|
519
|
+
await component.setProps({ search: "doe" });
|
|
520
|
+
await nextTick();
|
|
521
|
+
|
|
522
|
+
expect(emittedEvents![emittedEvents!.length - 1][0]).toBe(1);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("Search – finds partial matches", async () => {
|
|
526
|
+
const component = mountUTable(getDefaultProps());
|
|
527
|
+
|
|
528
|
+
await component.setProps({ search: "Jo" });
|
|
529
|
+
await nextTick();
|
|
530
|
+
|
|
531
|
+
expect(component.emitted("search")![0][0]).toBeGreaterThan(0);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it("Search Match – uses default searchMatch value", () => {
|
|
535
|
+
const component = mountUTable(getDefaultProps());
|
|
536
|
+
|
|
537
|
+
expect(component.vm.$props.searchMatch).toBe(-1);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("Search Match – accepts searchMatch index", () => {
|
|
541
|
+
const searchMatchIndex = 2;
|
|
542
|
+
|
|
543
|
+
const component = mountUTable(
|
|
544
|
+
getDefaultProps({
|
|
545
|
+
search: "example",
|
|
546
|
+
searchMatch: searchMatchIndex,
|
|
547
|
+
}),
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
expect(component.vm.$props.searchMatch).toBe(searchMatchIndex);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("Search Match – passes search props to table rows", async () => {
|
|
554
|
+
const searchQuery = "john";
|
|
555
|
+
|
|
556
|
+
const component = mountUTable(
|
|
557
|
+
getDefaultProps({
|
|
558
|
+
search: searchQuery,
|
|
559
|
+
}),
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
await nextTick();
|
|
563
|
+
|
|
564
|
+
const tableRow = component.getComponent(UTableRow);
|
|
565
|
+
|
|
566
|
+
expect(tableRow.props("search")).toBe(searchQuery);
|
|
567
|
+
});
|
|
433
568
|
});
|
|
434
569
|
|
|
435
570
|
describe("Slots", () => {
|
|
@@ -608,7 +743,7 @@ describe("UTable.vue", () => {
|
|
|
608
743
|
await firstRow.trigger("click");
|
|
609
744
|
|
|
610
745
|
expect(component.emitted("clickRow")).toBeTruthy();
|
|
611
|
-
expect(component.emitted("clickRow")![0][0]).
|
|
746
|
+
expect(component.emitted("clickRow")![0][0]).toMatchObject(defaultRows[0]);
|
|
612
747
|
});
|
|
613
748
|
|
|
614
749
|
it("Double Click Row – emits doubleClickRow event when row is double-clicked", async () => {
|
|
@@ -619,7 +754,7 @@ describe("UTable.vue", () => {
|
|
|
619
754
|
await firstRow.trigger("dblclick");
|
|
620
755
|
|
|
621
756
|
expect(component.emitted("doubleClickRow")).toBeTruthy();
|
|
622
|
-
expect(component.emitted("doubleClickRow")![0][0]).
|
|
757
|
+
expect(component.emitted("doubleClickRow")![0][0]).toMatchObject(defaultRows[0]);
|
|
623
758
|
});
|
|
624
759
|
|
|
625
760
|
it("Click Cell – emits clickCell event when cell is clicked", async () => {
|
|
@@ -630,22 +765,25 @@ describe("UTable.vue", () => {
|
|
|
630
765
|
await firstCell.trigger("click");
|
|
631
766
|
|
|
632
767
|
expect(component.emitted("clickCell")).toBeTruthy();
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
]);
|
|
768
|
+
const emittedData = component.emitted("clickCell")![0];
|
|
769
|
+
|
|
770
|
+
expect(emittedData[0]).toBe(defaultRows[0].name);
|
|
771
|
+
expect(emittedData[1]).toMatchObject(defaultRows[0]);
|
|
772
|
+
expect(emittedData[2]).toBe("name");
|
|
638
773
|
});
|
|
639
774
|
|
|
640
775
|
it("Toggle Row Checkbox – emits update:selectedRows when row checkbox is clicked", async () => {
|
|
641
776
|
const component = mountUTable(getDefaultProps({ selectable: true }));
|
|
642
777
|
|
|
643
|
-
const
|
|
778
|
+
const checkboxCell = component.find("tbody tr td[data-checkbox-id]");
|
|
644
779
|
|
|
645
|
-
await
|
|
780
|
+
await checkboxCell.trigger("click");
|
|
646
781
|
|
|
647
782
|
expect(component.emitted("update:selectedRows")).toBeTruthy();
|
|
648
|
-
|
|
783
|
+
const emittedRows = component.emitted("update:selectedRows")![0][0] as Row[];
|
|
784
|
+
|
|
785
|
+
expect(emittedRows).toHaveLength(1);
|
|
786
|
+
expect(emittedRows[0]).toMatchObject(defaultRows[0]);
|
|
649
787
|
});
|
|
650
788
|
|
|
651
789
|
it("Toggle Expand – emits row-expand and update:expandedRows when expand icon is clicked", async () => {
|
|
@@ -664,7 +802,7 @@ describe("UTable.vue", () => {
|
|
|
664
802
|
await expandIcon.trigger("click");
|
|
665
803
|
|
|
666
804
|
expect(component.emitted("row-expand")).toBeTruthy();
|
|
667
|
-
expect(component.emitted("row-expand")![0][0]).
|
|
805
|
+
expect(component.emitted("row-expand")![0][0]).toMatchObject(expandableRow);
|
|
668
806
|
expect(component.emitted("update:expandedRows")).toBeTruthy();
|
|
669
807
|
expect(component.emitted("update:expandedRows")![0][0]).toEqual(["1"]);
|
|
670
808
|
});
|
|
@@ -690,7 +828,7 @@ describe("UTable.vue", () => {
|
|
|
690
828
|
await collapseIcon.trigger("click");
|
|
691
829
|
|
|
692
830
|
expect(component.emitted("row-collapse")).toBeTruthy();
|
|
693
|
-
expect(component.emitted("row-collapse")![0][0]).
|
|
831
|
+
expect(component.emitted("row-collapse")![0][0]).toMatchObject(expandableRow);
|
|
694
832
|
expect(component.emitted("update:expandedRows")).toBeTruthy();
|
|
695
833
|
expect(component.emitted("update:expandedRows")![0][0]).toEqual([]);
|
|
696
834
|
});
|
|
@@ -698,20 +836,33 @@ describe("UTable.vue", () => {
|
|
|
698
836
|
it("Multiple Row Selection – emits update:selectedRows with all selected rows", async () => {
|
|
699
837
|
const component = mountUTable(getDefaultProps({ selectable: true }));
|
|
700
838
|
|
|
701
|
-
const tableRows = component.findAll("tbody tr");
|
|
702
|
-
|
|
703
839
|
// Select first row
|
|
704
|
-
|
|
840
|
+
let tableRows = component.findAll("tbody tr");
|
|
841
|
+
|
|
842
|
+
await tableRows[0].find("td[data-checkbox-id]").trigger("click");
|
|
843
|
+
await nextTick();
|
|
844
|
+
|
|
845
|
+
// Update props with the first selected row
|
|
846
|
+
const firstEmit = component.emitted("update:selectedRows")![0][0] as Row[];
|
|
847
|
+
|
|
848
|
+
await component.setProps({ selectedRows: firstEmit });
|
|
849
|
+
await nextTick();
|
|
850
|
+
|
|
851
|
+
// Re-query the DOM after props update
|
|
852
|
+
tableRows = component.findAll("tbody tr");
|
|
853
|
+
|
|
705
854
|
// Select second row
|
|
706
|
-
await tableRows[1].find("
|
|
855
|
+
await tableRows[1].find("td[data-checkbox-id]").trigger("click");
|
|
856
|
+
await nextTick();
|
|
707
857
|
|
|
708
858
|
const emittedEvents = component.emitted("update:selectedRows");
|
|
709
859
|
|
|
710
860
|
expect(emittedEvents).toBeTruthy();
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
]);
|
|
861
|
+
const emittedRows = emittedEvents![emittedEvents!.length - 1][0] as Row[];
|
|
862
|
+
|
|
863
|
+
expect(emittedRows).toHaveLength(2);
|
|
864
|
+
expect(emittedRows[0]).toMatchObject(defaultRows[0]);
|
|
865
|
+
expect(emittedRows[1]).toMatchObject(defaultRows[1]);
|
|
715
866
|
});
|
|
716
867
|
|
|
717
868
|
it("Nested Row Expansion – emits update:expandedRows for nested rows", async () => {
|
|
@@ -765,5 +916,151 @@ describe("UTable.vue", () => {
|
|
|
765
916
|
expect(component.emitted("clickRow")).toBeFalsy();
|
|
766
917
|
expect(component.emitted("doubleClickRow")).toBeFalsy();
|
|
767
918
|
});
|
|
919
|
+
|
|
920
|
+
it("Search Event – emits when search prop changes", async () => {
|
|
921
|
+
const component = mountUTable(getDefaultProps());
|
|
922
|
+
|
|
923
|
+
await component.setProps({ search: "doe" });
|
|
924
|
+
await nextTick();
|
|
925
|
+
|
|
926
|
+
expect(component.emitted("search")).toBeTruthy();
|
|
927
|
+
expect(component.emitted("search")![0][0]).toBe(1);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
it("Search Event – emits updated count when search changes", async () => {
|
|
931
|
+
const component = mountUTable(getDefaultProps());
|
|
932
|
+
|
|
933
|
+
await component.setProps({ search: "doe" });
|
|
934
|
+
await nextTick();
|
|
935
|
+
|
|
936
|
+
const initialCount = component.emitted("search")![0][0];
|
|
937
|
+
|
|
938
|
+
await component.setProps({ search: "example" });
|
|
939
|
+
await nextTick();
|
|
940
|
+
|
|
941
|
+
const updatedCount = component.emitted("search")![1][0];
|
|
942
|
+
|
|
943
|
+
expect(updatedCount).toBeGreaterThan(initialCount as number);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
it("Search Event – emits zero when search is cleared", async () => {
|
|
947
|
+
const component = mountUTable(getDefaultProps());
|
|
948
|
+
|
|
949
|
+
await component.setProps({ search: "doe" });
|
|
950
|
+
await nextTick();
|
|
951
|
+
|
|
952
|
+
await component.setProps({ search: "" });
|
|
953
|
+
await nextTick();
|
|
954
|
+
|
|
955
|
+
const emittedEvents = component.emitted("search");
|
|
956
|
+
|
|
957
|
+
expect(emittedEvents![emittedEvents!.length - 1][0]).toBe(0);
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
it("Search Event – counts all occurrences in a single cell", async () => {
|
|
961
|
+
const rowsWithDuplicates: Row[] = [
|
|
962
|
+
{
|
|
963
|
+
id: "1",
|
|
964
|
+
name: "Test Test Test",
|
|
965
|
+
email: "test@example.com",
|
|
966
|
+
role: "User",
|
|
967
|
+
},
|
|
968
|
+
];
|
|
969
|
+
|
|
970
|
+
const component = mountUTable(getDefaultProps({ rows: rowsWithDuplicates }));
|
|
971
|
+
|
|
972
|
+
await component.setProps({ search: "test" });
|
|
973
|
+
await nextTick();
|
|
974
|
+
|
|
975
|
+
expect(component.emitted("search")![0][0]).toBe(4);
|
|
976
|
+
});
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
describe("Virtual Scroll with Search", () => {
|
|
980
|
+
it("Virtual Scroll with Search – renders only visible rows with search", async () => {
|
|
981
|
+
const manyRows = Array.from({ length: 100 }, (_, i) => ({
|
|
982
|
+
id: String(i + 1),
|
|
983
|
+
name: `User ${i + 1}`,
|
|
984
|
+
email: `user${i + 1}@example.com`,
|
|
985
|
+
role: i % 2 === 0 ? "Admin" : "User",
|
|
986
|
+
}));
|
|
987
|
+
|
|
988
|
+
const component = mountUTable(
|
|
989
|
+
getDefaultProps({
|
|
990
|
+
rows: manyRows,
|
|
991
|
+
virtualScroll: true,
|
|
992
|
+
rowHeight: 40,
|
|
993
|
+
scrollHeight: "400px",
|
|
994
|
+
}),
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
await component.setProps({ search: "Admin" });
|
|
998
|
+
await nextTick();
|
|
999
|
+
|
|
1000
|
+
const tableRows = component.findAllComponents(UTableRow);
|
|
1001
|
+
|
|
1002
|
+
expect(tableRows.length).toBeLessThan(manyRows.length);
|
|
1003
|
+
expect(component.emitted("search")).toBeTruthy();
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it("Virtual Scroll with Search – passes search match columns to rows", async () => {
|
|
1007
|
+
const component = mountUTable(
|
|
1008
|
+
getDefaultProps({
|
|
1009
|
+
virtualScroll: true,
|
|
1010
|
+
search: "john",
|
|
1011
|
+
}),
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
await nextTick();
|
|
1015
|
+
|
|
1016
|
+
const tableRow = component.getComponent(UTableRow);
|
|
1017
|
+
|
|
1018
|
+
expect(tableRow.props("search")).toBe("john");
|
|
1019
|
+
expect(tableRow.props("searchMatchColumns")).toBeDefined();
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
it("Virtual Scroll with Search – handles searchMatch prop", async () => {
|
|
1023
|
+
const manyRows = Array.from({ length: 50 }, (_, i) => ({
|
|
1024
|
+
id: String(i + 1),
|
|
1025
|
+
name: `User ${i + 1}`,
|
|
1026
|
+
email: `user${i + 1}@example.com`,
|
|
1027
|
+
role: "User",
|
|
1028
|
+
}));
|
|
1029
|
+
|
|
1030
|
+
const component = mountUTable(
|
|
1031
|
+
getDefaultProps({
|
|
1032
|
+
rows: manyRows,
|
|
1033
|
+
virtualScroll: true,
|
|
1034
|
+
search: "user",
|
|
1035
|
+
searchMatch: 0,
|
|
1036
|
+
}),
|
|
1037
|
+
);
|
|
1038
|
+
|
|
1039
|
+
await nextTick();
|
|
1040
|
+
|
|
1041
|
+
expect(component.vm.$props.searchMatch).toBe(0);
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
it("Virtual Scroll with Search – emits search event with virtual scroll enabled", async () => {
|
|
1045
|
+
const manyRows = Array.from({ length: 100 }, (_, i) => ({
|
|
1046
|
+
id: String(i + 1),
|
|
1047
|
+
name: `User ${i + 1}`,
|
|
1048
|
+
email: `user${i + 1}@example.com`,
|
|
1049
|
+
role: "User",
|
|
1050
|
+
}));
|
|
1051
|
+
|
|
1052
|
+
const component = mountUTable(
|
|
1053
|
+
getDefaultProps({
|
|
1054
|
+
rows: manyRows,
|
|
1055
|
+
virtualScroll: true,
|
|
1056
|
+
}),
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
await component.setProps({ search: "user" });
|
|
1060
|
+
await nextTick();
|
|
1061
|
+
|
|
1062
|
+
expect(component.emitted("search")).toBeTruthy();
|
|
1063
|
+
expect(component.emitted("search")![0][0]).toBeGreaterThan(0);
|
|
1064
|
+
});
|
|
768
1065
|
});
|
|
769
1066
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { flushPromises, mount } from "@vue/test-utils";
|
|
2
2
|
import { describe, it, expect, vi } from "vitest";
|
|
3
|
-
import { ref } from "vue";
|
|
3
|
+
import { ref, nextTick } from "vue";
|
|
4
4
|
|
|
5
5
|
import UTableRow from "../UTableRow.vue";
|
|
6
6
|
import UIcon from "../../ui.image-icon/UIcon.vue";
|
|
@@ -36,6 +36,10 @@ describe("UTableRow.vue", () => {
|
|
|
36
36
|
bodyRowAttrs: ref({ class: "row-base" }),
|
|
37
37
|
bodyCellStickyLeftAttrs: ref({ class: "sticky-left" }),
|
|
38
38
|
bodyCellStickyRightAttrs: ref({ class: "sticky-right" }),
|
|
39
|
+
bodyCellSearchMatchAttrs: ref({ class: "search-match" }),
|
|
40
|
+
bodyCellSearchMatchTextAttrs: ref({ class: "search-match-text" }),
|
|
41
|
+
bodyCellSearchMatchActiveAttrs: ref({ class: "search-match-active" }),
|
|
42
|
+
bodyCellSearchMatchTextActiveAttrs: ref({ class: "search-match-active-text" }),
|
|
39
43
|
};
|
|
40
44
|
|
|
41
45
|
const defaultConfig = {
|
|
@@ -64,6 +68,7 @@ describe("UTableRow.vue", () => {
|
|
|
64
68
|
config: defaultConfig,
|
|
65
69
|
isChecked: false,
|
|
66
70
|
isExpanded: false,
|
|
71
|
+
isVisible: true,
|
|
67
72
|
columnPositions,
|
|
68
73
|
...overrides,
|
|
69
74
|
};
|
|
@@ -260,14 +265,17 @@ describe("UTableRow.vue", () => {
|
|
|
260
265
|
}),
|
|
261
266
|
});
|
|
262
267
|
|
|
263
|
-
|
|
268
|
+
let icon = component.findComponent(UIcon);
|
|
264
269
|
|
|
265
270
|
expect(icon.props("name")).toBe(defaultConfig.defaults.expandIcon);
|
|
266
271
|
|
|
267
|
-
component.setProps({ isExpanded: true });
|
|
268
|
-
|
|
272
|
+
await component.setProps({ isExpanded: true });
|
|
273
|
+
await nextTick();
|
|
269
274
|
await flushPromises();
|
|
270
275
|
|
|
276
|
+
// Re-query the icon after props update
|
|
277
|
+
icon = component.findComponent(UIcon);
|
|
278
|
+
|
|
271
279
|
expect(icon.props("name")).toBe(defaultConfig.defaults.collapseIcon);
|
|
272
280
|
});
|
|
273
281
|
|
|
@@ -286,106 +294,8 @@ describe("UTableRow.vue", () => {
|
|
|
286
294
|
});
|
|
287
295
|
});
|
|
288
296
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const component = mount(UTableRow, {
|
|
292
|
-
props: getDefaultProps(),
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
await component.get("tr").trigger("click");
|
|
296
|
-
|
|
297
|
-
expect(component.emitted("click")).toBeTruthy();
|
|
298
|
-
expect(component.emitted("click")![0][0]).toEqual(defaultRow);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it("Double Click – emits dblclick event when row is double-clicked", async () => {
|
|
302
|
-
const component = mount(UTableRow, {
|
|
303
|
-
props: getDefaultProps(),
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
await component.get("tr").trigger("dblclick");
|
|
307
|
-
|
|
308
|
-
expect(component.emitted("dblclick")).toBeTruthy();
|
|
309
|
-
expect(component.emitted("dblclick")![0][0]).toEqual(defaultRow);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it("Click Cell – emits clickCell event when cell is clicked", async () => {
|
|
313
|
-
const component = mount(UTableRow, {
|
|
314
|
-
props: getDefaultProps(),
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
const firstCell = component.find("td");
|
|
318
|
-
|
|
319
|
-
await firstCell.trigger("click");
|
|
320
|
-
|
|
321
|
-
expect(component.emitted("clickCell")).toBeTruthy();
|
|
322
|
-
expect(component.emitted("clickCell")![0]).toEqual(["John Doe", defaultRow, "name"]);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it("Toggle Expand – emits toggleExpand event when expand icon is clicked", async () => {
|
|
326
|
-
const expandableRow: FlatRow = {
|
|
327
|
-
...defaultRow,
|
|
328
|
-
row: [{ id: "2", name: "Child", nestedLeveL: 1 }],
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const component = mount(UTableRow, {
|
|
332
|
-
props: getDefaultProps({ row: expandableRow }),
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const expandIcon = component.find("[data-row-toggle-icon='1']");
|
|
336
|
-
|
|
337
|
-
await expandIcon.trigger("click");
|
|
338
|
-
|
|
339
|
-
expect(component.emitted("toggleExpand")).toBeTruthy();
|
|
340
|
-
expect(component.emitted("toggleExpand")![0][0]).toEqual(expandableRow);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it("Toggle Checkbox – emits toggleCheckbox event when checkbox is changed", async () => {
|
|
344
|
-
const component = mount(UTableRow, {
|
|
345
|
-
props: getDefaultProps({ selectable: true }),
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
const checkbox = component.getComponent(UCheckbox);
|
|
349
|
-
|
|
350
|
-
await checkbox.vm.$emit("input", defaultRow);
|
|
351
|
-
|
|
352
|
-
expect(component.emitted("toggleCheckbox")).toBeTruthy();
|
|
353
|
-
expect(component.emitted("toggleCheckbox")![0][0]).toEqual(defaultRow);
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it("Checkbox Cell – prevents row click events when checkbox cell is clicked", async () => {
|
|
357
|
-
const component = mount(UTableRow, {
|
|
358
|
-
props: getDefaultProps({ selectable: true }),
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
const checkboxCell = component.find("td");
|
|
362
|
-
|
|
363
|
-
await checkboxCell.trigger("click");
|
|
364
|
-
await checkboxCell.trigger("dblclick");
|
|
365
|
-
|
|
366
|
-
expect(component.emitted("click")).toBeFalsy();
|
|
367
|
-
expect(component.emitted("dblclick")).toBeFalsy();
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it("Expand Icon – prevents row click events when expand icon is clicked", async () => {
|
|
371
|
-
const expandableRow: FlatRow = {
|
|
372
|
-
...defaultRow,
|
|
373
|
-
row: [{ id: "2", name: "Child", nestedLeveL: 1 }],
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
const component = mount(UTableRow, {
|
|
377
|
-
props: getDefaultProps({ row: expandableRow }),
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
const expandIcon = component.find("[data-row-toggle-icon='1']");
|
|
381
|
-
|
|
382
|
-
await expandIcon.trigger("click");
|
|
383
|
-
await expandIcon.trigger("dblclick");
|
|
384
|
-
|
|
385
|
-
expect(component.emitted("click")).toBeFalsy();
|
|
386
|
-
expect(component.emitted("dblclick")).toBeFalsy();
|
|
387
|
-
});
|
|
388
|
-
});
|
|
297
|
+
// Events are now handled by UTable via event delegation
|
|
298
|
+
// UTableRow no longer emits click, dblclick, clickCell, toggleExpand, or toggleCheckbox events
|
|
389
299
|
|
|
390
300
|
describe("Slots", () => {
|
|
391
301
|
it("Cell Slot – renders custom content from cell slot", () => {
|
|
@@ -550,5 +460,26 @@ describe("UTableRow.vue", () => {
|
|
|
550
460
|
|
|
551
461
|
expect(iconWrapper.exists()).toBe(true);
|
|
552
462
|
});
|
|
463
|
+
|
|
464
|
+
it("isVisible – shows row when isVisible is true", () => {
|
|
465
|
+
const component = mount(UTableRow, {
|
|
466
|
+
props: getDefaultProps({ isVisible: true }),
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const row = component.find("tr");
|
|
470
|
+
const style = row.attributes("style") || "";
|
|
471
|
+
|
|
472
|
+
expect(style).not.toContain("display: none");
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("isVisible – hides row when isVisible is false", () => {
|
|
476
|
+
const component = mount(UTableRow, {
|
|
477
|
+
props: getDefaultProps({ isVisible: false }),
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const row = component.find("tr");
|
|
481
|
+
|
|
482
|
+
expect(row.attributes("style")).toContain("display: none");
|
|
483
|
+
});
|
|
553
484
|
});
|
|
554
485
|
});
|