vueless 1.3.9-beta.8 → 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.
@@ -291,6 +291,280 @@ describe("UTable.vue", () => {
291
291
  expect(cell.attributes("class")).toContain(compactClasses);
292
292
  });
293
293
  });
294
+
295
+ it("Virtual Scroll – is disabled by default", () => {
296
+ const component = mountUTable(getDefaultProps());
297
+
298
+ expect(component.vm.$props.virtualScroll).toBe(false);
299
+ });
300
+
301
+ it("Virtual Scroll – enables virtual scrolling when virtualScroll is true", () => {
302
+ const component = mountUTable(
303
+ getDefaultProps({
304
+ virtualScroll: true,
305
+ }),
306
+ );
307
+
308
+ expect(component.vm.$props.virtualScroll).toBe(true);
309
+
310
+ // Check that the table wrapper has virtual scroll classes
311
+ const tableWrapper = component.find("table").element.parentElement;
312
+
313
+ expect(tableWrapper?.className).toContain("overflow-y-auto");
314
+ });
315
+
316
+ it("Virtual Scroll – renders spacer rows when virtualScroll is enabled", async () => {
317
+ const manyRows = Array.from({ length: 100 }, (_, i) => ({
318
+ id: String(i + 1),
319
+ name: `User ${i + 1}`,
320
+ email: `user${i + 1}@example.com`,
321
+ role: "User",
322
+ }));
323
+
324
+ const component = mountUTable(
325
+ getDefaultProps({
326
+ rows: manyRows,
327
+ virtualScroll: true,
328
+ rowHeight: 40,
329
+ }),
330
+ );
331
+
332
+ await nextTick();
333
+
334
+ const allRows = component.findAll("tbody tr");
335
+ const spacerRows = allRows.filter((row) => {
336
+ const td = row.find("td");
337
+ const hasTableRow = row.findComponent(UTableRow).exists();
338
+
339
+ return (
340
+ td.exists() &&
341
+ td.attributes("colspan") &&
342
+ td.attributes("style")?.includes("height") &&
343
+ !hasTableRow
344
+ );
345
+ });
346
+
347
+ // Should have at least one spacer row (top or bottom)
348
+ expect(spacerRows.length).toBeGreaterThanOrEqual(1);
349
+ });
350
+
351
+ it("Row Height – uses default rowHeight value", () => {
352
+ const component = mountUTable(getDefaultProps());
353
+
354
+ expect(component.vm.$props.rowHeight).toBe(40);
355
+ });
356
+
357
+ it("Row Height – accepts custom rowHeight value", () => {
358
+ const customRowHeight = 60;
359
+
360
+ const component = mountUTable(
361
+ getDefaultProps({
362
+ rowHeight: customRowHeight,
363
+ }),
364
+ );
365
+
366
+ expect(component.vm.$props.rowHeight).toBe(customRowHeight);
367
+ });
368
+
369
+ it("Buffer Size – uses default bufferSize value", () => {
370
+ const component = mountUTable(getDefaultProps());
371
+
372
+ expect(component.vm.$props.bufferSize).toBe(10);
373
+ });
374
+
375
+ it("Buffer Size – accepts custom bufferSize value", () => {
376
+ const customBufferSize = 20;
377
+
378
+ const component = mountUTable(
379
+ getDefaultProps({
380
+ bufferSize: customBufferSize,
381
+ }),
382
+ );
383
+
384
+ expect(component.vm.$props.bufferSize).toBe(customBufferSize);
385
+ });
386
+
387
+ it("Virtual Scroll – renders only visible rows plus buffer", async () => {
388
+ const manyRows = Array.from({ length: 100 }, (_, i) => ({
389
+ id: String(i + 1),
390
+ name: `User ${i + 1}`,
391
+ email: `user${i + 1}@example.com`,
392
+ role: "User",
393
+ }));
394
+
395
+ const component = mountUTable(
396
+ getDefaultProps({
397
+ rows: manyRows,
398
+ virtualScroll: true,
399
+ rowHeight: 40,
400
+ scrollHeight: "400px",
401
+ bufferSize: 5,
402
+ }),
403
+ );
404
+
405
+ await nextTick();
406
+
407
+ const tableRows = component.findAllComponents(UTableRow);
408
+
409
+ // Should render less than total rows (only visible + buffer)
410
+ expect(tableRows.length).toBeLessThan(manyRows.length);
411
+ expect(tableRows.length).toBeGreaterThan(0);
412
+ });
413
+
414
+ it("Virtual Scroll – renders all rows when virtualScroll is false", () => {
415
+ const manyRows = Array.from({ length: 50 }, (_, i) => ({
416
+ id: String(i + 1),
417
+ name: `User ${i + 1}`,
418
+ email: `user${i + 1}@example.com`,
419
+ role: "User",
420
+ }));
421
+
422
+ const component = mountUTable(
423
+ getDefaultProps({
424
+ rows: manyRows,
425
+ virtualScroll: false,
426
+ }),
427
+ );
428
+
429
+ const tableRows = component.findAllComponents(UTableRow);
430
+
431
+ expect(tableRows.length).toBe(manyRows.length);
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
+ });
294
568
  });
295
569
 
296
570
  describe("Slots", () => {
@@ -469,7 +743,7 @@ describe("UTable.vue", () => {
469
743
  await firstRow.trigger("click");
470
744
 
471
745
  expect(component.emitted("clickRow")).toBeTruthy();
472
- expect(component.emitted("clickRow")![0][0]).toEqual(defaultRows[0]);
746
+ expect(component.emitted("clickRow")![0][0]).toMatchObject(defaultRows[0]);
473
747
  });
474
748
 
475
749
  it("Double Click Row – emits doubleClickRow event when row is double-clicked", async () => {
@@ -480,7 +754,7 @@ describe("UTable.vue", () => {
480
754
  await firstRow.trigger("dblclick");
481
755
 
482
756
  expect(component.emitted("doubleClickRow")).toBeTruthy();
483
- expect(component.emitted("doubleClickRow")![0][0]).toEqual(defaultRows[0]);
757
+ expect(component.emitted("doubleClickRow")![0][0]).toMatchObject(defaultRows[0]);
484
758
  });
485
759
 
486
760
  it("Click Cell – emits clickCell event when cell is clicked", async () => {
@@ -491,22 +765,25 @@ describe("UTable.vue", () => {
491
765
  await firstCell.trigger("click");
492
766
 
493
767
  expect(component.emitted("clickCell")).toBeTruthy();
494
- expect(component.emitted("clickCell")![0]).toEqual([
495
- defaultRows[0].name,
496
- defaultRows[0],
497
- "name",
498
- ]);
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");
499
773
  });
500
774
 
501
775
  it("Toggle Row Checkbox – emits update:selectedRows when row checkbox is clicked", async () => {
502
776
  const component = mountUTable(getDefaultProps({ selectable: true }));
503
777
 
504
- const rowCheckbox = component.find("tbody tr").find("input[type='checkbox']");
778
+ const checkboxCell = component.find("tbody tr td[data-checkbox-id]");
505
779
 
506
- await rowCheckbox.trigger("change");
780
+ await checkboxCell.trigger("click");
507
781
 
508
782
  expect(component.emitted("update:selectedRows")).toBeTruthy();
509
- 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]);
510
787
  });
511
788
 
512
789
  it("Toggle Expand – emits row-expand and update:expandedRows when expand icon is clicked", async () => {
@@ -525,7 +802,7 @@ describe("UTable.vue", () => {
525
802
  await expandIcon.trigger("click");
526
803
 
527
804
  expect(component.emitted("row-expand")).toBeTruthy();
528
- expect(component.emitted("row-expand")![0][0]).toEqual(expandableRow);
805
+ expect(component.emitted("row-expand")![0][0]).toMatchObject(expandableRow);
529
806
  expect(component.emitted("update:expandedRows")).toBeTruthy();
530
807
  expect(component.emitted("update:expandedRows")![0][0]).toEqual(["1"]);
531
808
  });
@@ -551,7 +828,7 @@ describe("UTable.vue", () => {
551
828
  await collapseIcon.trigger("click");
552
829
 
553
830
  expect(component.emitted("row-collapse")).toBeTruthy();
554
- expect(component.emitted("row-collapse")![0][0]).toEqual(expandableRow);
831
+ expect(component.emitted("row-collapse")![0][0]).toMatchObject(expandableRow);
555
832
  expect(component.emitted("update:expandedRows")).toBeTruthy();
556
833
  expect(component.emitted("update:expandedRows")![0][0]).toEqual([]);
557
834
  });
@@ -559,20 +836,33 @@ describe("UTable.vue", () => {
559
836
  it("Multiple Row Selection – emits update:selectedRows with all selected rows", async () => {
560
837
  const component = mountUTable(getDefaultProps({ selectable: true }));
561
838
 
562
- const tableRows = component.findAll("tbody tr");
563
-
564
839
  // Select first row
565
- 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
+
566
854
  // Select second row
567
- await tableRows[1].find("input[type='checkbox']").trigger("change");
855
+ await tableRows[1].find("td[data-checkbox-id]").trigger("click");
856
+ await nextTick();
568
857
 
569
858
  const emittedEvents = component.emitted("update:selectedRows");
570
859
 
571
860
  expect(emittedEvents).toBeTruthy();
572
- expect(emittedEvents![emittedEvents!.length - 1][0]).toEqual([
573
- defaultRows[0],
574
- defaultRows[1],
575
- ]);
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]);
576
866
  });
577
867
 
578
868
  it("Nested Row Expansion – emits update:expandedRows for nested rows", async () => {
@@ -626,5 +916,151 @@ describe("UTable.vue", () => {
626
916
  expect(component.emitted("clickRow")).toBeFalsy();
627
917
  expect(component.emitted("doubleClickRow")).toBeFalsy();
628
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
+ });
629
1065
  });
630
1066
  });