194 lines · 7.8 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest"; import {  acknowledgeComposerNativeEvent,  assumeComposerControlledState,  isComposerNativeEcho,  pruneAcknowledgedComposerNativeEvents,  resolveComposerControlledEventCount,} from "./composerEditorRevision"; describe("acknowledgeComposerNativeEvent", () => {  it("advances to newer native text revisions", () => {    expect(acknowledgeComposerNativeEvent(4, 5)).toBe(5);  });   it("accepts a duplicate event from the current native revision", () => {    expect(acknowledgeComposerNativeEvent(5, 5)).toBe(5);  });   it("rejects events older than the latest native text revision", () => {    expect(acknowledgeComposerNativeEvent(5, 4)).toBeNull();  });   it("rejects malformed revision counters", () => {    expect(acknowledgeComposerNativeEvent(5, Number.NaN)).toBeNull();    expect(acknowledgeComposerNativeEvent(5, 5.5)).toBeNull();  });}); describe("isComposerNativeEcho", () => {  const snapshots = [{ eventCount: 3, value: "native", selection: { start: 6, end: 6 } }];   it("matches the exact native text revision and selection", () => {    expect(isComposerNativeEcho("native", { start: 6, end: 6 }, 3, snapshots)).toBe(true);  });   it("does not claim parent-driven selection or repeated-text updates", () => {    expect(isComposerNativeEcho("native", { start: 2, end: 2 }, 3, snapshots)).toBe(false);    expect(isComposerNativeEcho("native", { start: 6, end: 6 }, 4, snapshots)).toBe(false);    expect(isComposerNativeEcho("parent edit", { start: 6, end: 6 }, 3, snapshots)).toBe(false);  });   it("matches value and revision when selection is uncontrolled", () => {    expect(isComposerNativeEcho("native", null, 3, snapshots)).toBe(true);  });   it("does not claim a controlled selection against an assumed state without one", () => {    // An echo payload serializes `selection: null`; classifying a controlled    // selection as an echo of an assumed state would drop a parent caret move.    const assumed = [{ eventCount: 3, value: "native", selection: null }];    expect(isComposerNativeEcho("native", { start: 0, end: 0 }, 3, assumed)).toBe(false);    expect(isComposerNativeEcho("other", { start: 0, end: 0 }, 3, assumed)).toBe(false);  });   it("matches an assumed state when selection is uncontrolled", () => {    const assumed = [{ eventCount: 3, value: "native", selection: null }];    expect(isComposerNativeEcho("native", null, 3, assumed)).toBe(true);  });}); describe("resolveComposerControlledEventCount", () => {  const snapshots = [    { eventCount: 0, value: "", selection: { start: 0, end: 0 } },    { eventCount: 2, value: "a", selection: { start: 1, end: 1 } },    { eventCount: 4, value: "ab", selection: { start: 2, end: 2 } },  ];   it("tags a delayed parent value with the native revision that produced it", () => {    expect(resolveComposerControlledEventCount("a", { start: 1, end: 1 }, 4, snapshots)).toBe(2);  });   it("does not acknowledge the pre-edit parent value as the latest revision", () => {    expect(resolveComposerControlledEventCount("", { start: 0, end: 0 }, 4, snapshots)).toBe(0);  });   it("acknowledges the latest native value at the latest revision", () => {    expect(resolveComposerControlledEventCount("ab", { start: 2, end: 2 }, 4, snapshots)).toBe(4);  });   it("allows an unmatched parent-driven edit at the latest native revision", () => {    expect(resolveComposerControlledEventCount("/plan ", { start: 6, end: 6 }, 4, snapshots)).toBe(      4,    );  });   it("uses the newest revision when selection events repeat the same value", () => {    expect(      resolveComposerControlledEventCount("ab", { start: 1, end: 1 }, 5, [        ...snapshots,        { eventCount: 5, value: "ab", selection: { start: 1, end: 1 } },      ]),    ).toBe(5);  });   it("keeps a stale selection paired with current text behind the native revision", () => {    expect(resolveComposerControlledEventCount("ab", { start: 1, end: 1 }, 4, snapshots)).toBe(3);  });   it("does not control selection when no selection prop is provided", () => {    expect(resolveComposerControlledEventCount("ab", null, 4, snapshots)).toBe(4);  });}); describe("pruneAcknowledgedComposerNativeEvents", () => {  it("releases an arbitrarily long acknowledged backlog without a fixed-size cliff", () => {    const snapshots = Array.from({ length: 1_000 }, (_, eventCount) => ({      eventCount,      value: `value-${eventCount}`,      selection: { start: eventCount, end: eventCount },    }));     expect(pruneAcknowledgedComposerNativeEvents(snapshots, 999)).toEqual([snapshots[999]]);  });   it("retains native events that arrive after the acknowledged render", () => {    const snapshots = [      { eventCount: 40, value: "a", selection: { start: 1, end: 1 } },      { eventCount: 41, value: "ab", selection: { start: 2, end: 2 } },    ];     expect(pruneAcknowledgedComposerNativeEvents(snapshots, 40)).toEqual(snapshots);  });   it("retains the newest acknowledged snapshot so settled re-renders stay echoes", () => {    const snapshots = [      { eventCount: 40, value: "a", selection: { start: 1, end: 1 } },      { eventCount: 41, value: "ab", selection: { start: 2, end: 2 } },      { eventCount: 42, value: "abc", selection: { start: 3, end: 3 } },    ];     const pruned = pruneAcknowledgedComposerNativeEvents(snapshots, 42);    expect(pruned).toEqual([snapshots[2]]);    expect(isComposerNativeEcho("abc", { start: 3, end: 3 }, 42, pruned)).toBe(true);  });   it("keeps the newest of several snapshots sharing the acknowledged revision", () => {    const snapshots = [      { eventCount: 41, value: "ab", selection: { start: 2, end: 2 } },      { eventCount: 41, value: "ab", selection: { start: 1, end: 1 } },    ];     expect(pruneAcknowledgedComposerNativeEvents(snapshots, 41)).toEqual([snapshots[1]]);  });}); describe("assumeComposerControlledState", () => {  it("replaces the acknowledged history with the applied controlled state", () => {    const snapshots = [{ eventCount: 3, value: "typed", selection: { start: 5, end: 5 } }];     expect(assumeComposerControlledState(snapshots, 3, "")).toEqual([      { eventCount: 3, value: "", selection: null },    ]);  });   it("keeps native events that raced past the controlled revision", () => {    const snapshots = [      { eventCount: 3, value: "typed", selection: { start: 5, end: 5 } },      { eventCount: 4, value: "typed!", selection: { start: 6, end: 6 } },    ];     expect(assumeComposerControlledState(snapshots, 3, "")).toEqual([      { eventCount: 3, value: "", selection: null },      snapshots[1],    ]);  });   it("applies a parent caret move on the assumed value at the assumed revision", () => {    // Same value, new caret: not an echo (so the selection is serialized) but    // still stamped at the assumed revision so the editor accepts it.    const snapshots = assumeComposerControlledState([], 3, "typed");     expect(isComposerNativeEcho("typed", { start: 2, end: 2 }, 3, snapshots)).toBe(false);    expect(resolveComposerControlledEventCount("typed", { start: 2, end: 2 }, 3, snapshots)).toBe(      3,    );  });   it("re-applies a parent value that round-trips back to an acknowledged state", () => {    // Native acknowledged "typed", the parent then controlled the editor to ""    // (a send clearing the draft) and back to "typed" (the send failed and the    // draft was restored). The restore must be a fresh non-echo edit stamped at    // the current revision, not an echo the editor would drop.    const snapshots = assumeComposerControlledState(      [{ eventCount: 3, value: "typed", selection: { start: 5, end: 5 } }],      3,      "",    );     expect(isComposerNativeEcho("typed", { start: 5, end: 5 }, 3, snapshots)).toBe(false);    expect(resolveComposerControlledEventCount("typed", { start: 5, end: 5 }, 3, snapshots)).toBe(      3,    );  });});