mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
The filter derefed item.text.toLowerCase() from a cast API response on the same line that already guards g.items ?? [], so one item with a null or missing text threw TypeError and the whole command exited 1 as API_ERROR. The filter is extracted into an exported filterAutocompleteGroups (the jobnet testability pattern), text is typed nullable so the compiler enforces the guard, and an item without usable text is skipped: it can never match the required non-empty query, so downstream output never sees one. The null-text case was verified to fail against the verbatim unguarded extraction with the production TypeError. Closes out the #416/#418 audit.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { filterAutocompleteGroups } from "../src/commands/autocomplete";
|
||||
|
||||
function groups() {
|
||||
return [
|
||||
{
|
||||
title: "Stillingsbetegnelser",
|
||||
items: [
|
||||
{ id: "1", text: "Data Engineer", value: 11, category: "title", slug: "data-engineer" },
|
||||
{ id: "2", text: "Dataanalytiker", value: 12, category: "title", slug: "dataanalytiker" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Kategorier",
|
||||
items: [{ id: "3", text: "Marketing", value: 21, category: "category", slug: "marketing" }],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
describe("jobdanmark autocomplete filtering", () => {
|
||||
test("keeps only items matching the query, drops empty groups", () => {
|
||||
const out = filterAutocompleteGroups(groups(), "data");
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].items.map((i) => i.text)).toEqual(["Data Engineer", "Dataanalytiker"]);
|
||||
});
|
||||
|
||||
test("tolerates a group with missing items (pins the existing ?? [] guard)", () => {
|
||||
const g = groups();
|
||||
// @ts-expect-error - the cast API response can omit fields the interface promises
|
||||
delete g[1].items;
|
||||
expect(filterAutocompleteGroups(g, "data")).toHaveLength(1);
|
||||
});
|
||||
|
||||
// The API response reaches this code through a bare type cast
|
||||
// (apiFetch<AutocompleteGroup[]>), so an item without text arrives typed as
|
||||
// if it had one. The unguarded filter threw TypeError from
|
||||
// item.text.toLowerCase() and the whole command died as API_ERROR (#421).
|
||||
// An item with no usable text can never match the (required, non-empty)
|
||||
// query, so it must simply be skipped.
|
||||
test("skips an item with null text instead of crashing the command", () => {
|
||||
const g = groups();
|
||||
g[0].items.push({ id: "4", text: null as unknown as string, value: 13, category: "title", slug: "x" });
|
||||
|
||||
const out = filterAutocompleteGroups(g, "data");
|
||||
|
||||
expect(out[0].items.map((i) => i.slug)).toEqual(["data-engineer", "dataanalytiker"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user