1
0
forked from rosa/hakurei

19 Commits

Author SHA1 Message Date
Kat
58b879f48f TODO: docs maybe?? 2026-03-23 06:30:33 +11:00
Kat
252e9a4efc TODO: actually write tests lol 2026-03-23 06:30:33 +11:00
Kat
7cba25eb40 TODO: relocate test code 2026-03-23 06:30:33 +11:00
Kat
20cfb8201c cmd/pkgserver: aria-describe test node summary with state
The summary marker does not appear in the AOM, so setting its alt text
is fruitless.
2026-03-23 06:30:32 +11:00
Kat
4385922bb8 cmd/pkgserver: provide role descriptions for test nodes in web UI 2026-03-23 06:30:32 +11:00
Kat
82c73c36cf cmd/pkgserver: fix dark mode in test web UI 2026-03-23 06:30:32 +11:00
Kat
dc883c9c49 cmd/pkgserver: serialize JS enums as ints instead of strings 2026-03-23 06:30:32 +11:00
Kat
6888033e8d cmd/pkgserver: set exit code when running JS tests from CLI 2026-03-23 06:30:32 +11:00
Kat
7c39b32470 cmd/pkgserver: expose verbose StreamReporter flag via CLI 2026-03-23 06:30:32 +11:00
Kat
52b9b7754a cmd/pkgserver: implement skipping JS tests from the DSL 2026-03-23 06:30:32 +11:00
Kat
0d0c659649 cmd/pkgserver: allow non-global JS test suites 2026-03-23 06:30:32 +11:00
Kat
47962b1f55 cmd/pkgserver: serialize raw log list for go test consumption 2026-03-23 06:30:32 +11:00
Kat
f160d1ab7f cmd/pkgserver: add JSON reporter to facilitate go test integration 2026-03-23 06:30:32 +11:00
Kat
9fbd26217a cmd/pkgserver: fix multi-line JS test output display 2026-03-23 06:30:32 +11:00
Kat
79e0bacf76 cmd/pkgserver: implement JS test DSL and runner 2026-03-23 06:30:32 +11:00
Kat
d552b98d42 cmd/pkgserver: move StreamReporter display() to Reporter interface 2026-03-23 06:30:32 +11:00
Kat
795f27205c cmd/pkgserver: add DOM reporter for JS tests 2026-03-23 06:30:32 +11:00
Kat
19857a02dc cmd/pkgserver: add basic CLI reporter for testing JS 2026-03-23 06:30:32 +11:00
Kat
6fffc4ccfc cmd/pkgserver: enable TypeScript's strict mode 2026-03-23 06:30:32 +11:00
4 changed files with 42 additions and 24 deletions

View File

@@ -1,4 +1,10 @@
class PackageIndexEntry {
function assertGetElementById(id: string): HTMLElement {
let elem = document.getElementById(id)
if(elem == null) throw new ReferenceError(`element with ID '${id}' missing from DOM`)
return elem
}
interface PackageIndexEntry {
name: string
size: number | null
description: string | null
@@ -34,7 +40,7 @@ function toByteSizeString(bytes: number): string {
const API_VERSION = 1
const ENDPOINT = `/api/v${API_VERSION}`
class InfoPayload {
interface InfoPayload {
count: number
hakurei_version: string
}
@@ -42,9 +48,9 @@ class InfoPayload {
async function infoRequest(): Promise<InfoPayload> {
const res = await fetch(`${ENDPOINT}/info`)
const payload = await res.json()
return payload as InfoPayload
return payload
}
class GetPayload {
interface GetPayload {
values: PackageIndexEntry[]
}
@@ -57,7 +63,7 @@ enum SortOrders {
async function getRequest(limit: number, index: number, sort: SortOrders): Promise<GetPayload> {
const res = await fetch(`${ENDPOINT}/get?limit=${limit}&index=${index}&sort=${sort.valueOf()}`)
const payload = await res.json()
return payload as GetPayload
return payload
}
class State {
entriesPerPage: number = 10
@@ -96,16 +102,16 @@ class State {
}
updatePage() {
let page = Math.ceil(((this.getEntryIndex() + this.getEntriesPerPage()) - 1) / this.getEntriesPerPage())
document.getElementById("page-number").innerText = String(page)
assertGetElementById("page-number").innerText = String(page)
}
updateRange() {
let max = Math.min(this.getEntryIndex() + this.getEntriesPerPage(), this.getMaxEntries())
document.getElementById("entry-counter").innerText = `${this.getEntryIndex() + 1}-${max} of ${this.getMaxEntries()}`
assertGetElementById("entry-counter").innerText = `${this.getEntryIndex() + 1}-${max} of ${this.getMaxEntries()}`
}
updateListings() {
getRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSortOrder())
.then(res => {
let table = document.getElementById("pkg-list")
let table = assertGetElementById("pkg-list")
table.innerHTML = ''
res.values.forEach((row) => {
table.appendChild(toHTML(row))
@@ -141,15 +147,15 @@ document.addEventListener("DOMContentLoaded", () => {
infoRequest()
.then(res => {
STATE.setMaxEntries(res.count)
document.getElementById("hakurei-version").innerText = res.hakurei_version
assertGetElementById("hakurei-version").innerText = res.hakurei_version
STATE.updateRange()
STATE.updateListings()
})
document.getElementById("count").addEventListener("change", (event) => {
assertGetElementById("count").addEventListener("change", (event) => {
STATE.setEntriesPerPage(parseInt((event.target as HTMLSelectElement).value))
})
document.getElementById("sort").addEventListener("change", (event) => {
assertGetElementById("sort").addEventListener("change", (event) => {
STATE.setSortOrder(parseInt((event.target as HTMLSelectElement).value))
})
})
})

View File

@@ -10,8 +10,8 @@ import { StreamReporter, TESTS } from "./test.js";
// TypeScript doesn't like process and Deno as their type definitions aren't
// installed, but doesn't seem to complain if they're accessed through
// globalThis.
const process: any = globalThis.process;
const Deno: any = globalThis.Deno;
const process: any = (globalThis as any).process;
const Deno: any = (globalThis as any).Deno;
function getArgs(): string[] {
if (process) {

View File

@@ -3,7 +3,7 @@
type TestTree = TestGroup | Test;
type TestGroup = { name: string, children: TestTree[] };
type Test = { name: string, test: (TestController) => void };
type Test = { name: string, test: (t: TestController) => void };
export class TestRegistrar {
#suites: TestGroup[];
@@ -39,7 +39,7 @@ export function context(name: string, children: TestTree[]): TestTree {
}
export const group = context;
export function test(name: string, test: (TestController) => void): TestTree {
export function test(name: string, test: (t: TestController) => void): TestTree {
return { name, test };
}
@@ -137,7 +137,7 @@ function extractExceptionString(e: any): string {
// String() instead of .toString() as null and undefined don't have
// properties.
const s = String(e);
if (!(e instanceof Error && "stack" in e)) return s;
if (!(e instanceof Error && e.stack)) return s;
// v8 (Chromium, NodeJS) include the error message, while Firefox and WebKit
// do not.
if (e.stack.startsWith(s)) return e.stack;
@@ -241,9 +241,10 @@ export class StreamReporter implements Reporter {
// into { "a b": ["c", "d"] }.
let pathMap = new Map<string, ({ name: string } & TestResult)[]>();
for (const t of data) {
if (t.path.length === 0) throw new RangeError("path is empty");
const key = t.path.slice(0, -1).join(SEP);
if (!pathMap.has(key)) pathMap.set(key, []);
pathMap.get(key).push({ name: t.path.at(-1), ...t });
pathMap.get(key)!.push({ name: t.path.at(-1)!, ...t });
}
this.stream.writeln("");
@@ -280,22 +281,31 @@ export class StreamReporter implements Reporter {
}
}
function throwNotInDOMError(id: string): never {
throw new ReferenceError(`element with ID '${id}' missing from DOM`);
}
export class DOMReporter implements Reporter {
register(suites: TestGroup[]) {}
update(path: string[], result: TestResult) {
if (path.length === 0) throw new RangeError("path is empty");
if (result.state === "skip") {
document.getElementById("skip-counter-text").classList.remove("hidden");
const text = document.getElementById("skip-counter-text");
if (!text) throwNotInDOMError("skip-counter-text");
text.classList.remove("hidden");
}
const counter = document.getElementById(`${result.state}-counter`);
if (!counter) throwNotInDOMError(`${result.state}-counter`);
counter.innerText = (Number(counter.innerText) + 1).toString();
let parent = document.getElementById("root");
if (!parent) throwNotInDOMError("root");
for (const node of path) {
let child: HTMLDetailsElement;
let summary: HTMLElement;
outer: for (const d of parent.children) {
let child: HTMLDetailsElement | null = null;
let summary: HTMLElement | null = null;
let d: Element;
outer: for (d of parent.children) {
if (!(d instanceof HTMLDetailsElement)) continue;
for (const s of d.children) {
if (!(s instanceof HTMLElement)) continue;
@@ -315,6 +325,7 @@ export class DOMReporter implements Reporter {
child.appendChild(summary);
parent.appendChild(child);
}
if (!summary) throw new Error("unreachable as assigned above");
switch (result.state) {
case "failure":
@@ -358,7 +369,7 @@ export class DOMReporter implements Reporter {
interface GoNode {
name: string;
subtests?: GoNode[];
subtests: GoNode[] | null;
}
// Used to display results via `go test`, via some glue code from the Go side.

View File

@@ -1,5 +1,6 @@
{
"compilerOptions": {
"strict": true,
"target": "ES2024"
}
}
}