diff --git a/csv-table-hub-main/src/__tests__/files-refresh.test.ts b/csv-table-hub-main/src/__tests__/files-refresh.test.ts new file mode 100644 index 0000000..2d68f92 --- /dev/null +++ b/csv-table-hub-main/src/__tests__/files-refresh.test.ts @@ -0,0 +1,34 @@ +// src/__tests__/files-refresh.test.ts +// Regression test for #64: after an upload, router.invalidate() re-runs the +// home route loader. The loader must refetch the files list rather than hand +// back the cached one, or the new file only appears after navigating away. + +import { describe, it, expect, vi, afterEach } from "vitest"; +import { QueryClient } from "@tanstack/react-query"; +import { filesQuery, loadFiles } from "@/lib/csv.functions"; + +const file = (name: string) => ({ + id: name, + file_name: name, + table_name: `csv_${name}`, + row_count: 1, + column_names: ["a"], + created_at: "2026-10-26T00:00:00Z", +}); + +afterEach(() => vi.unstubAllGlobals()); + +describe("loadFiles", () => { + it("refetches the files list when the loader re-runs", async () => { + const responses = [[file("a")], [file("a"), file("b")]]; + const fetchMock = vi.fn(async () => new Response(JSON.stringify(responses.shift()))); + vi.stubGlobal("fetch", fetchMock); + const qc = new QueryClient(); + + await loadFiles(qc); // first page load + await loadFiles(qc); // router.invalidate() after an upload + + await vi.waitFor(() => expect(qc.getQueryData(filesQuery.queryKey)).toHaveLength(2)); + expect(fetchMock).toHaveBeenCalledTimes(2); + }); +}); diff --git a/csv-table-hub-main/src/lib/csv.functions.ts b/csv-table-hub-main/src/lib/csv.functions.ts index a638181..f44d79c 100644 --- a/csv-table-hub-main/src/lib/csv.functions.ts +++ b/csv-table-hub-main/src/lib/csv.functions.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import type { QueryClient } from "@tanstack/react-query"; const columnTypeSchema = z.enum(["int8", "numeric", "date", "timestamptz", "boolean", "text"]); export type ColumnType = z.infer; @@ -163,6 +164,13 @@ export async function listCsvFiles(): Promise { return apiFetch("/api/csv/files", csvFileSummaryListSchema); } +export const filesQuery = { queryKey: ["csv-files"], queryFn: listCsvFiles }; + +// Route loader for the files list. revalidateIfStale makes router.invalidate() +// (called after each upload) refetch instead of returning the cached list (#64). +export const loadFiles = (queryClient: QueryClient) => + queryClient.ensureQueryData({ ...filesQuery, revalidateIfStale: true }); + const csvPreviewResultSchema = z.discriminatedUnion("status", [ z.object({ status: z.literal("ok"), diff --git a/csv-table-hub-main/src/routes/_authenticated/index.tsx b/csv-table-hub-main/src/routes/_authenticated/index.tsx index 98727d1..5eb31c3 100644 --- a/csv-table-hub-main/src/routes/_authenticated/index.tsx +++ b/csv-table-hub-main/src/routes/_authenticated/index.tsx @@ -23,7 +23,7 @@ import { createFileRoute, Link, useRouter } from "@tanstack/react-router"; import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; import { - listCsvFiles, previewCsvTable, previewCsv, listTeTables, uploadCsv, + filesQuery, loadFiles, previewCsvTable, previewCsv, listTeTables, uploadCsv, type CsvFileSummary, type ColumnType, type TeTableInfo, } from "@/lib/csv.functions"; import { parseCsvPreview, COLUMN_TYPE_LABEL, sanitizeColumns, type CsvPreview } from "@/lib/csv-preview"; @@ -59,13 +59,9 @@ import { import { Progress } from "@/components/ui/progress"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -// ─── query ─────────────────────────────────────────────────────────────────── - -const filesQuery = { queryKey: ["csv-files"], queryFn: () => listCsvFiles() }; - export const Route = createFileRoute("/_authenticated/")({ head: () => ({ meta: [{ title: "CSV Migrator — Upload CSV files to your database" }, { name: "description", content: "Upload any number of CSV files. Each becomes its own database table, with duplicates automatically skipped." }, { property: "og:title", content: "CSV Migrator" }] }), - loader: ({ context }) => context.queryClient.ensureQueryData(filesQuery), + loader: ({ context }) => loadFiles(context.queryClient), component: Home, });