mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-11 10:56:45 +08:00
MUL-6291. Vitest's own breakdown for `@multica/views` showed the shape of the problem: environment 633s and import 644s against only 508s of actual test execution. Two thirds of a cold frontend-test run went on building a jsdom per file and importing its module graph, not on running assertions. - 94 pure-logic `.test.ts` files across views/web/desktop now declare `// @vitest-environment node`. Measured on the 45 converted views files: environment 53.15s -> 4ms, wall 8.72s -> 2.03s. Candidates were picked by module-graph analysis (skipping type-only imports) and rejected when the code under test branches on `typeof window`/`document`, where node would silently take the SSR path and still pass; the remainder were verified empirically and reverted where they failed. - The three jsdom setup files bail out when there is no DOM, so a node-env suite can share them. - schedule-editor: the pure state transitions (isFullDay, displayWindow, timeAnchorOf, defaultAtTime, defaultEveryTime, clampWindow, toggleDay) move out of the component into transitions.ts, with the full combination matrix as a table-driven node-env suite. 34 cases in 3ms, replacing four DOM mounts. - Cross-layer duplicates removed where a canonical layer already owns the matrix: originSourceUrl (skills/lib/origin.test.ts) was re-run through the refresh dialog and the detail page; needsSourceBackfill (core/onboarding/needs-backfill.test.ts) through the backfill modal. Each component keeps one wiring case naming the canonical file. No behaviour change: the transitions extraction is a pure move, and every removal names where the same contract is still covered. Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]>
131 lines
4.3 KiB
TypeScript
131 lines
4.3 KiB
TypeScript
// @vitest-environment node
|
|
import { describe, expect, it } from "vitest";
|
|
import type { TimelineEntry } from "@multica/core/types";
|
|
import {
|
|
collectThreadReplies,
|
|
resolvedThreadRootIds,
|
|
rootCommentIds,
|
|
} from "./thread-utils";
|
|
|
|
function comment(id: string, createdAt: string, parentId: string | null): TimelineEntry {
|
|
return {
|
|
type: "comment",
|
|
id,
|
|
actor_type: "member",
|
|
actor_id: "user-1",
|
|
content: id,
|
|
parent_id: parentId,
|
|
created_at: createdAt,
|
|
updated_at: createdAt,
|
|
comment_type: "comment",
|
|
} as TimelineEntry;
|
|
}
|
|
|
|
function bucketByParent(entries: TimelineEntry[]): Map<string, TimelineEntry[]> {
|
|
const map = new Map<string, TimelineEntry[]>();
|
|
for (const e of entries) {
|
|
if (!e.parent_id) continue;
|
|
const list = map.get(e.parent_id) ?? [];
|
|
list.push(e);
|
|
map.set(e.parent_id, list);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
describe("collectThreadReplies", () => {
|
|
it("orders a late nested reply after earlier sibling replies (#3691)", () => {
|
|
// R1 (50m ago) triggered a slow agent; R2 (30m) and R3 (10m) arrived while
|
|
// it ran; D (3m ago) is the agent's reply, forced to nest under R1. A
|
|
// depth-first walk yields R1-D-R2-R3; the thread must read R1-R2-R3-D.
|
|
const r1 = comment("r1", "2026-06-11T10:00:00Z", "root");
|
|
const r2 = comment("r2", "2026-06-11T10:20:00Z", "root");
|
|
const r3 = comment("r3", "2026-06-11T10:40:00Z", "root");
|
|
const d = comment("d", "2026-06-11T10:47:00Z", "r1");
|
|
|
|
const out = collectThreadReplies("root", bucketByParent([r1, r2, r3, d]));
|
|
|
|
expect(out.map((e) => e.id)).toEqual(["r1", "r2", "r3", "d"]);
|
|
});
|
|
|
|
it("still returns every descendant across nesting levels", () => {
|
|
const r1 = comment("r1", "2026-06-11T10:00:00Z", "root");
|
|
const d1 = comment("d1", "2026-06-11T10:05:00Z", "r1");
|
|
const d2 = comment("d2", "2026-06-11T10:10:00Z", "d1");
|
|
|
|
const out = collectThreadReplies("root", bucketByParent([r1, d1, d2]));
|
|
|
|
expect(out.map((e) => e.id)).toEqual(["r1", "d1", "d2"]);
|
|
});
|
|
|
|
it("breaks created_at ties by id so the order is deterministic", () => {
|
|
const b = comment("b", "2026-06-11T10:00:00Z", "root");
|
|
const a = comment("a", "2026-06-11T10:00:00Z", "b");
|
|
|
|
const out = collectThreadReplies("root", bucketByParent([b, a]));
|
|
|
|
expect(out.map((e) => e.id)).toEqual(["a", "b"]);
|
|
});
|
|
});
|
|
|
|
function activity(id: string, createdAt: string): TimelineEntry {
|
|
return {
|
|
type: "activity",
|
|
id,
|
|
actor_type: "member",
|
|
actor_id: "user-1",
|
|
action: "status_changed",
|
|
created_at: createdAt,
|
|
} as TimelineEntry;
|
|
}
|
|
|
|
describe("rootCommentIds", () => {
|
|
it("returns top-level comments only, skipping replies and activities", () => {
|
|
const entries = [
|
|
activity("act-1", "2026-06-11T09:00:00Z"),
|
|
comment("root-1", "2026-06-11T10:00:00Z", null),
|
|
comment("reply-1", "2026-06-11T10:05:00Z", "root-1"),
|
|
comment("root-2", "2026-06-11T11:00:00Z", null),
|
|
];
|
|
|
|
expect(rootCommentIds(entries)).toEqual(["root-1", "root-2"]);
|
|
});
|
|
});
|
|
|
|
describe("resolvedThreadRootIds", () => {
|
|
it("includes root-resolved and reply-resolved threads, excludes unresolved", () => {
|
|
const rootResolved = {
|
|
...comment("root-resolved", "2026-06-11T10:00:00Z", null),
|
|
resolved_at: "2026-06-11T12:00:00Z",
|
|
};
|
|
const replyResolvedRoot = comment("root-reply-resolved", "2026-06-11T10:10:00Z", null);
|
|
const resolutionReply = {
|
|
...comment("reply-resolution", "2026-06-11T10:20:00Z", "root-reply-resolved"),
|
|
resolved_at: "2026-06-11T12:30:00Z",
|
|
};
|
|
const openRoot = comment("root-open", "2026-06-11T10:30:00Z", null);
|
|
const openReply = comment("reply-open", "2026-06-11T10:40:00Z", "root-open");
|
|
|
|
const ids = resolvedThreadRootIds([
|
|
activity("act-1", "2026-06-11T09:00:00Z"),
|
|
rootResolved,
|
|
replyResolvedRoot,
|
|
resolutionReply,
|
|
openRoot,
|
|
openReply,
|
|
]);
|
|
|
|
expect(ids).toEqual(["root-resolved", "root-reply-resolved"]);
|
|
});
|
|
|
|
it("detects a resolution on a nested reply", () => {
|
|
const root = comment("root", "2026-06-11T10:00:00Z", null);
|
|
const reply = comment("reply", "2026-06-11T10:05:00Z", "root");
|
|
const nested = {
|
|
...comment("nested", "2026-06-11T10:10:00Z", "reply"),
|
|
resolved_at: "2026-06-11T12:00:00Z",
|
|
};
|
|
|
|
expect(resolvedThreadRootIds([root, reply, nested])).toEqual(["root"]);
|
|
});
|
|
});
|