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.
@@ -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]).toEqual(defaultRows[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]).toEqual(defaultRows[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
- expect(component.emitted("clickCell")![0]).toEqual([
634
- defaultRows[0].name,
635
- defaultRows[0],
636
- "name",
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 rowCheckbox = component.find("tbody tr").find("input[type='checkbox']");
778
+ const checkboxCell = component.find("tbody tr td[data-checkbox-id]");
644
779
 
645
- await rowCheckbox.trigger("change");
780
+ await checkboxCell.trigger("click");
646
781
 
647
782
  expect(component.emitted("update:selectedRows")).toBeTruthy();
648
- expect(component.emitted("update:selectedRows")![0][0]).toEqual([defaultRows[0]]);
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]).toEqual(expandableRow);
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]).toEqual(expandableRow);
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
- await tableRows[0].find("input[type='checkbox']").trigger("change");
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("input[type='checkbox']").trigger("change");
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
- expect(emittedEvents![emittedEvents!.length - 1][0]).toEqual([
712
- defaultRows[0],
713
- defaultRows[1],
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
- const icon = component.findComponent(UIcon);
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
- describe("Events", () => {
290
- it("Click emits click event when row is clicked", async () => {
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
  });