mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-28 03:49:31 +00:00
test: remove duplicate and theatrical tests (#2826)
- delete commands-update-download.test.ts (7 tests): superseded by cmd-update-cov.test.ts which has 13 tests with better fallback URL coverage and uses clack mocks properly - remove saveSpawnRecord id generation describe from history-cov.test.ts (1 test): superseded by history-spawn-id.test.ts which has 3 more thorough tests covering the same scenario - remove 4 describe blocks from cmd-run-cov.test.ts (18 tests): getSignalGuidance, getScriptFailureGuidance, getScriptFailureGuidance additional, and getSignalGuidance additional are all covered more thoroughly by the dedicated script-failure-guidance.test.ts; the "additional" blocks were theatrical (only checked joined.length > 0) - delete picker.test.ts and merge its 8 parsePickerInput tests into picker-cov.test.ts to eliminate duplicate describe name collision 2063 -> 2036 tests (-27), 0 failures Co-authored-by: spawn-qa-bot <qa@openrouter.ai> Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
This commit is contained in:
parent
c323f10ae9
commit
a7cebd4054
5 changed files with 105 additions and 472 deletions
|
|
@ -4,7 +4,7 @@
|
|||
* Focuses on uncovered helper functions: resolveAndLog, detectAndFixSwappedArgs,
|
||||
* dry-run helpers (buildAgentLines, buildCloudLines, buildCredentialStatusLines,
|
||||
* buildEnvironmentLines, buildPromptLines), showDryRunPreview, classifyNetworkError,
|
||||
* isRetryableExitCode, headless output/error, and execScript paths.
|
||||
* isRetryableExitCode, and headless output/error paths.
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
|
||||
|
|
@ -14,14 +14,8 @@ import { createConsoleMocks, createMockManifest, mockClackPrompts, restoreMocks
|
|||
|
||||
const clack = mockClackPrompts();
|
||||
|
||||
const { cmdRun, cmdRunHeadless, getScriptFailureGuidance, getSignalGuidance, isRetryableExitCode } = await import(
|
||||
"../commands/index.js"
|
||||
);
|
||||
const { showDryRunPreview, execScript } = await import("../commands/run.js");
|
||||
|
||||
function stripAnsi(s: string): string {
|
||||
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
||||
}
|
||||
const { cmdRun, cmdRunHeadless, isRetryableExitCode } = await import("../commands/index.js");
|
||||
const { showDryRunPreview } = await import("../commands/run.js");
|
||||
|
||||
describe("commands/run.ts coverage", () => {
|
||||
let consoleMocks: ReturnType<typeof createConsoleMocks>;
|
||||
|
|
@ -122,162 +116,6 @@ describe("commands/run.ts coverage", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// ── getSignalGuidance ──────────────────────────────────────────────────
|
||||
|
||||
describe("getSignalGuidance", () => {
|
||||
it("returns guidance for SIGKILL", () => {
|
||||
const lines = getSignalGuidance("SIGKILL").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("killed");
|
||||
});
|
||||
|
||||
it("returns default guidance for unknown signal", () => {
|
||||
const lines = getSignalGuidance("SIGUSR1").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("SIGUSR1");
|
||||
expect(joined).toContain("terminated");
|
||||
});
|
||||
|
||||
it("includes dashboard hint when provided", () => {
|
||||
const lines = getSignalGuidance("SIGUSR1", "https://dash.example.com").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("https://dash.example.com");
|
||||
});
|
||||
});
|
||||
|
||||
// ── getScriptFailureGuidance ──────────────────────────────────────────
|
||||
|
||||
describe("getScriptFailureGuidance", () => {
|
||||
it("returns default guidance for null exit code", () => {
|
||||
const lines = getScriptFailureGuidance(null, "hetzner").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("Common causes");
|
||||
});
|
||||
|
||||
it("returns default guidance for unknown exit codes", () => {
|
||||
const lines = getScriptFailureGuidance(99, "hetzner").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("Common causes");
|
||||
});
|
||||
|
||||
it("includes dashboard hint for exit code 130", () => {
|
||||
const lines = getScriptFailureGuidance(130, "hetzner", undefined, "https://dash.test").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("interrupted");
|
||||
});
|
||||
|
||||
it("includes credential hints for exit code 1", () => {
|
||||
const lines = getScriptFailureGuidance(1, "hetzner", "Set HCLOUD_TOKEN").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("HCLOUD_TOKEN");
|
||||
});
|
||||
|
||||
it("handles exit code 137 (OOM/killed)", () => {
|
||||
const lines = getScriptFailureGuidance(137, "sprite").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("killed");
|
||||
});
|
||||
});
|
||||
|
||||
// ── classifyNetworkError (via reportDownloadError path) ──────────────
|
||||
|
||||
describe("classifyNetworkError", () => {
|
||||
it("handles timeout error in dry run preview", () => {
|
||||
showDryRunPreview(mockManifest, "claude", "sprite");
|
||||
// Just verify it completes without throwing
|
||||
expect(clack.logInfo).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ── showDryRunPreview with cloud defaults ──────────────────────────
|
||||
|
||||
describe("showDryRunPreview edge cases", () => {
|
||||
it("shows credential status for sprite (token auth)", () => {
|
||||
showDryRunPreview(mockManifest, "claude", "sprite");
|
||||
const allCalls = consoleMocks.log.mock.calls.flat().map(String);
|
||||
expect(allCalls.some((c) => c.includes("OPENROUTER_API_KEY"))).toBe(true);
|
||||
});
|
||||
|
||||
it("shows credential status for hetzner (token auth)", () => {
|
||||
showDryRunPreview(mockManifest, "claude", "hetzner");
|
||||
const allCalls = consoleMocks.log.mock.calls.flat().map(String);
|
||||
expect(allCalls.some((c) => c.includes("OPENROUTER_API_KEY"))).toBe(true);
|
||||
});
|
||||
|
||||
it("shows no environment lines when agent has no env", () => {
|
||||
const noEnvManifest = {
|
||||
...mockManifest,
|
||||
agents: {
|
||||
...mockManifest.agents,
|
||||
noenv: {
|
||||
name: "NoEnv Agent",
|
||||
description: "Agent without env",
|
||||
url: "https://example.com",
|
||||
install: "npm install noenv",
|
||||
launch: "noenv",
|
||||
},
|
||||
},
|
||||
matrix: {
|
||||
...mockManifest.matrix,
|
||||
"sprite/noenv": "implemented",
|
||||
},
|
||||
};
|
||||
global.fetch = mock(async () => new Response(JSON.stringify(noEnvManifest)));
|
||||
showDryRunPreview(noEnvManifest, "noenv", "sprite");
|
||||
expect(clack.logSuccess).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ── getScriptFailureGuidance edge cases ───────────────────────────
|
||||
|
||||
describe("getScriptFailureGuidance additional", () => {
|
||||
it("handles exit code 2 (misuse of shell builtin)", () => {
|
||||
const lines = getScriptFailureGuidance(2, "sprite").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles exit code 126 (permission denied)", () => {
|
||||
const lines = getScriptFailureGuidance(126, "hetzner").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles exit code 127 (command not found)", () => {
|
||||
const lines = getScriptFailureGuidance(127, "hetzner").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles exit code 255 (SSH error)", () => {
|
||||
const lines = getScriptFailureGuidance(255, "aws").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles exit code 1 with no auth hint", () => {
|
||||
const lines = getScriptFailureGuidance(1, "sprite").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("Cloud provider API error");
|
||||
});
|
||||
});
|
||||
|
||||
// ── getSignalGuidance additional ──────────────────────────────────
|
||||
|
||||
describe("getSignalGuidance additional", () => {
|
||||
it("returns guidance for SIGTERM", () => {
|
||||
const lines = getSignalGuidance("SIGTERM").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("terminated");
|
||||
});
|
||||
|
||||
it("returns guidance for unknown signal without dashboard", () => {
|
||||
const lines = getSignalGuidance("SIGXCPU").map(stripAnsi);
|
||||
const joined = lines.join("\n");
|
||||
expect(joined).toContain("SIGXCPU");
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdRun additional ─────────────────────────────────────────────
|
||||
|
||||
describe("cmdRun validation", () => {
|
||||
|
|
|
|||
|
|
@ -1,183 +0,0 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
|
||||
import { isString } from "@openrouter/spawn-shared";
|
||||
import pkg from "../../package.json" with { type: "json" };
|
||||
import { createConsoleMocks, mockClackPrompts, restoreMocks } from "./test-helpers";
|
||||
|
||||
const VERSION = pkg.version;
|
||||
|
||||
/**
|
||||
* Tests for cmdUpdate (commands/update.ts).
|
||||
*
|
||||
* Uses dependency injection (UpdateOptions.runUpdate) instead of mock.module
|
||||
* for node:child_process to avoid process-global mock pollution.
|
||||
*/
|
||||
|
||||
const { spinnerStart: mockSpinnerStart, spinnerStop: mockSpinnerStop } = mockClackPrompts();
|
||||
|
||||
// ── Import commands directly (no mock.module needed) ──────────────────────
|
||||
import { cmdUpdate } from "../commands/index.js";
|
||||
|
||||
/** No-op runUpdate to prevent real subprocess calls in tests. */
|
||||
const mockRunUpdate = mock(() => {});
|
||||
|
||||
describe("cmdUpdate", () => {
|
||||
let consoleMocks: ReturnType<typeof createConsoleMocks>;
|
||||
let originalFetch: typeof global.fetch;
|
||||
let processExitSpy: ReturnType<typeof spyOn>;
|
||||
|
||||
beforeEach(async () => {
|
||||
consoleMocks = createConsoleMocks();
|
||||
mockSpinnerStart.mockClear();
|
||||
mockSpinnerStop.mockClear();
|
||||
mockRunUpdate.mockClear();
|
||||
|
||||
processExitSpy = spyOn(process, "exit").mockImplementation(() => {
|
||||
throw new Error("process.exit");
|
||||
});
|
||||
|
||||
originalFetch = global.fetch;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
processExitSpy.mockRestore();
|
||||
restoreMocks(consoleMocks.log, consoleMocks.error);
|
||||
});
|
||||
|
||||
it("should report up-to-date when remote version matches current", async () => {
|
||||
global.fetch = mock(async (url: string) => {
|
||||
if (isString(url) && url.includes("/version")) {
|
||||
return new Response(`${VERSION}\n`);
|
||||
}
|
||||
return new Response("Not Found", {
|
||||
status: 404,
|
||||
});
|
||||
});
|
||||
|
||||
await cmdUpdate({
|
||||
runUpdate: mockRunUpdate,
|
||||
});
|
||||
|
||||
expect(mockSpinnerStart).toHaveBeenCalled();
|
||||
expect(mockSpinnerStop).toHaveBeenCalled();
|
||||
// The spinner stop message should indicate up-to-date
|
||||
const stopCalls = mockSpinnerStop.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
expect(stopCalls.some((msg: string) => msg.includes("up to date"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should report available update when remote version differs", async () => {
|
||||
global.fetch = mock(async (url: string) => {
|
||||
if (isString(url) && url.includes("/version")) {
|
||||
return new Response("99.99.99\n");
|
||||
}
|
||||
return new Response("Not Found", {
|
||||
status: 404,
|
||||
});
|
||||
});
|
||||
|
||||
await cmdUpdate({
|
||||
runUpdate: mockRunUpdate,
|
||||
});
|
||||
|
||||
expect(mockSpinnerStart).toHaveBeenCalled();
|
||||
// Should show update message with version transition
|
||||
const stopCalls = mockSpinnerStop.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
expect(stopCalls.some((msg: string) => msg.includes("99.99.99"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle package.json fetch failure gracefully", async () => {
|
||||
global.fetch = mock(
|
||||
async () =>
|
||||
new Response("Internal Server Error", {
|
||||
status: 500,
|
||||
}),
|
||||
);
|
||||
|
||||
await cmdUpdate({
|
||||
runUpdate: mockRunUpdate,
|
||||
});
|
||||
|
||||
expect(mockSpinnerStart).toHaveBeenCalled();
|
||||
// Should show failed message
|
||||
const stopCalls = mockSpinnerStop.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
expect(stopCalls.some((msg: string) => msg.includes("Failed"))).toBe(true);
|
||||
// Should output error details
|
||||
const errorOutput = consoleMocks.error.mock.calls.map((c: unknown[]) => c.join(" ")).join("\n");
|
||||
expect(errorOutput).toContain("Error:");
|
||||
});
|
||||
|
||||
it("should handle network error gracefully", async () => {
|
||||
global.fetch = mock(async () => {
|
||||
throw new TypeError("Failed to fetch");
|
||||
});
|
||||
|
||||
await cmdUpdate({
|
||||
runUpdate: mockRunUpdate,
|
||||
});
|
||||
|
||||
expect(mockSpinnerStart).toHaveBeenCalled();
|
||||
const stopCalls = mockSpinnerStop.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
expect(stopCalls.some((msg: string) => msg.includes("Failed"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle update failure gracefully", async () => {
|
||||
global.fetch = mock(async (url: string) => {
|
||||
if (isString(url) && url.includes("/version")) {
|
||||
return new Response("99.99.99\n");
|
||||
}
|
||||
return new Response("Not Found", {
|
||||
status: 404,
|
||||
});
|
||||
});
|
||||
|
||||
// Mock runUpdate that throws to simulate failure
|
||||
const failingRunUpdate = mock(() => {
|
||||
throw new Error("curl failed");
|
||||
});
|
||||
|
||||
await cmdUpdate({
|
||||
runUpdate: failingRunUpdate,
|
||||
});
|
||||
|
||||
// Should show the update version in spinner stop
|
||||
const stopCalls = mockSpinnerStop.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
expect(stopCalls.some((msg: string) => msg.includes("99.99.99"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should start spinner with checking message", async () => {
|
||||
global.fetch = mock(
|
||||
async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
version: VERSION,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await cmdUpdate({
|
||||
runUpdate: mockRunUpdate,
|
||||
});
|
||||
|
||||
const startCalls = mockSpinnerStart.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
expect(startCalls.some((msg: string) => msg.includes("Checking"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should show version in spinner stop during update", async () => {
|
||||
global.fetch = mock(async (url: string) => {
|
||||
if (isString(url) && url.includes("/version")) {
|
||||
return new Response("2.0.0\n");
|
||||
}
|
||||
return new Response("Error", {
|
||||
status: 500,
|
||||
});
|
||||
});
|
||||
|
||||
await cmdUpdate({
|
||||
runUpdate: mockRunUpdate,
|
||||
});
|
||||
|
||||
// cmdUpdate now uses s.stop() with version info instead of s.message()
|
||||
const stopCalls = mockSpinnerStop.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
expect(stopCalls.some((msg: string) => msg.includes("2.0.0"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -662,24 +662,6 @@ describe("history.ts coverage", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// ── saveSpawnRecord auto-generates id ─────────────────────────────────
|
||||
|
||||
describe("saveSpawnRecord id generation", () => {
|
||||
it("auto-generates id if not provided", () => {
|
||||
const recordWithoutId: SpawnRecord = {
|
||||
id: "",
|
||||
agent: "claude",
|
||||
cloud: "sprite",
|
||||
timestamp: "2026-01-01T00:00:00Z",
|
||||
};
|
||||
saveSpawnRecord(recordWithoutId);
|
||||
|
||||
const data = JSON.parse(readFileSync(join(testDir, "history.json"), "utf-8"));
|
||||
expect(data.records[0].id).toBeTruthy();
|
||||
expect(typeof data.records[0].id).toBe("string");
|
||||
});
|
||||
});
|
||||
|
||||
// ── Trimming with archiving ───────────────────────────────────────────
|
||||
|
||||
describe("trimming and archiving", () => {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,108 @@ describe("picker.ts coverage", () => {
|
|||
// ── parsePickerInput extended ─────────────────────────────────────────
|
||||
|
||||
describe("parsePickerInput", () => {
|
||||
it("handles tabs within values", () => {
|
||||
it("parses three-field tab-separated lines (value, label, hint)", () => {
|
||||
const result = parsePickerInput("us-east-1\tVirginia\tRecommended");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "us-east-1",
|
||||
label: "Virginia",
|
||||
hint: "Recommended",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("parses two-field lines (value, label) with no hint", () => {
|
||||
const result = parsePickerInput("us-east-1\tVirginia");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "us-east-1",
|
||||
label: "Virginia",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses value as label when only value is provided", () => {
|
||||
const result = parsePickerInput("us-east-1");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "us-east-1",
|
||||
label: "us-east-1",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("filters empty and whitespace-only lines", () => {
|
||||
const result = parsePickerInput("a\tAlpha\n\n \nb\tBeta\n");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "a",
|
||||
label: "Alpha",
|
||||
},
|
||||
{
|
||||
value: "b",
|
||||
label: "Beta",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles mixed field counts in a single input", () => {
|
||||
const input = [
|
||||
"val1\tLabel1\tHint1",
|
||||
"val2\tLabel2",
|
||||
"val3",
|
||||
].join("\n");
|
||||
const result = parsePickerInput(input);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "val1",
|
||||
label: "Label1",
|
||||
hint: "Hint1",
|
||||
},
|
||||
{
|
||||
value: "val2",
|
||||
label: "Label2",
|
||||
},
|
||||
{
|
||||
value: "val3",
|
||||
label: "val3",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns empty array for empty input", () => {
|
||||
expect(parsePickerInput("")).toEqual([]);
|
||||
expect(parsePickerInput(" ")).toEqual([]);
|
||||
expect(parsePickerInput("\n\n")).toEqual([]);
|
||||
});
|
||||
|
||||
it("trims whitespace from fields", () => {
|
||||
const result = parsePickerInput(" val \t Label \t Hint ");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "val",
|
||||
label: "Label",
|
||||
hint: "Hint",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("parses multiple lines correctly", () => {
|
||||
const input = "us-central1-a\tIowa\nus-east1-b\tVirginia";
|
||||
const result = parsePickerInput(input);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "us-central1-a",
|
||||
label: "Iowa",
|
||||
},
|
||||
{
|
||||
value: "us-east1-b",
|
||||
label: "Virginia",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles tabs within values (extra fields beyond 3 are ignored)", () => {
|
||||
const result = parsePickerInput("a\tb\tc\td");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].value).toBe("a");
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
import { describe, expect, it } from "bun:test";
|
||||
import { parsePickerInput } from "../picker";
|
||||
|
||||
describe("parsePickerInput", () => {
|
||||
it("parses three-field tab-separated lines (value, label, hint)", () => {
|
||||
const result = parsePickerInput("us-east-1\tVirginia\tRecommended");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "us-east-1",
|
||||
label: "Virginia",
|
||||
hint: "Recommended",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("parses two-field lines (value, label) with no hint", () => {
|
||||
const result = parsePickerInput("us-east-1\tVirginia");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "us-east-1",
|
||||
label: "Virginia",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses value as label when only value is provided", () => {
|
||||
const result = parsePickerInput("us-east-1");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "us-east-1",
|
||||
label: "us-east-1",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("filters empty and whitespace-only lines", () => {
|
||||
const result = parsePickerInput("a\tAlpha\n\n \nb\tBeta\n");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "a",
|
||||
label: "Alpha",
|
||||
},
|
||||
{
|
||||
value: "b",
|
||||
label: "Beta",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles mixed field counts in a single input", () => {
|
||||
const input = [
|
||||
"val1\tLabel1\tHint1",
|
||||
"val2\tLabel2",
|
||||
"val3",
|
||||
].join("\n");
|
||||
const result = parsePickerInput(input);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "val1",
|
||||
label: "Label1",
|
||||
hint: "Hint1",
|
||||
},
|
||||
{
|
||||
value: "val2",
|
||||
label: "Label2",
|
||||
},
|
||||
{
|
||||
value: "val3",
|
||||
label: "val3",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns empty array for empty input", () => {
|
||||
expect(parsePickerInput("")).toEqual([]);
|
||||
expect(parsePickerInput(" ")).toEqual([]);
|
||||
expect(parsePickerInput("\n\n")).toEqual([]);
|
||||
});
|
||||
|
||||
it("trims whitespace from fields", () => {
|
||||
const result = parsePickerInput(" val \t Label \t Hint ");
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "val",
|
||||
label: "Label",
|
||||
hint: "Hint",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("parses multiple lines correctly", () => {
|
||||
const input = "us-central1-a\tIowa\nus-east1-b\tVirginia";
|
||||
const result = parsePickerInput(input);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
value: "us-central1-a",
|
||||
label: "Iowa",
|
||||
},
|
||||
{
|
||||
value: "us-east1-b",
|
||||
label: "Virginia",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue