feat: Ollama models command, with load/pull/delete and unload (#16)

This commit was merged in pull request #16.
This commit is contained in:
2026-10-05 09:43:00 +00:00
parent 05fd9ff13b
commit 26e05dea5b
22 changed files with 1948 additions and 65 deletions
+85 -6
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_SYSTEM_PROMPT, RECENCY_HINT } from "../lib/constants";
import { buildMessages, composeSystemPrompt } from "../lib/history";
import { buildMessages, composeSystemPrompt, describeRuntime, type RuntimeContext } from "../lib/history";
import type { ChatTurn } from "../lib/types";
function turn(prompt: string, response: string): ChatTurn {
@@ -52,18 +52,97 @@ describe("buildMessages", () => {
});
});
/** The setup a test is composing for: the user's own machine, which is the case that exposed the gaps. */
function context(overrides: Partial<RuntimeContext> = {}): RuntimeContext {
return {
model: "ornith-1.5:9b",
host: "http://localhost:11434",
selfHosted: true,
webTools: true,
vision: "yes",
...overrides,
};
}
describe("composeSystemPrompt", () => {
it("uses the default prompt when the user has not set one", () => {
expect(composeSystemPrompt("", false)).toBe(DEFAULT_SYSTEM_PROMPT);
expect(composeSystemPrompt("", context())).toContain(DEFAULT_SYSTEM_PROMPT);
});
it("prefers the user's prompt when set", () => {
expect(composeSystemPrompt("Be terse.", false)).toBe("Be terse.");
it("prefers the user's prompt when set, and still says what the model is", () => {
// The runtime facts are not a matter of style, so a custom prompt must not
// drop them. This is the case that would otherwise silently lose them.
const composed = composeSystemPrompt("Be terse.", context());
expect(composed).toContain("Be terse.");
expect(composed).not.toContain(DEFAULT_SYSTEM_PROMPT);
expect(composed).toContain("You are Lam");
});
it("appends the recency hint only when web tools are on", () => {
expect(composeSystemPrompt("Be terse.", true)).toBe(`Be terse.\n\n${RECENCY_HINT}`);
expect(composeSystemPrompt("Be terse.", false)).not.toContain(RECENCY_HINT);
expect(composeSystemPrompt("Be terse.", context({ webTools: true }))).toContain(RECENCY_HINT);
expect(composeSystemPrompt("Be terse.", context({ webTools: false }))).not.toContain(RECENCY_HINT);
});
});
describe("describeRuntime", () => {
it("names the model and the server for a local setup", () => {
const text = describeRuntime(context());
expect(text).toContain("`ornith-1.5:9b`");
expect(text).toContain("http://localhost:11434");
expect(text).toContain("reachable on this machine");
});
it("says Ollama Cloud when that is the host", () => {
const text = describeRuntime(
context({ host: "https://ollama.com", selfHosted: false, model: "gpt-oss:120b" }),
);
expect(text).toContain("Ollama Cloud");
expect(text).not.toContain("reachable on this machine");
});
it("explains the :cloud convention only where it applies", () => {
// A local daemon proxying a cloud model is the case where a model that
// assumed it was local would tell the user their conversation stays put.
expect(describeRuntime(context({ selfHosted: true }))).toContain("`:cloud`");
expect(describeRuntime(context({ selfHosted: false }))).not.toContain("leaves this machine");
});
it("tells it to say so when there are no web tools, rather than offering", () => {
expect(describeRuntime(context({ webTools: true }))).toContain("You have `web_search`");
expect(describeRuntime(context({ webTools: false }))).toContain("no web tools in this session");
});
it("uses the confirmed answer about images when there is one", () => {
expect(describeRuntime(context({ vision: "yes" }))).toContain("You can read images");
expect(describeRuntime(context({ vision: "no" }))).toContain("You cannot read images");
});
it("tells a model whose eyes are unconfirmed to describe what it sees rather than reason about it", () => {
// The reported failure: handed a screenshot and an unconfirmed answer, the
// model wrote two paragraphs about whether it could see instead of looking.
const text = describeRuntime(context({ vision: "unknown" }));
expect(text).toContain("has not been confirmed");
expect(text).toContain("describe what you see");
});
it("always denies access it does not have", () => {
const text = describeRuntime(context());
expect(text).toContain("You see only what the user types or attaches");
expect(text).toContain("cannot read their screen, files, clipboard");
});
it("never claims the model is Vicinae, which was the actual confusion", () => {
// The reported answer: "Vicinae is a desktop launcher for Ollama". Vicinae is
// the launcher; Ollama is what serves the model, and they are not the same.
const text = describeRuntime(context());
expect(text).toContain("running inside the Vicinae launcher");
expect(text).not.toMatch(/You are Vicinae/);
});
});
+450 -1
View File
@@ -1,5 +1,19 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { modelCapabilities, normalizeHost, parseNdjsonStream, supportsVision } from "../lib/ollama";
import {
deleteModel,
describePullProgress,
isSelfHosted,
listModels,
listRunningModels,
modelCapabilities,
normalizeHost,
parseNdjsonStream,
pullModel,
supportsVision,
unloadModel,
webFetch,
webSearch,
} from "../lib/ollama";
/**
* The NDJSON reader is the part of the client most likely to break in a way
@@ -151,3 +165,438 @@ describe("modelCapabilities", () => {
expect(fetchMock.mock.calls[0][0]).toBe("http://localhost:11434/api/show");
});
});
describe("isSelfHosted", () => {
it("is true for a server the user runs", () => {
expect(isSelfHosted("http://localhost:11434")).toBe(true);
expect(isSelfHosted("https://box.local:11434")).toBe(true);
expect(isSelfHosted("http://192.168.1.10:11434/")).toBe(true);
});
it("is false for Ollama Cloud, which has nothing to pull or delete", () => {
expect(isSelfHosted("https://ollama.com")).toBe(false);
expect(isSelfHosted("https://ollama.com/")).toBe(false);
expect(isSelfHosted("https://api.ollama.com")).toBe(false);
expect(isSelfHosted(undefined)).toBe(false);
});
it("treats an unparseable host as local, because every request fails on its own terms anyway", () => {
expect(isSelfHosted("not a url")).toBe(true);
});
it("goes by hostname, so a local server behind https still counts", () => {
expect(isSelfHosted("https://gpu-box.internal:443")).toBe(true);
});
});
describe("listModels", () => {
const fetchMock = vi.fn();
beforeEach(() => {
fetchMock.mockReset();
vi.stubGlobal("fetch", fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
});
/** Trimmed from a real `GET /api/tags` on Ollama 0.35.1, mixed local and proxied. */
const realPayload = {
models: [
{
name: "ornith-1.5:9b",
model: "ornith-1.5:9b",
size: 6550813657,
details: { parameter_size: "9.0B" },
capabilities: ["tools", "thinking", "completion", "vision"],
},
{
name: "deepseek-v4.1-flash:cloud",
model: "deepseek-v4.1-flash:cloud",
remote_model: "deepseek-v4.1-flash",
remote_host: "https://ollama.com",
size: 326,
details: { parameter_size: "763B" },
capabilities: ["completion", "thinking", "tools", "vision"],
},
],
};
it("reads the size, capabilities and remote host off each entry", async () => {
fetchMock.mockResolvedValueOnce(new Response(JSON.stringify(realPayload), { status: 200 }));
const models = await listModels({ host: "http://localhost:11434", apiKey: "k" });
expect(models).toEqual([
{
name: "deepseek-v4.1-flash:cloud",
parameterSize: "763B",
size: 326,
capabilities: ["completion", "thinking", "tools", "vision"],
remoteHost: "https://ollama.com",
},
{
name: "ornith-1.5:9b",
parameterSize: "9.0B",
size: 6550813657,
capabilities: ["tools", "thinking", "completion", "vision"],
remoteHost: undefined,
},
]);
});
it("leaves capabilities absent rather than empty when the server does not send them", async () => {
// An absent list means "unknown", and a row that read it as [] would claim a
// model cannot see images when the server simply said nothing.
fetchMock.mockResolvedValueOnce(
new Response(JSON.stringify({ models: [{ name: "old-model:1b" }] }), { status: 200 }),
);
const [model] = await listModels({ host: "http://localhost:11434", apiKey: "k" });
expect(model.capabilities).toBeUndefined();
expect(model.size).toBeUndefined();
expect(model.remoteHost).toBeUndefined();
});
it("falls back to the model field and drops nameless entries", async () => {
fetchMock.mockResolvedValueOnce(
new Response(JSON.stringify({ models: [{ model: "from-model-field" }, { details: {} }] }), {
status: 200,
}),
);
await expect(listModels({ host: "http://localhost:11434", apiKey: "k" })).resolves.toEqual([
{ name: "from-model-field", parameterSize: undefined, size: undefined, capabilities: undefined, remoteHost: undefined },
]);
});
it("surfaces a rejected key instead of an empty list", async () => {
fetchMock.mockResolvedValueOnce(new Response("unauthorized", { status: 401 }));
await expect(listModels({ host: "https://ollama.com", apiKey: "k" })).rejects.toThrow(
/rejected the API key/,
);
});
});
describe("deleteModel", () => {
const fetchMock = vi.fn();
beforeEach(() => {
fetchMock.mockReset();
vi.stubGlobal("fetch", fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("sends DELETE, because Ollama answers the POST form of this route with 405", async () => {
fetchMock.mockResolvedValueOnce(new Response("", { status: 200 }));
await deleteModel({ host: "http://localhost:11434", apiKey: "k", model: "llama3.2:3b" });
const [url, init] = fetchMock.mock.calls[0];
expect(url).toBe("http://localhost:11434/api/delete");
expect(init.method).toBe("DELETE");
expect(JSON.parse(init.body)).toEqual({ model: "llama3.2:3b" });
});
it("says the model is not there rather than swallowing a 404 into a success", async () => {
// Reported as a failure on purpose: for a delete, "no model called that"
// usually means something else removed it, or the host is not the one the
// user thinks it is.
fetchMock.mockResolvedValueOnce(
new Response(JSON.stringify({ error: "model 'x:1' not found" }), { status: 404 }),
);
await expect(
deleteModel({ host: "http://localhost:11434", apiKey: "k", model: "x:1" }),
).rejects.toThrow(/has no model called x:1/);
});
});
describe("pullModel", () => {
const fetchMock = vi.fn();
beforeEach(() => {
fetchMock.mockReset();
vi.stubGlobal("fetch", fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
});
function ndjson(events: unknown[]): Response {
const body = `${events.map((event) => JSON.stringify(event)).join("\n")}\n`;
return new Response(body, { status: 200, headers: { "Content-Type": "application/x-ndjson" } });
}
async function collectPull(): Promise<{ status: string; total?: number; completed?: number }[]> {
const out: { status: string; total?: number; completed?: number }[] = [];
for await (const progress of pullModel({ host: "http://localhost:11434", apiKey: "k", model: "qwen3:8b" })) {
out.push(progress);
}
return out;
}
it("streams progress as it arrives", async () => {
fetchMock.mockResolvedValueOnce(
ndjson([
{ status: "pulling manifest" },
{ status: "downloading", total: 1000, completed: 250 },
{ status: "downloading", total: 1000, completed: 1000 },
{ status: "success" },
]),
);
await expect(collectPull()).resolves.toEqual([
{ status: "pulling manifest", total: undefined, completed: undefined },
{ status: "downloading", total: 1000, completed: 250 },
{ status: "downloading", total: 1000, completed: 1000 },
{ status: "success", total: undefined, completed: undefined },
]);
const [url, init] = fetchMock.mock.calls[0];
expect(url).toBe("http://localhost:11434/api/pull");
expect(JSON.parse(init.body)).toEqual({ model: "qwen3:8b", stream: true });
});
it("throws on the error field, which arrives with HTTP 200 rather than a status", async () => {
// Measured against 0.35.1: a bad model name answers 200 and puts the failure
// in the body, so a check of the status line alone reports a clean pull.
fetchMock.mockResolvedValueOnce(
ndjson([{ status: "pulling manifest" }, { error: "pull model manifest: file does not exist" }]),
);
await expect(collectPull()).rejects.toThrow(/file does not exist/);
});
it("throws a plain message when the server refuses outright", async () => {
fetchMock.mockResolvedValueOnce(new Response("nope", { status: 500 }));
await expect(collectPull()).rejects.toThrow(/HTTP 500/);
});
});
describe("describePullProgress", () => {
it("passes a status through when there is nothing to measure yet", () => {
expect(describePullProgress({ status: "pulling manifest" })).toBe("pulling manifest");
expect(describePullProgress({ status: "verifying sha256 digest" })).toBe("verifying sha256 digest");
});
it("adds a percentage and the transferred size once a total is known", () => {
expect(describePullProgress({ status: "downloading", total: 1_000, completed: 420 })).toBe(
"downloading · 42% · 420 B / 1.0 kB",
);
});
it("does not divide by a total of zero", () => {
// A tick can arrive with the total not yet filled in, and printing
// "Infinity%" at the user is the kind of thing that only shows up in the wild.
expect(describePullProgress({ status: "downloading", total: 0, completed: 10 })).toBe("downloading");
expect(describePullProgress({ status: "downloading", total: 100, completed: Number.NaN })).toBe(
"downloading",
);
});
it("never reports more than complete", () => {
expect(describePullProgress({ status: "downloading", total: 100, completed: 140 })).toContain("100%");
});
});
describe("web tool paths", () => {
const fetchMock = vi.fn();
beforeEach(() => {
fetchMock.mockReset();
vi.stubGlobal("fetch", fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
});
function json(payload: unknown, status = 200): Response {
return new Response(JSON.stringify(payload), { status });
}
it("uses the plain path on Ollama Cloud", async () => {
fetchMock.mockResolvedValueOnce(json({ results: [] }));
await webSearch({ host: "https://ollama.com", apiKey: "k", query: "q" });
expect(fetchMock.mock.calls[0][0]).toBe("https://ollama.com/api/web_search");
});
it("uses the experimental path on a local server, which is the one it serves", async () => {
// The reported bug: this used to post to /api/web_search on every host, so
// pointing the extension at a local server made every web call a 404 and the
// model reported that its web tools were not configured.
fetchMock.mockResolvedValueOnce(json({ results: [] }));
await webSearch({ host: "http://localhost:11434", apiKey: "k", query: "q" });
expect(fetchMock.mock.calls[0][0]).toBe("http://localhost:11434/api/experimental/web_search");
});
it("does the same for web_fetch", async () => {
fetchMock.mockResolvedValueOnce(json({ title: "T", content: "C", links: null }));
await expect(
webFetch({ host: "http://localhost:11434", apiKey: "k", url: "https://example.com" }),
).resolves.toEqual({ title: "T", content: "C", links: [] });
expect(fetchMock.mock.calls[0][0]).toBe("http://localhost:11434/api/experimental/web_fetch");
});
it("falls back to the other path when the preferred one is missing", async () => {
// A server built the other way round, or a future rename: a 404 means the
// route is absent, so trying the other one cannot double-send anything.
fetchMock
.mockResolvedValueOnce(new Response("404 page not found", { status: 404 }))
.mockResolvedValueOnce(json({ results: [{ title: "t", url: "u", content: "c" }] }));
await expect(webSearch({ host: "http://localhost:11434", apiKey: "k", query: "q" })).resolves.toHaveLength(
1,
);
expect(fetchMock.mock.calls.map((call) => call[0])).toEqual([
"http://localhost:11434/api/experimental/web_search",
"http://localhost:11434/api/web_search",
]);
});
it("does not fall back on anything other than a 404", async () => {
// A 401 or a 500 is the server answering, and retrying the other path would
// just send the same refused request twice.
fetchMock.mockResolvedValueOnce(new Response("unauthorized", { status: 401 }));
await expect(webSearch({ host: "https://ollama.com", apiKey: "k", query: "q" })).rejects.toThrow(
/rejected the API key/,
);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it("reports the failure from the path it actually tried last", async () => {
fetchMock
.mockResolvedValueOnce(new Response("404 page not found", { status: 404 }))
.mockResolvedValueOnce(new Response("404 page not found", { status: 404 }));
await expect(webSearch({ host: "http://localhost:11434", apiKey: "k", query: "q" })).rejects.toThrow(
/404/,
);
});
});
describe("listRunningModels", () => {
const fetchMock = vi.fn();
beforeEach(() => {
fetchMock.mockReset();
vi.stubGlobal("fetch", fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
});
/** Trimmed from a real `GET /api/ps` on Ollama 0.35.1 with one model in VRAM. */
const realPayload = {
models: [
{
name: "ornith-1.5:9b",
model: "ornith-1.5:9b",
size: 10413733314,
size_vram: 10413733314,
expires_at: "2026-10-05T17:44:11.125356+08:00",
context_length: 131072,
details: { parameter_size: "9.0B" },
},
],
};
it("reads what is in memory, including when it will go on its own", async () => {
fetchMock.mockResolvedValueOnce(new Response(JSON.stringify(realPayload), { status: 200 }));
const running = await listRunningModels({ host: "http://localhost:11434", apiKey: "k" });
expect(running).toEqual([
{
name: "ornith-1.5:9b",
parameterSize: "9.0B",
sizeVram: 10413733314,
expiresAt: Date.parse("2026-10-05T17:44:11.125356+08:00"),
},
]);
expect(fetchMock.mock.calls[0][0]).toBe("http://localhost:11434/api/ps");
});
it("is empty, not an error, when the server does not serve /api/ps", async () => {
// Older builds answer 404. Taking the whole model list down because the
// *running* list is unavailable would be a bad trade.
fetchMock.mockResolvedValueOnce(new Response("404 page not found", { status: 404 }));
await expect(listRunningModels({ host: "http://localhost:11434", apiKey: "k" })).resolves.toEqual([]);
});
it("is empty when the request fails outright", async () => {
fetchMock.mockRejectedValueOnce(new Error("connection refused"));
await expect(listRunningModels({ host: "http://localhost:11434", apiKey: "k" })).resolves.toEqual([]);
});
it("copes with a model holding no VRAM figure, as a CPU-offloaded one would", async () => {
fetchMock.mockResolvedValueOnce(
new Response(JSON.stringify({ models: [{ name: "cpu-model:1b", size_vram: 0 }] }), { status: 200 }),
);
await expect(listRunningModels({ host: "http://localhost:11434", apiKey: "k" })).resolves.toEqual([
{ name: "cpu-model:1b", parameterSize: undefined, sizeVram: 0, expiresAt: undefined },
]);
});
});
describe("unloadModel", () => {
const fetchMock = vi.fn();
beforeEach(() => {
fetchMock.mockReset();
vi.stubGlobal("fetch", fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("posts keep_alive 0 with no prompt, which is how Ollama is told to release a model", async () => {
fetchMock.mockResolvedValueOnce(
new Response(JSON.stringify({ done: true, done_reason: "unload" }), { status: 200 }),
);
await unloadModel({ host: "http://localhost:11434", apiKey: "k", model: "ornith-1.5:9b" });
const [url, init] = fetchMock.mock.calls[0];
expect(url).toBe("http://localhost:11434/api/generate");
expect(init.method).toBe("POST");
// No `prompt` key at all, so this can never generate a completion.
expect(JSON.parse(init.body)).toEqual({
model: "ornith-1.5:9b",
keep_alive: 0,
stream: false,
});
});
it("surfaces a refusal rather than pretending the memory came back", async () => {
fetchMock.mockResolvedValueOnce(new Response("nope", { status: 500 }));
await expect(
unloadModel({ host: "http://localhost:11434", apiKey: "k", model: "x:1" }),
).rejects.toThrow(/HTTP 500/);
});
});
+94 -1
View File
@@ -1,13 +1,18 @@
import { describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { __resetLocalStorage, __setPreferenceValues } from "../__mocks__/@vicinae/api";
import { DEFAULT_HOST, DEFAULT_MAX_TOOL_ROUNDS, DEFAULT_MODEL } from "../lib/constants";
import {
clearModelSelection,
describeAutoDelete,
describeMaxToolRounds,
describeResumeTimeout,
resolveAutoDelete,
resolveMaxToolRounds,
resolvePreferences,
loadPreferences,
readModelSelection,
resolveResumeTimeout,
selectModel,
} from "../lib/preferences";
const MINUTE = 60_000;
@@ -203,3 +208,91 @@ describe("describeMaxToolRounds", () => {
expect(describeMaxToolRounds(1)).toBe("1 round per answer");
});
});
describe("the model selection", () => {
beforeEach(() => {
__resetLocalStorage();
__setPreferenceValues({ apiKey: "k", model: "from-preference:1b" });
});
afterEach(() => {
__setPreferenceValues({});
});
it("uses the preference when nothing has been picked", async () => {
const resolved = await loadPreferences();
expect(resolved.model).toBe("from-preference:1b");
expect(resolved.modelSource).toBe("preference");
expect(resolved.preferenceModel).toBe("from-preference:1b");
});
it("prefers a picked model over the preference, and says so", async () => {
// The whole reason this exists: an extension cannot write its own
// preferences, so the pick has to live somewhere else and win.
await selectModel("picked:9b");
const resolved = await loadPreferences();
expect(resolved.model).toBe("picked:9b");
expect(resolved.modelSource).toBe("selection");
// Both values are carried, so a screen can answer "what would a reset give
// me back".
expect(resolved.preferenceModel).toBe("from-preference:1b");
});
it("round-trips a pick through storage", async () => {
await selectModel("first:1b");
await expect(readModelSelection()).resolves.toBe("first:1b");
await selectModel("second:2b");
await expect(readModelSelection()).resolves.toBe("second:2b");
});
it("hands the model back to the preference when the selection is cleared", async () => {
await selectModel("picked:9b");
await clearModelSelection();
const resolved = await loadPreferences();
expect(resolved.model).toBe("from-preference:1b");
expect(resolved.modelSource).toBe("preference");
});
it("ignores a stored selection that is empty or whitespace", async () => {
// A blank value is not a model name, and treating it as one would leave the
// chat asking for "" on every turn.
await selectModel(" ");
await expect(readModelSelection()).resolves.toBeUndefined();
await expect(loadPreferences()).resolves.toMatchObject({ model: "from-preference:1b" });
});
it("trims a stored selection", async () => {
await selectModel(" spaced:1b ");
await expect(readModelSelection()).resolves.toBe("spaced:1b");
});
it("falls back to the preference when storage cannot be read", async () => {
const { LocalStorage } = await import("../__mocks__/@vicinae/api");
const original = LocalStorage.getItem;
LocalStorage.getItem = async () => {
throw new Error("storage is unavailable");
};
try {
await expect(loadPreferences()).resolves.toMatchObject({ model: "from-preference:1b" });
} finally {
LocalStorage.getItem = original;
}
});
it("leaves the model source as the preference when resolving without storage", () => {
expect(resolvePreferences({ model: "plain:1b" })).toMatchObject({
model: "plain:1b",
modelSource: "preference",
preferenceModel: "plain:1b",
});
});
});
+33
View File
@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import {
errorMessage,
formatBytes,
formatRelativeTime,
recencyGroup,
titleFromPrompt,
@@ -98,3 +99,35 @@ describe("errorMessage", () => {
expect(typeof errorMessage(circular)).toBe("string");
});
});
describe("formatBytes", () => {
it("matches what `ollama list` prints for the same bytes", () => {
// Captured from this machine: `ollama list` shows ornith-1.5:9b as 6.6 GB,
// and a size that disagreed with the terminal would read as our bug.
expect(formatBytes(6_550_813_657)).toBe("6.6 GB");
});
it("keeps small counts in bytes", () => {
expect(formatBytes(0)).toBe("0 B");
expect(formatBytes(326)).toBe("326 B");
expect(formatBytes(999)).toBe("999 B");
});
it("steps up one unit at a time", () => {
expect(formatBytes(1_500)).toBe("1.5 kB");
expect(formatBytes(2_500_000)).toBe("2.5 MB");
expect(formatBytes(3_000_000_000_000)).toBe("3.0 TB");
});
it("stops at terabytes rather than inventing a unit", () => {
expect(formatBytes(9_000_000_000_000_000)).toBe("9000.0 TB");
});
it("has no answer for a size that is absent or nonsense", () => {
// undefined rather than "NaN B", so a caller leaves the field out.
expect(formatBytes(undefined)).toBeUndefined();
expect(formatBytes(-1)).toBeUndefined();
expect(formatBytes(Number.NaN)).toBeUndefined();
expect(formatBytes(Number.POSITIVE_INFINITY)).toBeUndefined();
});
});