2026-03-31 04:32:58 +00:00
|
|
|
import * as assert from "node:assert/strict";
|
|
|
|
|
import test from "node:test";
|
2026-05-04 19:40:03 +00:00
|
|
|
import { CommentSessionController } from "../src/comments/commentSessionController";
|
2026-03-31 04:32:58 +00:00
|
|
|
import type { DraftComment } from "../src/domain/drafts";
|
|
|
|
|
|
|
|
|
|
function createDraft(overrides: Partial<DraftComment> = {}): DraftComment {
|
|
|
|
|
return {
|
|
|
|
|
id: overrides.id ?? "draft-1",
|
|
|
|
|
filePath: overrides.filePath ?? "docs/architecture.md",
|
|
|
|
|
startLine: overrides.startLine ?? 10,
|
|
|
|
|
startChar: overrides.startChar ?? 2,
|
|
|
|
|
endLine: overrides.endLine ?? 10,
|
|
|
|
|
endChar: overrides.endChar ?? 18,
|
|
|
|
|
selectedText: overrides.selectedText ?? "Module Blueprint",
|
|
|
|
|
selectedTextHash: overrides.selectedTextHash ?? "hash:module-blueprint",
|
|
|
|
|
comment: overrides.comment ?? "Draft note",
|
|
|
|
|
timestamp: overrides.timestamp ?? 123,
|
|
|
|
|
anchorKind: overrides.anchorKind ?? "selection",
|
|
|
|
|
orphaned: overrides.orphaned ?? false,
|
|
|
|
|
mode: overrides.mode ?? "new",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createHarness() {
|
|
|
|
|
let refreshCommentViewsCount = 0;
|
|
|
|
|
let refreshEditorDecorationsCount = 0;
|
|
|
|
|
let refreshMarkdownPreviewsCount = 0;
|
|
|
|
|
const clearedMarkdownSelections: string[] = [];
|
|
|
|
|
|
|
|
|
|
const controller = new CommentSessionController({
|
|
|
|
|
refreshCommentViews: async () => {
|
|
|
|
|
refreshCommentViewsCount += 1;
|
|
|
|
|
},
|
|
|
|
|
refreshEditorDecorations: () => {
|
|
|
|
|
refreshEditorDecorationsCount += 1;
|
|
|
|
|
},
|
|
|
|
|
refreshMarkdownPreviews: () => {
|
|
|
|
|
refreshMarkdownPreviewsCount += 1;
|
|
|
|
|
},
|
|
|
|
|
clearMarkdownSelection: (filePath) => {
|
|
|
|
|
clearedMarkdownSelections.push(filePath);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
controller,
|
|
|
|
|
getRefreshCommentViewsCount: () => refreshCommentViewsCount,
|
|
|
|
|
getRefreshEditorDecorationsCount: () => refreshEditorDecorationsCount,
|
|
|
|
|
getRefreshMarkdownPreviewsCount: () => refreshMarkdownPreviewsCount,
|
|
|
|
|
clearedMarkdownSelections,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 01:57:48 +00:00
|
|
|
test("comment session controller refreshes comment views when nested comments are toggled", async () => {
|
2026-04-07 06:40:07 +00:00
|
|
|
const harness = createHarness();
|
|
|
|
|
|
2026-04-30 05:25:16 +00:00
|
|
|
assert.equal(harness.controller.shouldShowNestedComments(), true);
|
2026-05-05 16:39:14 +00:00
|
|
|
assert.equal(await harness.controller.setShowNestedComments(false), true);
|
|
|
|
|
assert.equal(harness.controller.shouldShowNestedComments(), false);
|
2026-04-07 06:40:07 +00:00
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 1);
|
|
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 0);
|
|
|
|
|
assert.equal(harness.getRefreshMarkdownPreviewsCount(), 0);
|
|
|
|
|
|
2026-05-05 16:39:14 +00:00
|
|
|
assert.equal(await harness.controller.setShowNestedComments(false), false);
|
2026-04-13 01:57:48 +00:00
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 1);
|
2026-04-07 06:40:07 +00:00
|
|
|
});
|
|
|
|
|
|
2026-04-20 18:52:46 +00:00
|
|
|
test("comment session controller can override nested comment visibility per thread and reset with show all", async () => {
|
2026-04-20 17:36:38 +00:00
|
|
|
const harness = createHarness();
|
|
|
|
|
|
2026-04-30 05:25:16 +00:00
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-1"), true);
|
2026-05-05 16:39:14 +00:00
|
|
|
assert.equal(await harness.controller.setShowNestedCommentsForThread("thread-1", false), true);
|
|
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-1"), false);
|
|
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-2"), true);
|
2026-04-20 17:36:38 +00:00
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 1);
|
|
|
|
|
|
2026-05-05 16:39:14 +00:00
|
|
|
assert.equal(await harness.controller.setShowNestedCommentsForThread("thread-1", false), false);
|
2026-04-20 17:36:38 +00:00
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 1);
|
|
|
|
|
|
2026-04-30 05:25:16 +00:00
|
|
|
assert.equal(await harness.controller.setShowNestedComments(true), true);
|
|
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-1"), true);
|
|
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-2"), true);
|
2026-04-20 17:36:38 +00:00
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 2);
|
|
|
|
|
|
2026-04-30 05:25:16 +00:00
|
|
|
assert.equal(await harness.controller.setShowNestedCommentsForThread("thread-1", false), true);
|
|
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-1"), false);
|
|
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-2"), true);
|
2026-04-20 17:36:38 +00:00
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 3);
|
2026-04-20 18:52:46 +00:00
|
|
|
|
2026-04-30 05:25:16 +00:00
|
|
|
assert.equal(await harness.controller.setShowNestedComments(false), true);
|
|
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-1"), false);
|
|
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-2"), false);
|
2026-04-20 18:52:46 +00:00
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 4);
|
2026-04-20 17:36:38 +00:00
|
|
|
});
|
|
|
|
|
|
2026-04-23 20:33:53 +00:00
|
|
|
test("comment session controller can skip sidebar rerenders for deleted and nested visibility updates", async () => {
|
|
|
|
|
const harness = createHarness();
|
|
|
|
|
|
|
|
|
|
assert.equal(await harness.controller.setShowDeletedComments(true, {
|
|
|
|
|
skipCommentViewRefresh: true,
|
|
|
|
|
}), true);
|
|
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 0);
|
|
|
|
|
|
2026-05-05 16:39:14 +00:00
|
|
|
assert.equal(await harness.controller.setShowNestedCommentsForThread("thread-1", false, {
|
2026-04-23 20:33:53 +00:00
|
|
|
skipCommentViewRefresh: true,
|
|
|
|
|
}), true);
|
2026-05-05 16:39:14 +00:00
|
|
|
assert.equal(harness.controller.shouldShowNestedCommentsForThread("thread-1"), false);
|
2026-04-23 20:33:53 +00:00
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 0);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-31 04:32:58 +00:00
|
|
|
test("comment session controller tracks revealed comments and clears markdown selections on reset", () => {
|
|
|
|
|
const harness = createHarness();
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
harness.controller.setRevealedCommentState("docs/architecture.md", "comment-1"),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
harness.controller.getRevealedCommentId("docs/architecture.md"),
|
|
|
|
|
"comment-1",
|
|
|
|
|
);
|
|
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 1);
|
|
|
|
|
assert.equal(harness.getRefreshMarkdownPreviewsCount(), 1);
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
harness.controller.setRevealedCommentState("docs/architecture.md", "comment-1"),
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 1);
|
|
|
|
|
assert.equal(harness.getRefreshMarkdownPreviewsCount(), 1);
|
|
|
|
|
|
|
|
|
|
harness.controller.clearRevealedCommentSelection();
|
|
|
|
|
assert.equal(harness.controller.getRevealedCommentId("docs/architecture.md"), null);
|
|
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 2);
|
|
|
|
|
assert.equal(harness.getRefreshMarkdownPreviewsCount(), 2);
|
|
|
|
|
assert.deepEqual(harness.clearedMarkdownSelections, ["docs/architecture.md"]);
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-05 15:28:32 +00:00
|
|
|
test("comment session controller can skip markdown preview rerender for a revealed comment update", () => {
|
|
|
|
|
const harness = createHarness();
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
2026-05-13 18:44:11 +00:00
|
|
|
harness.controller.setRevealedCommentState("Aside index.md", "comment-7", {
|
2026-04-05 15:28:32 +00:00
|
|
|
refreshMarkdownPreviews: false,
|
|
|
|
|
}),
|
|
|
|
|
true,
|
|
|
|
|
);
|
2026-05-13 18:44:11 +00:00
|
|
|
assert.equal(harness.controller.getRevealedCommentId("Aside index.md"), "comment-7");
|
2026-04-05 15:28:32 +00:00
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 1);
|
|
|
|
|
assert.equal(harness.getRefreshMarkdownPreviewsCount(), 0);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-31 04:32:58 +00:00
|
|
|
test("comment session controller still refreshes reveal state surfaces when no comment is active", () => {
|
|
|
|
|
const harness = createHarness();
|
|
|
|
|
|
|
|
|
|
harness.controller.clearRevealedCommentSelection();
|
|
|
|
|
|
|
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 1);
|
|
|
|
|
assert.equal(harness.getRefreshMarkdownPreviewsCount(), 1);
|
|
|
|
|
assert.deepEqual(harness.clearedMarkdownSelections, []);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("comment session controller manages draft state and refresh side effects", async () => {
|
|
|
|
|
const harness = createHarness();
|
|
|
|
|
const draft = createDraft();
|
|
|
|
|
|
2026-05-13 18:44:11 +00:00
|
|
|
await harness.controller.setDraftComment(draft, "Aside index.md");
|
2026-03-31 04:32:58 +00:00
|
|
|
assert.deepEqual(harness.controller.getDraftForFile(draft.filePath), draft);
|
2026-05-13 18:44:11 +00:00
|
|
|
assert.deepEqual(harness.controller.getDraftForView("Aside index.md"), draft);
|
|
|
|
|
assert.equal(harness.controller.getDraftHostFilePath(), "Aside index.md");
|
2026-03-31 04:32:58 +00:00
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 1);
|
|
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 1);
|
|
|
|
|
|
|
|
|
|
assert.equal(harness.controller.updateDraftCommentText(draft.id, "Updated draft"), true);
|
|
|
|
|
assert.equal(harness.controller.getDraftComment()?.comment, "Updated draft");
|
|
|
|
|
|
|
|
|
|
harness.controller.setSavingDraftCommentId(draft.id);
|
|
|
|
|
assert.equal(harness.controller.isSavingDraft(draft.id), true);
|
|
|
|
|
|
|
|
|
|
assert.equal(await harness.controller.cancelDraft("other-id"), false);
|
|
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 1);
|
|
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 1);
|
|
|
|
|
|
|
|
|
|
assert.equal(await harness.controller.cancelDraft(draft.id), true);
|
|
|
|
|
assert.equal(harness.controller.getDraftComment(), null);
|
|
|
|
|
assert.equal(harness.controller.getDraftHostFilePath(), null);
|
|
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 2);
|
|
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 2);
|
|
|
|
|
});
|
2026-04-22 04:30:41 +00:00
|
|
|
|
|
|
|
|
test("comment session controller can skip the initial comment view refresh when opening a draft", async () => {
|
|
|
|
|
const harness = createHarness();
|
|
|
|
|
const draft = createDraft();
|
|
|
|
|
|
|
|
|
|
await harness.controller.setDraftComment(draft, draft.filePath, {
|
|
|
|
|
skipCommentViewRefresh: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(harness.controller.getDraftForView(draft.filePath), draft);
|
|
|
|
|
assert.equal(harness.getRefreshCommentViewsCount(), 0);
|
|
|
|
|
assert.equal(harness.getRefreshEditorDecorationsCount(), 1);
|
|
|
|
|
});
|