Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: drop an unused export and test the classifier settings logic

The runtime list was exported but only its type was used, so the type is
now written out. The form's pure logic (draft, checks, request body,
change detection) gets tests under node's built-in runner, run by the
existing swarm-ui check.
This commit is contained in:
iris 2026-10-14 14:51:45 +02:00 • committed by mara
commit 93ae435287
4 changed files with 126 additions and 3 deletions

View file

@ -6,7 +6,8 @@
"type": "module",
"scripts": {
"build": "node ./build.mjs",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"test": "node --test test/*.test.mjs"
},
"dependencies": {
"@hive/shared": "*",

View file

@ -8,8 +8,7 @@ export type Effort = (typeof EFFORTS)[number];
/** Which runtime a classification runs on: the claude CLI on the Anthropic
* API key, or opencode on an ACP provider. */
export const RUNTIMES = ["api-key", "acp"] as const;
export type Runtime = (typeof RUNTIMES)[number];
export type Runtime = "api-key" | "acp";
/** What the provider charges, in USD per million tokens. A cache price left
* out counts as `input`. */

View file

@ -0,0 +1,119 @@
// The classifier settings form's pure logic: the draft a stored document
// becomes, the checks before a save, the request body, and what counts as a
// change. Run by `npm test`, which Node runs with its built-in test runner and
// type stripping, so the `.ts` module is imported as it is.
import assert from "node:assert/strict";
import { test } from "node:test";
import {
changed,
draftOf,
maxValue,
priceValue,
settingsProblem,
toSettings,
} from "../src/pages/review-settings/SettingsTypes.ts";
const apiKey = {
model: "haiku",
effort: null,
max_concurrent_reviews: 2,
runtime: "api-key",
acp: null,
};
const acpStored = {
...apiKey,
runtime: "acp",
acp: {
base_url: "https://llm.example/v1",
model: "Qwen/Qwen3-35B",
prices: { input: 0.5, output: 2, cache_read: 0.1 },
},
};
// An ACP draft over the API key settings, with every provider field filled.
function acpDraft(over = {}) {
return {
...draftOf(apiKey),
runtime: "acp",
acpUrl: "https://llm.example/v1",
acpModel: "Qwen/Qwen3-35B",
priceInput: "0.5",
priceOutput: "2",
...over,
};
}
test("an unedited draft is valid and changes nothing", () => {
const d = draftOf(apiKey);
assert.equal(settingsProblem(d), null);
assert.equal(changed(d, apiKey), false);
});
test("the API key request leaves the provider out, so the stored one stays", () => {
const body = toSettings({ ...draftOf(acpStored), runtime: "api-key" });
assert.deepEqual(body, {
model: "haiku",
effort: null,
max_concurrent_reviews: 2,
runtime: "api-key",
});
assert.equal("acp" in body, false);
});
test("the ACP request carries the provider and its prices", () => {
const body = toSettings(acpDraft({ priceCacheRead: "0.1" }));
assert.equal(body.runtime, "acp");
assert.deepEqual(body.acp, {
base_url: "https://llm.example/v1",
model: "Qwen/Qwen3-35B",
prices: { input: 0.5, output: 2, cache_read: 0.1 },
});
});
test("a cache price left empty is left out of the request", () => {
const { prices } = toSettings(acpDraft()).acp;
assert.equal("cache_read" in prices, false);
assert.equal("cache_write" in prices, false);
});
test("the draft of a stored provider round-trips to the same request", () => {
const body = toSettings(draftOf(acpStored));
assert.deepEqual(body.acp, acpStored.acp);
assert.equal(changed(draftOf(acpStored), acpStored), false);
});
test("the provider fields are checked only on the ACP runtime", () => {
const empty = { ...draftOf(apiKey), acpUrl: "http://nope" };
assert.equal(settingsProblem(empty), null);
assert.match(settingsProblem({ ...empty, runtime: "acp" }), /https:\/\//);
});
test("the ACP checks name the field at fault", () => {
assert.match(settingsProblem(acpDraft({ acpUrl: "http://x" })), /https:\/\//);
assert.match(settingsProblem(acpDraft({ acpModel: "a b" })), /model/);
assert.match(settingsProblem(acpDraft({ priceOutput: "-1" })), /price/);
assert.match(settingsProblem(acpDraft({ priceInput: "" })), /price/);
assert.match(settingsProblem(acpDraft({ priceCacheWrite: "x" })), /cache/);
assert.equal(settingsProblem(acpDraft()), null);
});
test("switching the runtime is a change; edits to the hidden provider are not", () => {
const saved = apiKey;
assert.equal(changed({ ...draftOf(saved), runtime: "acp" }, saved), true);
assert.equal(
changed({ ...draftOf(saved), acpUrl: "https://x" }, saved),
false,
);
assert.equal(changed(acpDraft({ priceOutput: "3" }), acpStored), true);
});
test("the reviews limit and a price parse as the form needs", () => {
assert.equal(maxValue("8"), 8);
assert.equal(maxValue("9"), null);
assert.equal(maxValue("1.5"), null);
assert.equal(priceValue("0"), 0);
assert.equal(priceValue("0.25"), 0.25);
assert.equal(priceValue("-1"), null);
assert.equal(priceValue("1e3"), null);
});

View file

@ -369,6 +369,10 @@ in
buildPhase = ''
runHook preBuild
npm run typecheck --workspace=packages/swarm-ui
# The package's own tests (`test/`, Node's built-in runner) ride this
# derivation: it already has the workspace installed, and a second
# `npm ci` for the same tree would only repeat it.
npm run test --workspace=packages/swarm-ui
runHook postBuild
'';