Files
docmd-io--docmd/packages/plugins/threads/e2e/comment-display.e2e.spec.ts
T
wehub-resource-sync 6db8fca185
docmd CI verification / verify (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:31:55 +08:00

143 lines
5.5 KiB
TypeScript

/**
* Comment display tests - adapted from old comment-display.e2e.spec.ts.
*
* Changes from old plugin:
* - Comments are server-rendered HTML (.threads-comment) not Lit components
* - Server-rendered comments have .threads-comment__meta and .threads-comment__body
* - Author info comes from data-author attribute on .threads-comment element
* - Date comes from data-date attribute
* - No "just now" time formatting (server renders static date strings like "2026-03-09")
*/
import { test, expect } from "@playwright/test";
import { Actor } from "./screenplay.ts";
import { cleanThreads, setAuthor, seedAndReload, waitForReload } from "./helpers.ts";
let user: Actor;
test.beforeEach(async ({ page }) => {
user = new Actor("TestUser", page);
await page.goto("/");
await page.waitForSelector("threads-app", { state: "attached", timeout: 10_000 });
await setAuthor(page, "TestUser");
await cleanThreads(page);
await waitForReload(page);
});
test.describe("comment display", () => {
test("author name is displayed on the comment", async () => {
const { id: threadId } = await seedAndReload(user.page, {
author: "Alice",
body: "Hello from Alice",
});
const thread = user.page.locator(`.threads-thread[data-thread-id="${threadId}"]`);
await expect(thread).toBeAttached({ timeout: 5_000 });
// Server-rendered comment should show the author
const meta = thread.locator(".threads-comment__meta").first();
await expect(meta).toContainText("Alice");
});
test("comment body is displayed", async () => {
const { id: threadId } = await seedAndReload(user.page, {
author: "TestUser",
body: "This is the comment body text",
});
const thread = user.page.locator(`.threads-thread[data-thread-id="${threadId}"]`);
await expect(thread).toBeAttached({ timeout: 5_000 });
const body = thread.locator(".threads-comment__body").first();
await expect(body).toContainText("This is the comment body text");
});
test("date is displayed on the comment", async () => {
const { id: threadId } = await seedAndReload(user.page, {
author: "TestUser",
body: "Date display test",
});
const thread = user.page.locator(`.threads-thread[data-thread-id="${threadId}"]`);
await expect(thread).toBeAttached({ timeout: 5_000 });
// Server-rendered meta should contain a date (format: YYYY-MM-DD)
const meta = thread.locator(".threads-comment__meta").first();
const metaText = await meta.textContent();
// Should contain a date pattern like 2026-03-09
expect(metaText).toMatch(/\d{4}-\d{2}-\d{2}/);
});
test("comment has data-author attribute", async () => {
const { id: threadId } = await seedAndReload(user.page, {
author: "Bob",
body: "Attribute test",
});
const thread = user.page.locator(`.threads-thread[data-thread-id="${threadId}"]`);
const comment = thread.locator(".threads-comment").first();
await expect(comment).toHaveAttribute("data-author", "Bob");
});
test("multiple comments displayed in order", async () => {
const { id: threadId } = await seedAndReload(user.page, {
author: "Alice",
body: "First comment",
replies: [
{ author: "Bob", body: "Second comment" },
{ author: "Charlie", body: "Third comment" },
],
});
let thread = user.page.locator(`.threads-thread[data-thread-id="${threadId}"]`);
await expect(thread).toBeAttached({ timeout: 5_000 });
// The dev server may need an additional reload to pick up the last reply
let comments = thread.locator(".threads-comment");
if ((await comments.count()) < 3) {
await waitForReload(user.page);
thread = user.page.locator(`.threads-thread[data-thread-id="${threadId}"]`);
comments = thread.locator(".threads-comment");
}
await expect(comments).toHaveCount(3);
await expect(comments.nth(0).locator(".threads-comment__body")).toContainText("First comment");
await expect(comments.nth(1).locator(".threads-comment__body")).toContainText("Second comment");
await expect(comments.nth(2).locator(".threads-comment__body")).toContainText("Third comment");
});
});
test.describe("markdown rendering in comments", () => {
test("comment body renders inline markdown from markdown-it", async () => {
// Server-rendered comments go through markdown-it, which handles bold/italic/code
const { id: threadId } = await seedAndReload(user.page, {
body: "Use `console.log()` for debugging",
});
const thread = user.page.locator(`.threads-thread[data-thread-id="${threadId}"]`);
const body = thread.locator(".threads-comment__body").first();
await expect(body).toBeAttached({ timeout: 5_000 });
// markdown-it should render backtick as <code>
const codeEl = body.locator("code");
await expect(codeEl).toBeAttached();
});
test("HTML special characters are escaped", async () => {
const { id: threadId } = await seedAndReload(user.page, {
body: "<script>alert('xss')</script>",
});
const thread = user.page.locator(`.threads-thread[data-thread-id="${threadId}"]`);
const body = thread.locator(".threads-comment__body").first();
await expect(body).toBeAttached({ timeout: 5_000 });
// No live <script> element must exist inside the comment body
await expect(body.locator("script")).toHaveCount(0);
// The raw innerHTML must contain escaped entities
const rawHtml = await body.evaluate((el) => el.innerHTML);
expect(rawHtml).not.toContain("<script>");
});
});