* chore: move @types/canvas-confetti to devDependencies, remove unused get-tsconfig direct dep * chore: configure knip with workspace entry points for all packages * refactor(shared): split 1119-line types.ts into domain modules under types/ * refactor: remove llm-service.ts shim, migrate all import sites to llm/service directly * refactor(settings): migrate 4 manually-resolved settings into conversion registry * refactor: split gmail-sync.ts into gmail-api, email-router, and thin orchestrator * refactor(orchestrator): extract useKeyboardShortcuts and usePipelineControls from OrchestratorPage Splits the 840-line OrchestratorPage into a thin orchestration shell (~480 lines) by extracting keyboard shortcut handling into useKeyboardShortcuts.ts and pipeline control logic into usePipelineControls.ts. Net negative line count across all files. * feat: create settings registry (Step 1) Introduces a single source of truth for all settings, combining schema definitions, default logic, parsing, and serialization into a single configuration object. * feat: derive schema, keys, and types from settings registry (Step 2) Derives AppSettings nested shape, SettingKey DB union, and updateSettingsSchema Zod shape automatically from the settings registry. * refactor: gut envSettings and remove settings-conversion (Step 3) Replaces manual env arrays with registry-driven maps in envSettings.ts. Deletes settings-conversion.ts since all parsing/defaults now live in the registry. * refactor: simplify getEffectiveSettings with generic loop (Step 4) Replaces ~334 lines of manual key-by-key unpacking with a generic registry-driven iteration loop (~40 lines). Models, typed, string, and virtual kinds are automatically derived. * refactor: simplify settingsUpdateRegistry (Step 5) Replaces ~350 lines of explicit per-key update handlers with a dynamic generic loop over the settings registry, properly routing persistence and side effects. * refactor(settings): implement nested settings registry and clean up tests - Migrate settings system to use a centralized nested registry (`settings-schema.ts`, `registry.ts`) - Remove obsolete flat-to-nested conversion logic (`settings-conversion.ts`) - Address Biome warnings by explicitly ignoring intentional `any` usage in generic runtime schema builder and registry logic - Clean up unused variables in test files (`SettingsPage.test.tsx`) to achieve a 100% green CI pipeline * refactor(settings): address PR comments on env data and registry parsing - Narrow `getEnvSettingsData` return type to `Partial<AppSettings>` to satisfy strict typing and omit 'typed' registry entries - Introduce `parseNonEmptyStringOrNull` for typed string settings so empty-string overrides cleanly fall back to defaults (matching original `||` logic) - Add missing unit tests for registry parse/serialize helpers (JSON, bools, numeric clamping)
106 lines
4.1 KiB
TypeScript
106 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { settingsRegistry } from "./settings-registry";
|
|
|
|
describe("settingsRegistry helpers", () => {
|
|
describe("string parsing (parseNonEmptyStringOrNull)", () => {
|
|
it("returns null for undefined", () => {
|
|
expect(settingsRegistry.model.parse(undefined)).toBeNull();
|
|
});
|
|
|
|
it("returns null for empty string", () => {
|
|
expect(settingsRegistry.searchCities.parse("")).toBeNull();
|
|
});
|
|
|
|
it("returns the string for non-empty string", () => {
|
|
expect(settingsRegistry.searchCities.parse("London")).toBe("London");
|
|
});
|
|
});
|
|
|
|
describe("number parsing and clamping", () => {
|
|
it("returns null for empty/invalid values", () => {
|
|
expect(settingsRegistry.ukvisajobsMaxJobs.parse("")).toBeNull();
|
|
expect(settingsRegistry.ukvisajobsMaxJobs.parse("abc")).toBeNull();
|
|
expect(settingsRegistry.ukvisajobsMaxJobs.parse(undefined)).toBeNull();
|
|
});
|
|
|
|
it("parses valid numbers", () => {
|
|
expect(settingsRegistry.ukvisajobsMaxJobs.parse("42")).toBe(42);
|
|
});
|
|
|
|
it("clamps backupHour to 0-23", () => {
|
|
expect(settingsRegistry.backupHour.parse("25")).toBe(23);
|
|
expect(settingsRegistry.backupHour.parse("-1")).toBe(0);
|
|
expect(settingsRegistry.backupHour.parse("12")).toBe(12);
|
|
});
|
|
|
|
it("clamps backupMaxCount to 1-5", () => {
|
|
expect(settingsRegistry.backupMaxCount.parse("10")).toBe(5);
|
|
expect(settingsRegistry.backupMaxCount.parse("0")).toBe(1);
|
|
expect(settingsRegistry.backupMaxCount.parse("3")).toBe(3);
|
|
});
|
|
|
|
it("clamps missingSalaryPenalty to 0-100", () => {
|
|
expect(settingsRegistry.missingSalaryPenalty.parse("150")).toBe(100);
|
|
expect(settingsRegistry.missingSalaryPenalty.parse("-10")).toBe(0);
|
|
expect(settingsRegistry.missingSalaryPenalty.parse("50")).toBe(50);
|
|
});
|
|
});
|
|
|
|
describe("boolean (bit-bool) parsing and serialization", () => {
|
|
it("parses bit bools correctly", () => {
|
|
expect(settingsRegistry.showSponsorInfo.parse("1")).toBe(true);
|
|
expect(settingsRegistry.showSponsorInfo.parse("true")).toBe(true);
|
|
expect(settingsRegistry.showSponsorInfo.parse("0")).toBe(false);
|
|
expect(settingsRegistry.showSponsorInfo.parse("false")).toBe(false);
|
|
expect(settingsRegistry.showSponsorInfo.parse("")).toBeNull();
|
|
expect(settingsRegistry.showSponsorInfo.parse(undefined)).toBeNull();
|
|
});
|
|
|
|
it("serializes bit bools correctly", () => {
|
|
expect(settingsRegistry.showSponsorInfo.serialize(true)).toBe("1");
|
|
expect(settingsRegistry.showSponsorInfo.serialize(false)).toBe("0");
|
|
expect(settingsRegistry.showSponsorInfo.serialize(null)).toBeNull();
|
|
expect(settingsRegistry.showSponsorInfo.serialize(undefined)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("JSON array parsing", () => {
|
|
it("parses valid JSON arrays", () => {
|
|
expect(settingsRegistry.searchTerms.parse('["dev", "engineer"]')).toEqual(
|
|
["dev", "engineer"],
|
|
);
|
|
});
|
|
|
|
it("returns null for invalid JSON or non-arrays", () => {
|
|
expect(settingsRegistry.searchTerms.parse('{"not": "array"}')).toBeNull();
|
|
expect(settingsRegistry.searchTerms.parse("invalid json")).toBeNull();
|
|
expect(settingsRegistry.searchTerms.parse("")).toBeNull();
|
|
expect(settingsRegistry.searchTerms.parse(undefined)).toBeNull();
|
|
});
|
|
|
|
it("serializes arrays back to JSON", () => {
|
|
expect(settingsRegistry.searchTerms.serialize(["dev", "engineer"])).toBe(
|
|
'["dev","engineer"]',
|
|
);
|
|
expect(settingsRegistry.searchTerms.serialize(null)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("Resume projects settings", () => {
|
|
it("parses and serializes resume projects", () => {
|
|
const obj = {
|
|
maxProjects: 10,
|
|
lockedProjectIds: ["1", "2"],
|
|
aiSelectableProjectIds: ["3"],
|
|
};
|
|
const json = JSON.stringify(obj);
|
|
|
|
expect(settingsRegistry.resumeProjects.parse(json)).toEqual(obj);
|
|
expect(settingsRegistry.resumeProjects.parse("invalid")).toBeNull();
|
|
|
|
expect(settingsRegistry.resumeProjects.serialize(obj)).toBe(json);
|
|
expect(settingsRegistry.resumeProjects.serialize(null)).toBeNull();
|
|
});
|
|
});
|
|
});
|