Files
template-app/test/live-writes.test.ts
2026-11-14 09:56:45 +00:00

82 lines
3.6 KiB
TypeScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// A write never reloads to show itself (AGENTS.md, "The recipe for a module", steps 5 and 9).
//
// - No module reloads the document: `location.reload()` throws the page's state away and reads every
// loader again, where a live read changes only what changed.
// - A module that declares a live collection writes through `useWrite`, which resolves once the
// organization's live stream has seen the write, so every live read on the page already shows it.
// Its UI re-runs no loader after a write (`usePageNavigation().refresh()`, `router.invalidate()`):
// a page of it reads through `useLive` or a `useRead` kept live by the module's entities.
//
// Which modules declare a collection comes from their own manifests, every module whose source is
// here, selected or not: a module that is switched on later is held to the same rule.
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
import { join, relative } from "node:path"
import type { ModuleManifest } from "@conusai-kit/core"
import { describe, expect, it } from "vitest"
const root = new URL("..", import.meta.url).pathname
const modulesDir = join(root, "modules")
const RELOAD = /\blocation\s*(?:\.\s*reload\b|\[\s*["'`]reload["'`]\s*\])/
const LOADER_RERUN = /\.\s*refresh\s*\(|\brouter\s*\.\s*invalidate\s*\(/
function sources(dir: string): string[] {
if (!existsSync(dir)) return []
return readdirSync(dir).flatMap((name) => {
const path = join(dir, name)
if (statSync(path).isDirectory()) return name === "node_modules" || name === "dist" ? [] : sources(path)
return /\.(?:ts|tsx|mts)$/.test(name) ? [path] : []
})
}
const moduleIds = readdirSync(modulesDir).filter((name) => existsSync(join(modulesDir, name, "index.ts")))
const isManifest = (value: unknown): value is ModuleManifest =>
typeof value === "object" && value !== null && "id" in value && "procedures" in value
/** The ids of the source modules whose manifests declare at least one live collection. */
async function modulesWithCollections(): Promise<string[]> {
const found = new Set<string>()
for (const id of moduleIds) {
const exports = (await import(join(modulesDir, id, "index.ts"))) as Record<string, unknown>
for (const value of Object.values(exports)) {
if (isManifest(value) && (value.collections?.length ?? 0) > 0) found.add(id)
}
}
return [...found].sort()
}
/** `file:line: text` for each line of `files` that matches `pattern`, outside comments. */
function offending(files: readonly string[], pattern: RegExp): string[] {
return files.flatMap((file) =>
readFileSync(file, "utf8")
.split("\n")
.flatMap((line, index) => {
const code = line.replace(/\/\/.*$/, "")
return pattern.test(code) ? [`${relative(root, file)}:${index + 1}: ${line.trim()}`] : []
}),
)
}
describe("a write never reloads to show itself", () => {
it("no module reloads the document", () => {
expect(offending(sources(modulesDir), RELOAD)).toEqual([])
})
// kit:notes:begin
it("finds the modules that declare live collections from their manifests", async () => {
// notes is the worked example; if it stops declaring one, the next test proves nothing about it.
expect(await modulesWithCollections()).toContain("notes")
})
// kit:notes:end
it("a module with a live collection re-runs no loader after a write", async () => {
const problems = (await modulesWithCollections()).flatMap((id) =>
offending(sources(join(modulesDir, id, "ui")), LOADER_RERUN),
)
expect(problems, "write with useWrite and read with useLive or a live useRead").toEqual([])
}, 120_000)
})