discourse/plugins/checklist/test/javascripts/integration/rich-editor-extension-test.js
Renato Atilio 97bf164592
FEATURE: consolidate composer toolbar list and add check list (#37336)
This PR merges the bulleted and numbered list buttons into a single
Lists dropdown menu in the composer toolbar and introduces a new
checklist option to the dropdown.

 * Combines individual list buttons into a single "Lists" dropdown
* Adds a new checklist option within the list menu (integrated via the
checklist plugin)
 * ProseMirror/Rich Editor:
* Added logic for checklist continuation on Enter and exit on double
Enter
* Implemented Backspace handling to manage checkbox removal and line
joining
* Added cursor positioning constraints to prevent selection before the
checkbox
     * Added handler to toggle the check when clicked

Updated `addComposerToolbarPopupMenuOption` to allow plugins to register
custom options specifically within a popup menu through its identifier

<img width="467" height="190" alt="image"
src="https://github.com/user-attachments/assets/91205405-3d6f-4e04-85f5-800e6b9d95b4"
/>
2026-02-24 10:08:34 -03:00

80 lines
3.2 KiB
JavaScript

import { module, test } from "qunit";
import {
registerRichEditorExtension,
resetRichEditorExtensions,
} from "discourse/lib/composer/rich-editor-extensions";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { testMarkdown } from "discourse/tests/helpers/rich-editor-helper";
import richEditorExtension from "discourse/plugins/checklist/lib/rich-editor-extension";
module(
"Integration | Component | prosemirror-editor - checklist plugin extension",
function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
this.siteSettings.rich_editor = true;
resetRichEditorExtensions().then(() => {
registerRichEditorExtension(richEditorExtension);
});
});
const checked =
'<span class="chcklst-box checked fa fa-square-check-o" contenteditable="false" draggable="true"></span>';
const unchecked =
'<span class="chcklst-box fa fa-square-o" contenteditable="false" draggable="true"></span>';
Object.entries({
"renders unchecked checkbox correctly": [
"[ ] todo item",
`<p>${unchecked} todo item</p>`,
"[ ] todo item",
],
"renders checked checkbox correctly": [
"[x] completed item",
`<p>${checked} completed item</p>`,
"[x] completed item",
],
"handles multiple checkboxes in a single paragraph": [
"[] first task [x] second task",
`<p>${unchecked} first task ${checked} second task</p>`,
"[ ] first task [x] second task",
],
"handles checkboxes in lists": [
"* [ ] unchecked list item\n* [x] checked list item",
`<ul data-tight="true"><li class="has-checkbox"><p>${unchecked} unchecked list item</p></li><li class="has-checkbox"><p>${checked} checked list item</p></li></ul>`,
"* [ ] unchecked list item\n* [x] checked list item",
],
"handles checkboxes with formatting": [
"[ ] *italics* and [x] **bold**",
`<p>${unchecked} <em>italics</em> and ${checked} <strong>bold</strong></p>`,
"[ ] *italics* and [x] **bold**",
],
"does not render escaped checkbox": [
"\\[x] not a checkbox",
"<p>[x] not a checkbox</p>",
"\\[x\\] not a checkbox",
],
"handles escaped checkbox followed by real checkbox": [
"\\[x] escaped [x] real",
`<p>[x] escaped ${checked} real</p>`,
"\\[x\\] escaped [x] real",
],
"preserves checkbox state in ordered lists": [
"1. [ ] unchecked\n2. [x] checked",
`<ol data-tight="true"><li><p>${unchecked} unchecked</p></li><li><p>${checked} checked</p></li></ol>`,
"1. [ ] unchecked\n2. [x] checked",
],
"handles nested list with checkboxes": [
"* [ ] parent\n * [x] child",
`<ul data-tight="true"><li class="has-checkbox"><p>${unchecked} parent</p><ul data-tight="true"><li class="has-checkbox"><p>${checked} child</p></li></ul></li></ul>`,
"* [ ] parent\n * [x] child",
],
}).forEach(([name, [markdown, html, expectedMarkdown]]) => {
test(name, async function (assert) {
await testMarkdown(assert, markdown, html, expectedMarkdown);
});
});
}
);