UNPKG

vueless

Version:

Vue Styleless UI Component Library, powered by Tailwind CSS.

613 lines (472 loc) 18.9 kB
import { mount } from "@vue/test-utils"; import { describe, it, expect } from "vitest"; import { nextTick } from "vue"; import UTable from "../UTable.vue"; import UTableRow from "../UTableRow.vue"; import UEmpty from "../../ui.text-empty/UEmpty.vue"; import ULoaderProgress from "../../ui.loader-progress/ULoaderProgress.vue"; import UDivider from "../../ui.container-divider/UDivider.vue"; import { LoaderProgressSymbol, createLoaderProgress, } from "../../ui.loader-progress/useLoaderProgress.ts"; import type { Column, Row, Props, ColumnObject } from "../types.ts"; describe("UTable.vue", () => { const defaultColumns: ColumnObject[] = [ { key: "name", label: "Name" }, { key: "email", label: "Email" }, { key: "role", label: "Role" }, ]; const defaultRows: Row[] = [ { id: "1", name: "John Doe", email: "john@example.com", role: "Admin", }, { id: "2", name: "Jane Smith", email: "jane@example.com", role: "User", }, { id: "3", name: "Bob Johnson", email: "bob@example.com", role: "Manager", }, ]; const rowsWithDates: Row[] = [ { id: "1", name: "John Doe", email: "john@example.com", role: "Admin", rowDate: "2023-01-01", }, { id: "2", name: "Jane Smith", email: "jane@example.com", role: "User", rowDate: "2023-01-02", }, ]; function getDefaultProps(overrides = {}): Props { return { columns: defaultColumns, rows: defaultRows, ...overrides, }; } function getGlobalOptions() { return { provide: { [LoaderProgressSymbol]: createLoaderProgress(), }, }; } function mountUTable(props: Props, options = {}) { return mount(UTable, { props, global: getGlobalOptions(), ...options, }); } describe("Props", () => { it("Columns – renders table headers correctly", () => { const component = mountUTable(getDefaultProps()); defaultColumns.forEach((column) => { expect(component.text()).toContain(column.label); }); }); it("Columns – handles string columns", () => { const stringColumns: Column[] = ["name", "email", "role"]; const component = mountUTable(getDefaultProps({ columns: stringColumns })); stringColumns.forEach((column) => { expect(component.text()).toContain(column); }); }); it("Columns – applies column thClass to header cells", () => { const columnsWithClasses: ColumnObject[] = [ { key: "name", label: "Name", thClass: "name-header-class" }, { key: "email", label: "Email", thClass: "email-header-class" }, { key: "role", label: "Role" }, ]; const component = mountUTable(getDefaultProps({ columns: columnsWithClasses })); const headerCells = component.findAll("th"); expect(headerCells[0].attributes("class")).toContain(columnsWithClasses[0].thClass); expect(headerCells[1].attributes("class")).toContain(columnsWithClasses[1].thClass); }); it("Columns – hides columns when isShown is false", async () => { const columnsWithHidden: ColumnObject[] = [ { key: "name", label: "Name" }, { key: "email", label: "Email", isShown: false }, { key: "role", label: "Role" }, ]; const component = mountUTable(getDefaultProps({ columns: columnsWithHidden })); const theadElement = component.get("thead"); expect(theadElement.text()).toContain(columnsWithHidden[0].label); expect(theadElement.text()).not.toContain(columnsWithHidden[1].label); expect(theadElement.text()).toContain(columnsWithHidden[2].label); }); it("Rows – renders all table rows", () => { const component = mountUTable(getDefaultProps()); const tableRows = component.findAllComponents(UTableRow); expect(tableRows).toHaveLength(defaultRows.length); }); it("Rows – renders empty state when no rows provided", () => { const component = mountUTable(getDefaultProps({ rows: [] })); const emptyComponent = component.findComponent(UEmpty); expect(emptyComponent.exists()).toBe(true); }); it("Selectable – renders select all checkbox when selectable is true", () => { const component = mountUTable(getDefaultProps({ selectable: true })); const selectAllCheckbox = component.get("thead").find("input"); expect(selectAllCheckbox.exists()).toBe(true); }); it("Selectable – does not render select all checkbox when selectable is false", () => { const component = mountUTable(getDefaultProps({ selectable: false })); const selectAllCheckbox = component.get("thead").find("input"); expect(selectAllCheckbox.exists()).toBe(false); }); it("Selected Rows – displays selection counter", async () => { const selectedRows = [defaultRows[0], defaultRows[1]]; const component = mountUTable(getDefaultProps({ selectable: true, selectedRows })); const theadElement = component.get("thead"); expect(theadElement.text()).toContain(selectedRows.length.toString()); }); it("Selected Rows – passes is checked state to row", async () => { const selectedRows = [defaultRows[0], defaultRows[1]]; const component = mountUTable(getDefaultProps({ selectable: true, selectedRows })); const tableRows = component.findAllComponents(UTableRow); tableRows.forEach((row) => { const currentRowId = row.props("row").id; const isChecked = !!selectedRows.find((selectedRow) => selectedRow.id === currentRowId); expect(row.props("isChecked")).toBe(isChecked); }); }); it("Expanded Rows – passes is expanded state to row", async () => { const expandedRows = [defaultRows[0].id, defaultRows[2].id]; const component = mountUTable(getDefaultProps({ expandedRows })); const tableRows = component.findAllComponents(UTableRow); tableRows.forEach((row) => { const currentRowId = row.props("row").id; expect(row.props("isExpanded")).toBe(expandedRows.includes(currentRowId)); }); }); it("Loading – shows loader when loading is true", () => { const component = mountUTable(getDefaultProps({ loading: true })); const loader = component.findComponent(ULoaderProgress); expect(loader.exists()).toBe(true); expect(loader.props("loading")).toBe(true); }); it("Loading – hides loader when loading is false", () => { const component = mountUTable(getDefaultProps({ loading: false })); const loader = component.findComponent(ULoaderProgress); expect(loader.props("loading")).toBe(false); }); it("Data Test – applies correct data-test attributes", () => { const dataTest = "test-table"; const component = mountUTable(getDefaultProps({ dataTest })); expect(component.attributes("data-test")).toBe(dataTest); }); it("Empty Cell Label – passes empty cell label to table rows", () => { const emptyCellLabel = "No data available"; const component = mountUTable(getDefaultProps({ emptyCellLabel })); const tableRow = component.getComponent(UTableRow); expect(tableRow.props("emptyCellLabel")).toBe(emptyCellLabel); }); it("Date Divider – renders date dividers when dateDivider is true", () => { const component = mountUTable( getDefaultProps({ rows: rowsWithDates, dateDivider: true, }), ); const dividers = component.findAllComponents(UDivider); expect(dividers.length).toBeGreaterThan(0); }); it("Date Divider – renders custom date dividers", () => { const customDividers = [ { date: "2023-01-01", label: "New Year" }, { date: "2023-01-02", label: "Day Two" }, ]; const component = mountUTable( getDefaultProps({ rows: rowsWithDates, dateDivider: customDividers, }), ); expect(component.text()).not.toContain("New Year"); // should not render above first item expect(component.text()).toContain("Day Two"); }); it("Compact – applies compact classes from config", () => { const compactClasses = "px-4 py-3"; const component = mountUTable( getDefaultProps({ compact: true, selectable: true, selectedRows: [defaultRows[0]], }), { slots: { footer: "Footer content", }, }, ); const headerCells = component.findAll("th"); headerCells.forEach((cell) => { expect(cell.attributes("class")).toContain(compactClasses); }); }); }); describe("Slots", () => { it("Header Slot – renders custom header content", () => { const customHeaderContent = "Custom Name Header"; const component = mountUTable(getDefaultProps(), { slots: { "header-name": customHeaderContent, }, }); expect(component.text()).toContain(customHeaderContent); }); it("Header Slot – exposes column and index to slot", () => { const component = mountUTable(getDefaultProps(), { slots: { "header-name": "Column: {{ params.column?.label }}, Index: {{ params.index }}", }, }); expect(component.text()).toContain("Column: Name"); expect(component.text()).toContain("Index: 0"); }); it("Cell Slot – renders custom cell content", () => { const customCellContent = "Custom Cell Content"; const component = mountUTable(getDefaultProps(), { slots: { "cell-name": customCellContent, }, }); expect(component.text()).toContain(customCellContent); }); it("Cell Slot – exposes value, row, index, and cellIndex to slot", () => { const component = mountUTable(getDefaultProps(), { slots: { "cell-name": ` Value: {{ params.value }}, Row: {{ params.row.id }} Index: {{ params.index }}, Cell: {{ params.cellIndex }} `, }, }); expect(component.text()).toContain("Value: John Doe"); expect(component.text()).toContain("Row: 1"); expect(component.text()).toContain("Index: 0"); expect(component.text()).toContain("Cell: 0"); }); it("Expand Slot – renders custom expand content", () => { const nestedRows: Row[] = [ { id: "1", name: "Parent Row", email: "parent@example.com", role: "Admin", row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], }, ]; const customExpandContent = "Custom Expand Button"; const component = mountUTable(getDefaultProps({ rows: nestedRows }), { slots: { expand: customExpandContent, }, }); expect(component.text()).toContain(customExpandContent); }); it("Before First Row Slot – renders content before first row", () => { const customContent = "Before First Row Content"; const component = mountUTable(getDefaultProps(), { slots: { "before-first-row": customContent, }, }); expect(component.text()).toContain(customContent); }); it("Header Actions Slot – renders header actions when rows are selected", async () => { const customActions = "Custom Actions"; const component = mountUTable( getDefaultProps({ selectable: true, selectedRows: [defaultRows[0]], }), { slots: { "header-actions": customActions, }, }, ); await nextTick(); expect(component.text()).toContain(customActions); }); it("Footer Slot – renders custom footer content", () => { const customFooterContent = "Custom Footer"; const component = mountUTable(getDefaultProps(), { slots: { footer: customFooterContent, }, }); expect(component.text()).toContain(customFooterContent); }); it("Nested Row Slot – renders custom nested row content", async () => { const nestedRows: Row[] = [ { id: "1", name: "Parent Row", email: "parent@example.com", role: "Admin", row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], }, ]; const customNestedContent = "Custom Nested Content"; const component = mountUTable( getDefaultProps({ rows: nestedRows, expandedRows: ["1"], }), { slots: { "nested-row": customNestedContent, }, }, ); await nextTick(); expect(component.text()).toContain(customNestedContent); }); }); describe("Events", () => { it("Click Row – emits clickRow event when row is clicked", async () => { const component = mountUTable(getDefaultProps()); const firstRow = component.find("tbody tr"); await firstRow.trigger("click"); expect(component.emitted("clickRow")).toBeTruthy(); expect(component.emitted("clickRow")![0][0]).toEqual(defaultRows[0]); }); it("Double Click Row – emits doubleClickRow event when row is double-clicked", async () => { const component = mountUTable(getDefaultProps()); const firstRow = component.find("tbody tr"); await firstRow.trigger("dblclick"); expect(component.emitted("doubleClickRow")).toBeTruthy(); expect(component.emitted("doubleClickRow")![0][0]).toEqual(defaultRows[0]); }); it("Click Cell – emits clickCell event when cell is clicked", async () => { const component = mountUTable(getDefaultProps()); const firstCell = component.find("tbody tr td"); await firstCell.trigger("click"); expect(component.emitted("clickCell")).toBeTruthy(); expect(component.emitted("clickCell")![0]).toEqual([ defaultRows[0].name, defaultRows[0], "name", ]); }); it("Toggle Row Checkbox – emits update:selectedRows when row checkbox is clicked", async () => { const component = mountUTable(getDefaultProps({ selectable: true })); const rowCheckbox = component.find("tbody tr").find("input[type='checkbox']"); await rowCheckbox.trigger("change"); expect(component.emitted("update:selectedRows")).toBeTruthy(); expect(component.emitted("update:selectedRows")![0][0]).toEqual([defaultRows[0]]); }); it("Toggle Expand – emits row-expand and update:expandedRows when expand icon is clicked", async () => { const expandableRow: Row = { id: "1", name: "Parent Row", email: "parent@example.com", role: "Admin", row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], }; const component = mountUTable(getDefaultProps({ rows: [expandableRow] })); const expandIcon = component.find("[data-row-toggle-icon='1']"); await expandIcon.trigger("click"); expect(component.emitted("row-expand")).toBeTruthy(); expect(component.emitted("row-expand")![0][0]).toEqual(expandableRow); expect(component.emitted("update:expandedRows")).toBeTruthy(); expect(component.emitted("update:expandedRows")![0][0]).toEqual(["1"]); }); it("Toggle Expand – emits row-collapse and update:expandedRows when collapse icon is clicked", async () => { const expandableRow: Row = { id: "1", name: "Parent Row", email: "parent@example.com", role: "Admin", row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], }; const component = mountUTable( getDefaultProps({ rows: [expandableRow], expandedRows: ["1"], }), ); const collapseIcon = component.find("[data-row-toggle-icon='1']"); await collapseIcon.trigger("click"); expect(component.emitted("row-collapse")).toBeTruthy(); expect(component.emitted("row-collapse")![0][0]).toEqual(expandableRow); expect(component.emitted("update:expandedRows")).toBeTruthy(); expect(component.emitted("update:expandedRows")![0][0]).toEqual([]); }); it("Multiple Row Selection – emits update:selectedRows with all selected rows", async () => { const component = mountUTable(getDefaultProps({ selectable: true })); const tableRows = component.findAll("tbody tr"); // Select first row await tableRows[0].find("input[type='checkbox']").trigger("change"); // Select second row await tableRows[1].find("input[type='checkbox']").trigger("change"); const emittedEvents = component.emitted("update:selectedRows"); expect(emittedEvents).toBeTruthy(); expect(emittedEvents![emittedEvents!.length - 1][0]).toEqual([ defaultRows[0], defaultRows[1], ]); }); it("Nested Row Expansion – emits update:expandedRows for nested rows", async () => { const nestedRows: Row[] = [ { id: "1", name: "Parent", email: "parent@example.com", role: "Admin", row: [ { id: "1-1", name: "Child 1", email: "child1@example.com", role: "User", row: [ { id: "1-1-1", name: "Grandchild", email: "grandchild@example.com", role: "Guest" }, ], }, ], }, ]; const component = mountUTable(getDefaultProps({ rows: nestedRows })); const expandIcon = component.find("[data-row-toggle-icon='1']"); await expandIcon.trigger("click"); expect(component.emitted("update:expandedRows")).toBeTruthy(); expect(component.emitted("update:expandedRows")![0][0]).toEqual(["1"]); }); it("Expand Icon – prevents row click events when expand icon is clicked", async () => { const expandableRow: Row = { id: "1", name: "Parent Row", email: "parent@example.com", role: "Admin", row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], }; const component = mountUTable(getDefaultProps({ rows: [expandableRow] })); const expandIcon = component.find("[data-row-toggle-icon='1']"); await expandIcon.trigger("click"); await expandIcon.trigger("dblclick"); // Row click events should not be triggered when expand icon is clicked expect(component.emitted("clickRow")).toBeFalsy(); expect(component.emitted("doubleClickRow")).toBeFalsy(); }); }); });