/**
* Tests for threads container rules - markdown-it rendering of ::: threads blocks
*
* Run: node packages/plugins/threads/tests/containers.test.js
*
* @copyright Copyright (c) 2026 Saulo Vallory
* @license MIT
*/
import MarkdownIt from 'markdown-it';
import { setup as setupContainers } from '../dist/plugin/containers.js';
let passed = 0;
let total = 0;
function assert(condition, msg) {
total++;
if (!condition) {
console.error(`FAIL: ${msg}`);
process.exit(1);
}
passed++;
console.log(` PASS: ${msg}`);
}
function assertContains(html, substring, msg) {
assert(html.includes(substring), `${msg}\n expected to contain: ${substring}\n actual: ${html.slice(0, 500)}`);
}
function assertNotContains(html, substring, msg) {
assert(!html.includes(substring), `${msg}\n expected NOT to contain: ${substring}\n actual: ${html.slice(0, 500)}`);
}
function createMd() {
const md = MarkdownIt({ html: true });
setupContainers(md);
return md;
}
// ─── Test 1: Full threads block renders with correct structure ───
console.log('\nTest 1: Full threads block renders correct HTML structure');
const md1 = createMd();
const fullInput = `::: threads
::: thread t-abc123
::: comment "alice" "2026-03-07"
This is the first comment
:::
:::
:::
`;
const html1 = md1.render(fullInput);
assertContains(html1, 'class="threads-sidebar"', 'threads wrapper has correct class');
assertContains(html1, 'class="threads-thread"', 'thread has correct class');
assertContains(html1, 'data-thread-id="t-abc123"', 'thread has data-thread-id');
assertContains(html1, 'class="threads-comment"', 'comment has correct class');
assertContains(html1, 'data-author="alice"', 'comment has data-author');
assertContains(html1, 'data-date="2026-03-07"', 'comment has data-date');
assertContains(html1, 'class="threads-comment__meta"', 'comment meta renders');
assertContains(html1, 'class="threads-comment__body"', 'comment body wrapper renders');
assertContains(html1, 'This is the first comment', 'comment body content renders');
// ─── Test 2: Thread ID attribute is present ───
console.log('\nTest 2: Thread id attribute');
const md2 = createMd();
const idInput = `::: threads
::: thread t-xyz789
::: comment "bob" "2026-01-01"
Test
:::
:::
:::
`;
const html2 = md2.render(idInput);
assertContains(html2, 'data-thread-id="t-xyz789"', 'thread id attribute is present and correct');
// ─── Test 3: Resolved thread has resolved class ───
console.log('\nTest 3: Resolved thread has resolved class');
const md3 = createMd();
const resolvedInput = `::: threads
::: thread t-res001 resolved "charlie" "2026-03-08"
::: comment "charlie" "2026-03-08"
Fixed the typo
:::
:::
:::
`;
const html3 = md3.render(resolvedInput);
assertContains(html3, 'threads-thread--resolved', 'resolved thread has resolved class');
assertContains(html3, 'data-thread-id="t-res001"', 'resolved thread has id attribute');
// ─── Test 4: Comment has data-author and data-date ───
console.log('\nTest 4: Comment data attributes');
const md4 = createMd();
const commentInput = `::: threads
::: thread t-c001
::: comment "dave" "2026-05-15"
Comment with attributes
:::
:::
:::
`;
const html4 = md4.render(commentInput);
assertContains(html4, 'data-author="dave"', 'comment has data-author');
assertContains(html4, 'data-date="2026-05-15"', 'comment has data-date');
// ─── Test 5: Comment meta header renders author and date ───
console.log('\nTest 5: Comment meta header');
const md5 = createMd();
const metaInput = `::: threads
::: thread t-m001
::: comment "eve" "2026-06-20"
Testing meta
:::
:::
:::
`;
const html5 = md5.render(metaInput);
assertContains(html5, 'eve', 'meta includes author in strong tag');
assertContains(html5, '2026-06-20', 'meta includes date');
// ─── Test 6: Reactions container renders ───
console.log('\nTest 6: Reactions container');
const md6 = createMd();
const reactionsInput = `::: threads
::: thread t-r001
::: comment "frank" "2026-07-01"
Comment with reactions
::: reactions
- 👍 alice, bob
- 🎉 charlie
:::
:::
:::
:::
`;
const html6 = md6.render(reactionsInput);
assertContains(html6, 'class="threads-reactions"', 'reactions container has correct class');
// ─── Test 7: Multiple threads render ───
console.log('\nTest 7: Multiple threads');
const md7 = createMd();
const multiInput = `::: threads
::: thread t-first
::: comment "alice" "2026-01-01"
First thread
:::
:::
::: thread t-second
::: comment "bob" "2026-01-02"
Second thread
:::
:::
:::
`;
const html7 = md7.render(multiInput);
assertContains(html7, 'data-thread-id="t-first"', 'first thread renders');
assertContains(html7, 'data-thread-id="t-second"', 'second thread renders');
// ─── Test 8: Unresolved thread does NOT have resolved class ───
console.log('\nTest 8: Unresolved thread lacks resolved class');
const md8 = createMd();
const unresolvedInput = `::: threads
::: thread t-unres
::: comment "alice" "2026-01-01"
Not resolved
:::
:::
:::
`;
const html8 = md8.render(unresolvedInput);
assertContains(html8, 'data-thread-id="t-unres"', 'unresolved thread renders');
assertNotContains(html8, 'threads-thread--resolved', 'unresolved thread does not have resolved class');
// ─── Test 9: Edited comment has edited data attribute ───
console.log('\nTest 9: Edited comment');
const md9 = createMd();
const editedInput = `::: threads
::: thread t-ed01
::: comment "alice" "2026-01-01" edited "2026-01-02"
Edited comment
:::
:::
:::
`;
const html9 = md9.render(editedInput);
assertContains(html9, 'data-edited="2026-01-02"', 'edited comment has data-edited attribute');
// ─── Test 10: Comment body is rendered as markdown ───
console.log('\nTest 10: Comment body rendered as markdown');
const md10 = createMd();
const markdownBodyInput = `::: threads
::: thread t-md01
::: comment "alice" "2026-01-01"
This has **bold** and *italic* text
:::
:::
:::
`;
const html10 = md10.render(markdownBodyInput);
assertContains(html10, 'bold', 'bold markdown renders in comment body');
assertContains(html10, 'italic', 'italic markdown renders in comment body');
// ─── Test 11: Comment body escapes raw HTML while preserving markdown ───
console.log('\nTest 11: Comment body escapes raw HTML');
const md11 = createMd();
const rawHtmlInput = `::: threads
::: thread t-html01
::: comment "alice" "2026-01-01"
raw html
This still has **bold** text
:::
:::
:::
`;
const html11 = md11.render(rawHtmlInput);
assert(!html11.includes('raw html'), 'raw HTML is not emitted as an element');
assert(html11.includes('<span') || html11.includes('<span'), 'raw HTML tag marker is escaped');
assertContains(html11, 'bold', 'markdown still renders in escaped comment body');
// ─── Test 12: Empty threads block ───
console.log('\nTest 12: Empty threads block');
const md12 = createMd();
const emptyInput = `::: threads
:::
`;
const html12 = md12.render(emptyInput);
assertContains(html12, 'class="threads-sidebar"', 'empty threads wrapper still renders');
// ─── Test 13: Comment with ID (new serialized format) ───
console.log('\nTest 13: Comment with ID in info string');
const md13 = createMd();
const commentWithIdInput = `::: threads
::: thread t-id01
::: comment c-abc12345 "alice" "2026-03-07"
Comment with persistent ID
:::
:::
:::
`;
const html13 = md13.render(commentWithIdInput);
assertContains(html13, 'data-comment-id="c-abc12345"', 'comment has data-comment-id from new format');
assertContains(html13, 'data-author="alice"', 'comment has correct author from new format');
assertContains(html13, 'data-date="2026-03-07"', 'comment has correct date from new format');
// ─── Test 14: Comment with ID and edited (new serialized format) ───
console.log('\nTest 14: Comment with ID and edited');
const md14 = createMd();
const commentWithIdEditedInput = `::: threads
::: thread t-id02
::: comment c-def67890 "bob" "2026-03-07" edited "2026-03-08"
Edited comment with ID
:::
:::
:::
`;
const html14 = md14.render(commentWithIdEditedInput);
assertContains(html14, 'data-comment-id="c-def67890"', 'edited comment has data-comment-id');
assertContains(html14, 'data-author="bob"', 'edited comment has correct author');
assertContains(html14, 'data-edited="2026-03-08"', 'edited comment has data-edited');
// ─── Done ───
console.log(`\n✓ All ${passed}/${total} containers tests passed.\n`);