2b1d0cf7d1
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
653 lines
21 KiB
TypeScript
653 lines
21 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
sessionId as toSessionId,
|
|
taskId as toTaskId,
|
|
type Message,
|
|
type Turn,
|
|
} from "@/lib/types/http";
|
|
import {
|
|
clarificationTurnIdForSession,
|
|
findPendingClarification,
|
|
findPendingClarificationGroup,
|
|
hasPendingClarification,
|
|
hasPendingPermissionRequest,
|
|
newestDurableTurnId,
|
|
} from "./pending-clarification";
|
|
|
|
const CURRENT_TURN_ID = "turn-new";
|
|
const OLD_PENDING_ID = "pending-old";
|
|
const TURN_TIMESTAMP = "2026-08-14T12:00:00Z";
|
|
|
|
function message(overrides: Partial<Message>): Message {
|
|
return {
|
|
id: "msg-1",
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
author_type: "agent",
|
|
content: "",
|
|
type: "message",
|
|
created_at: "2026-05-02T00:00:00Z",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function turn(id: string, startedAt = TURN_TIMESTAMP, createdAt = TURN_TIMESTAMP): Turn {
|
|
return {
|
|
id,
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
started_at: startedAt,
|
|
created_at: createdAt,
|
|
updated_at: createdAt,
|
|
};
|
|
}
|
|
|
|
describe("hasPendingClarification", () => {
|
|
it("detects clarification requests with pending status", () => {
|
|
expect(
|
|
hasPendingClarification([
|
|
message({
|
|
type: "clarification_request",
|
|
metadata: { status: "pending" },
|
|
}),
|
|
]),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("treats missing clarification status as pending", () => {
|
|
expect(hasPendingClarification([message({ type: "clarification_request" })])).toBe(true);
|
|
});
|
|
|
|
it("ignores answered clarification requests and ordinary messages", () => {
|
|
expect(
|
|
hasPendingClarification([
|
|
message({ type: "message" }),
|
|
message({
|
|
type: "clarification_request",
|
|
metadata: { status: "answered" },
|
|
}),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("treats rejected and expired clarifications as not pending", () => {
|
|
expect(
|
|
hasPendingClarification([
|
|
message({
|
|
type: "clarification_request",
|
|
metadata: { status: "rejected" },
|
|
}),
|
|
]),
|
|
).toBe(false);
|
|
expect(
|
|
hasPendingClarification([
|
|
message({
|
|
type: "clarification_request",
|
|
metadata: { status: "expired" },
|
|
}),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("hasPendingClarification turn scoping", () => {
|
|
it("ignores a detached pending clarification from a previous turn", () => {
|
|
expect(
|
|
hasPendingClarification([
|
|
message({
|
|
id: "old-detached",
|
|
turn_id: "t1",
|
|
type: "clarification_request",
|
|
metadata: { status: "pending", agent_disconnected: true },
|
|
}),
|
|
message({
|
|
id: "current",
|
|
turn_id: "t2",
|
|
type: "message",
|
|
created_at: "2026-05-02T00:00:01Z",
|
|
}),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("detects a pending clarification in the current turn", () => {
|
|
expect(
|
|
hasPendingClarification([
|
|
message({
|
|
id: "stale",
|
|
turn_id: "t1",
|
|
type: "clarification_request",
|
|
metadata: { status: "pending" },
|
|
}),
|
|
message({
|
|
id: "active",
|
|
turn_id: "t2",
|
|
type: "clarification_request",
|
|
metadata: { status: "pending" },
|
|
created_at: "2026-05-02T00:00:01Z",
|
|
}),
|
|
]),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("ignores a legacy null-turn clarification when a newer turn exists", () => {
|
|
expect(
|
|
hasPendingClarification([
|
|
message({
|
|
id: "legacy",
|
|
type: "clarification_request",
|
|
metadata: { status: "pending" },
|
|
}),
|
|
message({ id: "current", turn_id: "t2", type: "message" }),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("findPendingClarificationGroup", () => {
|
|
it("returns empty array when there are no messages", () => {
|
|
expect(findPendingClarificationGroup([])).toEqual([]);
|
|
expect(findPendingClarificationGroup(null)).toEqual([]);
|
|
expect(findPendingClarificationGroup(undefined)).toEqual([]);
|
|
});
|
|
|
|
it("returns empty array when there is no pending clarification", () => {
|
|
expect(
|
|
findPendingClarificationGroup([
|
|
message({ type: "clarification_request", metadata: { status: "answered" } }),
|
|
]),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("returns the single message when no pending_id is set", () => {
|
|
const msg = message({ type: "clarification_request" });
|
|
expect(findPendingClarificationGroup([msg])).toEqual([msg]);
|
|
});
|
|
|
|
it("returns every message that shares the latest pending_id", () => {
|
|
const a = message({
|
|
id: "a",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "p1", question_total: 3, question_index: 0, status: "pending" },
|
|
});
|
|
const b = message({
|
|
id: "b",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "p1", question_total: 3, question_index: 1, status: "pending" },
|
|
});
|
|
const c = message({
|
|
id: "c",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "p1", question_total: 3, question_index: 2, status: "pending" },
|
|
});
|
|
const noise = message({ id: "n", type: "message" });
|
|
expect(findPendingClarificationGroup([noise, a, b, c]).map((m) => m.id)).toEqual([
|
|
"a",
|
|
"b",
|
|
"c",
|
|
]);
|
|
});
|
|
|
|
it("gates on question_total — returns [] when not all messages have arrived", () => {
|
|
// Backend declared 3 questions but only 1 has streamed in; the overlay
|
|
// should stay hidden until the remaining 2 land.
|
|
const a = message({
|
|
id: "a",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "p1", question_total: 3, question_index: 0, status: "pending" },
|
|
});
|
|
expect(findPendingClarificationGroup([a])).toEqual([]);
|
|
});
|
|
|
|
it("ignores messages from a different pending bundle", () => {
|
|
const old = message({
|
|
id: "old",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "p0", status: "answered" },
|
|
});
|
|
const a = message({
|
|
id: "a",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "p1", question_total: 1, status: "pending" },
|
|
});
|
|
expect(findPendingClarificationGroup([old, a]).map((m) => m.id)).toEqual(["a"]);
|
|
});
|
|
|
|
it("counts terminal siblings for completeness but returns only pending questions", () => {
|
|
const answered = message({
|
|
id: "answered",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "p1", question_total: 2, status: "answered" },
|
|
});
|
|
const pending = message({
|
|
id: "pending",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "p1", question_total: 2, status: "pending" },
|
|
});
|
|
|
|
expect(findPendingClarificationGroup([answered, pending]).map((item) => item.id)).toEqual([
|
|
"pending",
|
|
]);
|
|
});
|
|
});
|
|
|
|
// eslint-disable-next-line max-lines-per-function
|
|
describe("current-turn clarification ownership", () => {
|
|
it("ignores an older pending request when a newer durable turn exists", () => {
|
|
const messages = [
|
|
message({
|
|
id: "old",
|
|
turn_id: "turn-old",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: OLD_PENDING_ID, status: "pending" },
|
|
}),
|
|
message({ id: "new", turn_id: CURRENT_TURN_ID, type: "message" }),
|
|
];
|
|
expect(findPendingClarification(messages, { currentTurnId: CURRENT_TURN_ID })).toBeNull();
|
|
expect(findPendingClarificationGroup(messages, { currentTurnId: CURRENT_TURN_ID })).toEqual([]);
|
|
});
|
|
it("keeps a detached clarification answerable after a newer turn starts", () => {
|
|
const detached = message({
|
|
id: "detached",
|
|
turn_id: "turn-old",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: OLD_PENDING_ID, status: "pending", agent_disconnected: true },
|
|
});
|
|
const newer = message({ id: "newer", turn_id: CURRENT_TURN_ID, type: "message" });
|
|
|
|
expect(
|
|
findPendingClarificationGroup([detached, newer], {
|
|
currentTurnId: CURRENT_TURN_ID,
|
|
pendingAction: null,
|
|
}),
|
|
).toEqual([detached]);
|
|
});
|
|
|
|
it.each([null, "permission"] as const)(
|
|
"treats a matching visible turn as inert when explicit authority is %s",
|
|
(pendingAction) => {
|
|
const pending = message({
|
|
id: "visible-predecessor",
|
|
turn_id: CURRENT_TURN_ID,
|
|
type: "clarification_request",
|
|
metadata: { pending_id: OLD_PENDING_ID, status: "pending" },
|
|
});
|
|
const scope = { currentTurnId: CURRENT_TURN_ID, pendingAction };
|
|
|
|
expect(findPendingClarification([pending], scope)).toBeNull();
|
|
expect(findPendingClarificationGroup([pending], scope)).toEqual([]);
|
|
},
|
|
);
|
|
|
|
it("returns only the current turn's exact pending bundle", () => {
|
|
const messages = [
|
|
message({
|
|
id: "old",
|
|
turn_id: "turn-old",
|
|
type: "clarification_request",
|
|
metadata: { pending_id: OLD_PENDING_ID, status: "pending" },
|
|
}),
|
|
message({
|
|
id: "current-a",
|
|
turn_id: CURRENT_TURN_ID,
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "pending-new", question_total: 2, status: "pending" },
|
|
}),
|
|
message({
|
|
id: "current-b",
|
|
turn_id: CURRENT_TURN_ID,
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "pending-new", question_total: 2, status: "pending" },
|
|
}),
|
|
];
|
|
expect(
|
|
findPendingClarificationGroup(messages, { currentTurnId: CURRENT_TURN_ID }).map(
|
|
(item) => item.id,
|
|
),
|
|
).toEqual(["current-a", "current-b"]);
|
|
});
|
|
|
|
it("keeps an earlier pending bundle visible when a newer bundle is terminal", () => {
|
|
const messages = [
|
|
message({
|
|
id: "pending",
|
|
turn_id: CURRENT_TURN_ID,
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "pending-1", status: "pending" },
|
|
}),
|
|
message({
|
|
id: "rejected",
|
|
turn_id: CURRENT_TURN_ID,
|
|
type: "clarification_request",
|
|
metadata: { pending_id: "pending-2", status: "rejected" },
|
|
}),
|
|
];
|
|
const scope = { currentTurnId: CURRENT_TURN_ID };
|
|
expect(findPendingClarification(messages, scope)?.id).toBe("pending");
|
|
expect(findPendingClarificationGroup(messages, scope).map((item) => item.id)).toEqual([
|
|
"pending",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("newestDurableTurnId load states", () => {
|
|
it("distinguishes unavailable, empty, and populated durable turn history", () => {
|
|
expect(newestDurableTurnId()).toBeUndefined();
|
|
expect(newestDurableTurnId([])).toBeNull();
|
|
expect(newestDurableTurnId([turn("turn-only")])).toBe("turn-only");
|
|
});
|
|
|
|
it("selects the turn with the later start time", () => {
|
|
expect(
|
|
newestDurableTurnId([
|
|
turn("turn-id-at-noon", "2026-08-14T12:00:00Z"),
|
|
turn("turn-id-one-second-later", "2026-08-14T12:00:01Z"),
|
|
]),
|
|
).toBe("turn-id-one-second-later");
|
|
});
|
|
});
|
|
|
|
describe("clarificationTurnIdForSession", () => {
|
|
it("quarantines pending message history for terminal sessions", () => {
|
|
const turns = [turn(CURRENT_TURN_ID)];
|
|
expect(clarificationTurnIdForSession("COMPLETED", turns)).toBeNull();
|
|
expect(clarificationTurnIdForSession("FAILED", turns)).toBeNull();
|
|
expect(clarificationTurnIdForSession("CANCELLED", turns)).toBeNull();
|
|
});
|
|
|
|
it("keeps current-turn authority for input-capable sessions", () => {
|
|
expect(clarificationTurnIdForSession("RUNNING", [turn(CURRENT_TURN_ID)])).toBe(CURRENT_TURN_ID);
|
|
});
|
|
});
|
|
|
|
describe("clarification authority fallbacks", () => {
|
|
it("uses compact session authority while turn history is unavailable", () => {
|
|
const messages = [message({ type: "clarification_request", metadata: { status: "pending" } })];
|
|
expect(findPendingClarification(messages, { pendingAction: null })).toBeNull();
|
|
expect(findPendingClarification(messages, { pendingAction: "clarification" })?.id).toBe(
|
|
"msg-1",
|
|
);
|
|
});
|
|
|
|
it("treats loaded empty turn history as having no active clarification", () => {
|
|
const messages = [message({ type: "clarification_request", metadata: { status: "pending" } })];
|
|
expect(findPendingClarification(messages, { currentTurnId: null })).toBeNull();
|
|
});
|
|
|
|
it("applies every turn-authority state to direct pending checks", () => {
|
|
const old = message({
|
|
id: "old",
|
|
turn_id: "turn-old",
|
|
type: "clarification_request",
|
|
metadata: { status: "pending" },
|
|
});
|
|
const current = message({
|
|
id: "current",
|
|
turn_id: CURRENT_TURN_ID,
|
|
type: "clarification_request",
|
|
metadata: { status: "pending" },
|
|
});
|
|
|
|
expect(hasPendingClarification([old, current], { currentTurnId: null })).toBe(false);
|
|
expect(
|
|
hasPendingClarification([old, current], {
|
|
currentTurnId: undefined,
|
|
pendingAction: "clarification",
|
|
}),
|
|
).toBe(true);
|
|
expect(hasPendingClarification([old, current], { currentTurnId: undefined })).toBe(false);
|
|
expect(hasPendingClarification([old, current], { currentTurnId: CURRENT_TURN_ID })).toBe(true);
|
|
expect(hasPendingClarification([old], { currentTurnId: CURRENT_TURN_ID })).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("newestDurableTurnId ordering", () => {
|
|
it("uses creation time before the turn id when start times match", () => {
|
|
expect(
|
|
newestDurableTurnId([
|
|
turn("turn-z", TURN_TIMESTAMP, TURN_TIMESTAMP),
|
|
turn("turn-a", TURN_TIMESTAMP, "2026-08-14T12:00:00.000000001Z"),
|
|
]),
|
|
).toBe("turn-a");
|
|
});
|
|
|
|
it("selects the newest durable turn with backend tie-break ordering", () => {
|
|
expect(
|
|
newestDurableTurnId([
|
|
{
|
|
id: "turn-a",
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
started_at: TURN_TIMESTAMP,
|
|
created_at: TURN_TIMESTAMP,
|
|
updated_at: TURN_TIMESTAMP,
|
|
},
|
|
{
|
|
id: "turn-b",
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
started_at: TURN_TIMESTAMP,
|
|
created_at: TURN_TIMESTAMP,
|
|
updated_at: TURN_TIMESTAMP,
|
|
},
|
|
]),
|
|
).toBe("turn-b");
|
|
});
|
|
|
|
it("uses preserved subsecond precision before the turn id tie-break", () => {
|
|
expect(
|
|
newestDurableTurnId([
|
|
{
|
|
id: "turn-z",
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
started_at: "2026-08-14T12:00:00.100Z",
|
|
created_at: TURN_TIMESTAMP,
|
|
updated_at: TURN_TIMESTAMP,
|
|
},
|
|
{
|
|
id: "turn-a",
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
started_at: "2026-08-14T12:00:00.900Z",
|
|
created_at: TURN_TIMESTAMP,
|
|
updated_at: TURN_TIMESTAMP,
|
|
},
|
|
]),
|
|
).toBe("turn-a");
|
|
});
|
|
|
|
it("orders an exact-second timestamp before a later fractional timestamp", () => {
|
|
expect(
|
|
newestDurableTurnId([
|
|
{
|
|
id: "turn-z",
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
started_at: TURN_TIMESTAMP,
|
|
created_at: TURN_TIMESTAMP,
|
|
updated_at: TURN_TIMESTAMP,
|
|
},
|
|
{
|
|
id: "turn-a",
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
started_at: "2026-08-14T12:00:00.1Z",
|
|
created_at: TURN_TIMESTAMP,
|
|
updated_at: TURN_TIMESTAMP,
|
|
},
|
|
]),
|
|
).toBe("turn-a");
|
|
});
|
|
|
|
it("orders an exact-second timestamp before a one-nanosecond-later timestamp", () => {
|
|
expect(
|
|
newestDurableTurnId([
|
|
{
|
|
id: "turn-z",
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
started_at: TURN_TIMESTAMP,
|
|
created_at: TURN_TIMESTAMP,
|
|
updated_at: TURN_TIMESTAMP,
|
|
},
|
|
{
|
|
id: "turn-a",
|
|
session_id: toSessionId("session-1"),
|
|
task_id: toTaskId("task-1"),
|
|
started_at: "2026-08-14T12:00:00.000000001Z",
|
|
created_at: TURN_TIMESTAMP,
|
|
updated_at: TURN_TIMESTAMP,
|
|
},
|
|
]),
|
|
).toBe("turn-a");
|
|
});
|
|
});
|
|
|
|
describe("hasPendingPermissionRequest", () => {
|
|
it("detects permission requests with pending status", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({ type: "permission_request", metadata: { status: "pending" } }),
|
|
]),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("treats missing permission status as pending", () => {
|
|
expect(hasPendingPermissionRequest([message({ type: "permission_request" })])).toBe(true);
|
|
});
|
|
|
|
it("ignores approved permission requests", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({ type: "permission_request", metadata: { status: "approved" } }),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("ignores rejected permission requests", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({ type: "permission_request", metadata: { status: "rejected" } }),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("ignores expired permission requests", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({ type: "permission_request", metadata: { status: "expired" } }),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("returns false for non-permission messages", () => {
|
|
expect(hasPendingPermissionRequest([message({ type: "message" })])).toBe(false);
|
|
});
|
|
|
|
it("returns false for empty or null input", () => {
|
|
expect(hasPendingPermissionRequest([])).toBe(false);
|
|
expect(hasPendingPermissionRequest(null)).toBe(false);
|
|
expect(hasPendingPermissionRequest(undefined)).toBe(false);
|
|
});
|
|
|
|
// Mixed-state: only the latest permission_request drives the UI. A stale
|
|
// pending row left behind by an earlier crash followed by a newer approved
|
|
// one must not light the amber icon — the agent is no longer blocked on
|
|
// the old row.
|
|
it("returns false when an older permission is still pending but a newer one is approved", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({ id: "old", type: "permission_request", metadata: { status: "pending" } }),
|
|
message({ id: "new", type: "permission_request", metadata: { status: "approved" } }),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("returns true when the latest permission is pending and earlier ones are resolved", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({ id: "a", type: "permission_request", metadata: { status: "approved" } }),
|
|
message({ id: "b", type: "permission_request", metadata: { status: "rejected" } }),
|
|
message({ id: "c", type: "permission_request", metadata: { status: "pending" } }),
|
|
]),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("returns false when all permission requests are resolved regardless of order", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({ id: "a", type: "permission_request", metadata: { status: "approved" } }),
|
|
message({ id: "b", type: "permission_request", metadata: { status: "rejected" } }),
|
|
message({ id: "c", type: "permission_request", metadata: { status: "expired" } }),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
// Turn-scoped boundary: walking back across a different turn_id ends the
|
|
// scan, so a pending row from a previous (crashed) turn must not leak into
|
|
// the current turn's indicator.
|
|
describe("hasPendingPermissionRequest — turn scoping", () => {
|
|
it("ignores a pending permission from a previous turn", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({
|
|
id: "old",
|
|
turn_id: "t1",
|
|
type: "permission_request",
|
|
metadata: { status: "pending" },
|
|
}),
|
|
message({ id: "current", turn_id: "t2", type: "message", content: "hi" }),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("detects a pending permission in the current turn", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({
|
|
id: "stale",
|
|
turn_id: "t1",
|
|
type: "permission_request",
|
|
metadata: { status: "pending" },
|
|
}),
|
|
message({
|
|
id: "active",
|
|
turn_id: "t2",
|
|
type: "permission_request",
|
|
metadata: { status: "pending" },
|
|
}),
|
|
]),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("stops at the turn boundary even when an older turn has a pending permission", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({
|
|
id: "old-pending",
|
|
turn_id: "t1",
|
|
type: "permission_request",
|
|
metadata: { status: "pending" },
|
|
}),
|
|
message({
|
|
id: "new-approved",
|
|
turn_id: "t2",
|
|
type: "permission_request",
|
|
metadata: { status: "approved" },
|
|
}),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("ignores a legacy null-turn_id pending permission when a turn is active", () => {
|
|
expect(
|
|
hasPendingPermissionRequest([
|
|
message({ id: "legacy", type: "permission_request", metadata: { status: "pending" } }),
|
|
message({ id: "current", turn_id: "t2", type: "message", content: "hi" }),
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
});
|