diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/Table.svelte | 42 | ||||
| -rw-r--r-- | src/components/Table.test.ts | 35 |
2 files changed, 77 insertions, 0 deletions
diff --git a/src/components/Table.svelte b/src/components/Table.svelte new file mode 100644 index 0000000..7c56e69 --- /dev/null +++ b/src/components/Table.svelte @@ -0,0 +1,42 @@ +<script lang="ts"> + // Generic, purely presentational table. Props in → markup out; zero logic, + // zero data-fetching. Shared by the surface custom-field "table" renderer and + // the frontend "Loaded Modules" view, so neither feature depends on the other. + let { + columns, + rows, + empty = "No data", + }: { + readonly columns: readonly string[]; + readonly rows: readonly (readonly string[])[]; + /** Text shown when there are no rows. */ + readonly empty?: string; + } = $props(); +</script> + +<div class="overflow-x-auto"> + <table class="table table-sm"> + <thead> + <tr> + {#each columns as col, i (i)} + <th>{col}</th> + {/each} + </tr> + </thead> + <tbody> + {#if rows.length === 0} + <tr> + <td colspan={Math.max(columns.length, 1)} class="opacity-60">{empty}</td> + </tr> + {:else} + {#each rows as row, r (r)} + <tr> + {#each row as cell, c (c)} + <td>{cell}</td> + {/each} + </tr> + {/each} + {/if} + </tbody> + </table> +</div> diff --git a/src/components/Table.test.ts b/src/components/Table.test.ts new file mode 100644 index 0000000..9fbecd3 --- /dev/null +++ b/src/components/Table.test.ts @@ -0,0 +1,35 @@ +import { render, screen, within } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; +import Table from "./Table.svelte"; + +describe("Table", () => { + it("renders a header cell per column", () => { + render(Table, { props: { columns: ["Name", "Version"], rows: [] } }); + const headers = screen.getAllByRole("columnheader"); + expect(headers.map((h) => h.textContent)).toEqual(["Name", "Version"]); + }); + + it("renders one row per data row with aligned cells", () => { + render(Table, { + props: { + columns: ["Name", "Version"], + rows: [ + ["alpha", "1.0"], + ["beta", "2.3"], + ], + }, + }); + const body = screen.getAllByRole("rowgroup")[1]; + if (body === undefined) throw new Error("expected a tbody rowgroup"); + const rows = within(body).getAllByRole("row"); + expect(rows).toHaveLength(2); + expect(within(rows[0] as HTMLElement).getByText("alpha")).toBeInTheDocument(); + expect(within(rows[0] as HTMLElement).getByText("1.0")).toBeInTheDocument(); + expect(within(rows[1] as HTMLElement).getByText("beta")).toBeInTheDocument(); + }); + + it("shows the empty message when there are no rows", () => { + render(Table, { props: { columns: ["A"], rows: [], empty: "Nothing loaded" } }); + expect(screen.getByText("Nothing loaded")).toBeInTheDocument(); + }); +}); |
