1// @vitest-environment jsdom
2import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
3import { readThreadFindPosition } from "./threadFindPosition";
4
5afterEach(() => vi.restoreAllMocks());
6beforeEach(() => {
7 Object.defineProperty(Range.prototype, "getBoundingClientRect", {
8 configurable: true,
9 writable: true,
10 value: () => new DOMRect(),
11 });
12});
13
14function viewport(text = "COD4 COD4") {
15 const container = document.createElement("div");
16 container.innerHTML = `<div data-timeline-row-id="above"><div data-thread-find-text>COD4</div></div>
17 <div data-timeline-row-id="reading"><div data-thread-find-text>${text}</div></div>`;
18 vi.spyOn(container, "getBoundingClientRect").mockReturnValue(new DOMRect(0, 100, 500, 400));
19 vi.spyOn(container.children[0]!, "getBoundingClientRect").mockReturnValue(
20 new DOMRect(0, 0, 500, 80),
21 );
22 vi.spyOn(container.children[1]!, "getBoundingClientRect").mockReturnValue(
23 new DOMRect(0, 50, 500, 300),
24 );
25 return container;
26}
27
28describe("reading position for find", () => {
29 it("skips earlier rows and occurrences above the viewport within the current message", () => {
30 const container = viewport();
31 vi.spyOn(Range.prototype, "getBoundingClientRect").mockImplementation(function (this: Range) {
32 return new DOMRect(0, this.startOffset === 0 ? 60 : 160, 40, 20);
33 });
34 expect(readThreadFindPosition(container, "COD4", (id) => id, 100)).toEqual({
35 entryId: "reading",
36 occurrence: 1,
37 });
38 });
39
40 it("starts after the current message when all its occurrences are above the viewport", () => {
41 const container = viewport();
42 vi.spyOn(Range.prototype, "getBoundingClientRect").mockReturnValue(new DOMRect(0, 60, 40, 20));
43 expect(readThreadFindPosition(container, "COD4", (id) => id, 100)).toEqual({
44 entryId: "reading",
45 occurrence: 2,
46 });
47 });
48
49 it("anchors unmatched or folded content and excludes rows hidden behind the composer", () => {
50 const container = viewport("nothing");
51 expect(readThreadFindPosition(container, "COD4", () => "folded-message", 100)).toEqual({
52 entryId: "folded-message",
53 occurrence: 0,
54 });
55 vi.spyOn(container.children[1]!, "getBoundingClientRect").mockReturnValue(
56 new DOMRect(0, 420, 500, 300),
57 );
58 expect(readThreadFindPosition(container, "COD4", (id) => id, 100)).toBeUndefined();
59 });
60});