import { describe, test } from "@odoo/hoot"; import { testEditor } from "./_helpers/editor"; import { TAB_WIDTH, getCharWidth, getIndentWidth, oeTab, testTabulation } from "./_helpers/tabs"; import { deleteBackward, deleteForward, insertText, keydownShiftTab, keydownTab, } from "./_helpers/user_actions"; describe("insert tabulation", () => { test("should insert a tab character", async () => { const expectedTabWidth = TAB_WIDTH - getCharWidth("p", "a"); await testTabulation({ contentBefore: `
a[]b
`, stepFunction: keydownTab, contentAfterEdit: `a${oeTab(expectedTabWidth, false)}[]b
`, contentAfter: `a${oeTab(expectedTabWidth)}[]b
`, }); }); test("should keep selection and insert a tab character at the beginning of the paragraph", async () => { await testTabulation({ contentBefore: `a[xxx]b
`, stepFunction: keydownTab, contentAfterEdit: `${oeTab(TAB_WIDTH, false)}a[xxx]b
`, contentAfter: `${oeTab(TAB_WIDTH)}a[xxx]b
`, }); }); test("should insert two tab characters", async () => { const expectedTabWidth = TAB_WIDTH - getCharWidth("p", "a"); await testTabulation({ contentBefore: `a[]b
`, stepFunction: async (editor) => { await keydownTab(editor); await keydownTab(editor); }, contentAfterEdit: `a${oeTab(expectedTabWidth, false)}${oeTab( TAB_WIDTH, false )}[]b
`, contentAfter: `a${oeTab(expectedTabWidth)}${oeTab(TAB_WIDTH)}[]b
`, }); }); test("should insert two tab characters with one char between them", async () => { const expectedTabWidth = TAB_WIDTH - getCharWidth("p", "a"); await testTabulation({ contentBefore: `a[]b
`, stepFunction: async (editor) => { await keydownTab(editor); await insertText(editor, "a"); await keydownTab(editor); }, contentAfterEdit: `a${oeTab(expectedTabWidth, false)}a${oeTab( expectedTabWidth, false )}[]b
`, contentAfter: `a${oeTab(expectedTabWidth)}a${oeTab(expectedTabWidth)}[]b
`, }); }); test("should insert tab characters at the beginning of two separate paragraphs", async () => { await testTabulation({ contentBefore: `a[b
` + `c]d
`, stepFunction: keydownTab, contentAfterEdit: `${oeTab(TAB_WIDTH, false)}a[b
` + `${oeTab(TAB_WIDTH, false)}c]d
`, contentAfter: `${oeTab(TAB_WIDTH)}a[b
` + `${oeTab(TAB_WIDTH)}c]d
`, }); }); test("should insert tab characters at the beginning of two separate indented paragraphs", async () => { await testTabulation({ contentBefore: `${oeTab()}a[b
` + `${oeTab()}c]d
`, // @todo: add contentBeforeEdit in some test cases to test the addition // of the contenteditable="false" attribute by setup. stepFunction: keydownTab, contentAfterEdit: `${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[b
` + `${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}c]d
`, contentAfter: `${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[b
` + `${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}c]d
`, }); }); test("should insert tab characters at the beginning of two separate paragraphs (one indented, the other not)", async () => { await testTabulation({ contentBefore: `${oeTab()}a[b
` + `c]d
`, stepFunction: keydownTab, contentAfterEdit: `${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[b
` + `${oeTab(TAB_WIDTH, false)}c]d
`, contentAfter: `${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[b
` + `${oeTab(TAB_WIDTH)}c]d
`, }); await testTabulation({ contentBefore: `a[b
` + `${oeTab()}c]d
`, stepFunction: keydownTab, contentAfterEdit: `${oeTab(TAB_WIDTH, false)}a[b
` + `${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}c]d
`, contentAfter: `${oeTab(TAB_WIDTH)}a[b
` + `${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}c]d
`, }); }); test("should insert tab characters at the beginning of two separate paragraphs with tabs in them", async () => { const tabAfterA = TAB_WIDTH - getCharWidth("p", "a"); const tabAfterB = TAB_WIDTH - getCharWidth("p", "b"); const tabAfterC = TAB_WIDTH - getCharWidth("p", "c"); const tabAfterD = TAB_WIDTH - getCharWidth("p", "d"); await testTabulation({ contentBefore: `${oeTab()}a[${oeTab()}b${oeTab()}
` + `c${oeTab()}]d${oeTab()}
`, stepFunction: keydownTab, contentAfter: `${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[${oeTab(tabAfterA)}b${oeTab( tabAfterB )}
` + `${oeTab(TAB_WIDTH)}c${oeTab(tabAfterC)}]d${oeTab(tabAfterD)}
`, }); }); test("should insert tab characters at the beginning of three separate blocks", async () => { const tabInBlockquote = TAB_WIDTH - getIndentWidth("blockquote"); await testTabulation({ contentBefore: `xxx
` + `a[b
` + `e]f` + `
xxx
` + `${oeTab(TAB_WIDTH, false)}a[b
` + `${oeTab(tabInBlockquote, false)}e]f` + `
xxx
` + `${oeTab(TAB_WIDTH)}a[b
` + `${oeTab(tabInBlockquote)}e]f` + `
${oeTab()}xxx
` + `${oeTab()}a[b
` + `${oeTab()}e]f` + `
${oeTab(TAB_WIDTH, false)}xxx
` + `${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[b
` + `${oeTab(tabInBlockquote, false)}${oeTab( TAB_WIDTH, false )}e]f` + `
${oeTab(TAB_WIDTH)}xxx
` + `${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[b
` + `${oeTab(tabInBlockquote)}${oeTab(TAB_WIDTH)}e]f` + `
xxx
` + `${oeTab()}${oeTab()}a[b
` + `e]f` + `
xxx
` + `${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}${oeTab( TAB_WIDTH, false )}a[b
` + `${oeTab(tabInBlockquote, false)}e]f` + `
xxx
` + `${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[b
` + `${oeTab(tabInBlockquote)}e]f` + `
xxx
` + `${oeTab()}a[${oeTab()}b${oeTab()}
` + `e${oeTab()}]f` + `
xxx
` + `${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[${oeTab( tabAfterA, false )}b${oeTab(tabAfterB, false)}
` + `${oeTab(tabInBlockquote, false)}e${oeTab( tabAfterEinBlockquote, false )}]f` + `
xxx
` + `${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[${oeTab(tabAfterA)}b${oeTab( tabAfterB )}
` + `${oeTab(tabInBlockquote)}e${oeTab( tabAfterEinBlockquote )}]f` + `
${oeTab()}a[${oeTab()}b${oeTab()}
` + `f${oeTab()}]g`, stepFunction: keydownTab, contentAfterEdit: `
${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[${oeTab(tabAfterA, false)}b${oeTab(tabAfterB,false)}
` + `${oeTab(tabInBlockquote, false)}f${oeTab(tabAfterFinBlockquote, false)}]g`, contentAfter: `
${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[${oeTab(tabAfterA)}b${oeTab(tabAfterB)}
` + `${oeTab(tabInBlockquote)}f${oeTab(tabAfterFinBlockquote)}]g`, }); }); }); describe("delete backward tabulation", () => { test("should remove one tab character", async () => { const tabAfterA = TAB_WIDTH - getCharWidth("p", "a"); await testEditor({ contentBefore: `
a${oeTab(tabAfterA)}[]b
`, stepFunction: async (editor) => { deleteBackward(editor); }, contentAfter: `a[]b
`, }); await testEditor({ contentBefore: `a${oeTab(tabAfterA)}[]${oeTab()}b
`, stepFunction: async (editor) => { deleteBackward(editor); }, contentAfter: `a[]${oeTab(tabAfterA)}b
`, }); }); test("should remove two tab characters", async () => { const tabAfterA = TAB_WIDTH - getCharWidth("p", "a"); await testEditor({ contentBefore: `a${oeTab(tabAfterA)}${oeTab()}[]b
`, stepFunction: async (editor) => { deleteBackward(editor); deleteBackward(editor); }, contentAfter: `a[]b
`, }); await testEditor({ contentBefore: `a${oeTab(tabAfterA)}${oeTab()}[]${oeTab()}b
`, stepFunction: async (editor) => { deleteBackward(editor); deleteBackward(editor); }, contentAfter: `a[]${oeTab(tabAfterA)}b
`, }); }); test("should remove three tab characters", async () => { await testEditor({ contentBefore: `a${oeTab()}${oeTab()}${oeTab()}[]b
`, stepFunction: async (editor) => { deleteBackward(editor); deleteBackward(editor); deleteBackward(editor); }, contentAfter: `a[]b
`, }); }); }); describe("delete forward tabulation", () => { test("should remove one tab character", async () => { const tabAfterA = TAB_WIDTH - getCharWidth("p", "a"); await testTabulation({ contentBefore: `a[]${oeTab(tabAfterA)}b1
`, stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: `a[]b1
`, }); await testTabulation({ contentBefore: `a${oeTab(tabAfterA)}[]${oeTab()}b2
`, stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: `a${oeTab(tabAfterA)}[]b2
`, }); await testTabulation({ contentBefore: `a[]${oeTab(tabAfterA)}${oeTab()}b3
`, stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: `a[]${oeTab(tabAfterA)}b3
`, }); }); test("should remove two tab characters", async () => { const tabAfterA = TAB_WIDTH - getCharWidth("p", "a"); await testEditor({ contentBefore: `a[]${oeTab(tabAfterA)}${oeTab()}b1
`, stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: `a[]b1
`, }); await testEditor({ contentBefore: `a[]${oeTab(tabAfterA)}${oeTab()}${oeTab()}b2
`, stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: `a[]${oeTab(tabAfterA)}b2
`, }); await testEditor({ contentBefore: `a${oeTab(tabAfterA)}[]${oeTab()}${oeTab()}b3
`, stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: `a${oeTab(tabAfterA)}[]b3
`, }); }); test("should remove three tab characters", async () => { await testEditor({ contentBefore: `a[]${oeTab()}${oeTab()}${oeTab()}b
`, stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); deleteForward(editor); }, contentAfter: `a[]b
`, }); }); }); describe("delete mixed tabulation", () => { test("should remove all tab characters", async () => { const tabAfterA = TAB_WIDTH - getCharWidth("p", "a"); await testEditor({ contentBefore: `a${oeTab(tabAfterA)}[]${oeTab()}b1
`, stepFunction: async (editor) => { deleteForward(editor); deleteBackward(editor); }, contentAfter: `a[]b1
`, }); await testEditor({ contentBefore: `a${oeTab(tabAfterA)}[]${oeTab()}b2
`, stepFunction: async (editor) => { deleteBackward(editor); deleteForward(editor); }, contentAfter: `a[]b2
`, }); await testEditor({ contentBefore: `a${oeTab(tabAfterA)}${oeTab()}[]${oeTab()}b3
`, stepFunction: async (editor) => { deleteBackward(editor); deleteForward(editor); deleteBackward(editor); }, contentAfter: `a[]b3
`, }); await testEditor({ contentBefore: `a${oeTab(tabAfterA)}[]${oeTab()}${oeTab()}b4
`, stepFunction: async (editor) => { deleteForward(editor); deleteBackward(editor); deleteForward(editor); }, contentAfter: `a[]b4
`, }); }); }); describe("remove tabulation with shift+tab", () => { test("should not remove a non-leading tab character", async () => { function oeTab(size, contenteditable = true) { return ( `\u0009\u200B` ); } const tabAfterA = TAB_WIDTH - getCharWidth("p", "a"); await testEditor({ contentBefore: `a${oeTab(tabAfterA)}[]b
`, stepFunction: keydownShiftTab, contentAfterEdit: `a${oeTab(tabAfterA, false)}[]b
`, contentAfter: `a${oeTab(tabAfterA)}[]b
`, }); }); test("should remove a tab character", async () => { await testEditor({ contentBefore: `${oeTab()}a[]b
`, stepFunction: keydownShiftTab, contentAfter: `a[]b
`, }); }); test("should keep selection and remove a tab character from the beginning of the paragraph", async () => { await testEditor({ contentBefore: `${oeTab()}a[xxx]b
`, stepFunction: keydownShiftTab, contentAfter: `a[xxx]b
`, }); }); test("should remove two tab characters", async () => { await testEditor({ contentBefore: `${oeTab()}${oeTab()}a[]b
`, stepFunction: async (editor) => { await keydownShiftTab(editor); await keydownShiftTab(editor); }, contentAfter: `a[]b
`, }); }); test("should remove tab characters from the beginning of two separate paragraphs", async () => { await testEditor({ contentBefore: `${oeTab()}a[b
` + `${oeTab()}c]d
`, stepFunction: keydownShiftTab, contentAfter: `a[b
` + `c]d
`, }); }); test("should remove tab characters from the beginning of two separate double-indented paragraphs", async () => { await testTabulation({ contentBefore: `${oeTab()}${oeTab()}a[b
` + `${oeTab()}${oeTab()}c]d
`, stepFunction: keydownShiftTab, contentAfterEdit: `${oeTab(TAB_WIDTH, false)}a[b
` + `${oeTab(TAB_WIDTH, false)}c]d
`, contentAfter: `${oeTab(TAB_WIDTH)}a[b
` + `${oeTab(TAB_WIDTH)}c]d
`, }); }); test("should remove tab characters from the beginning of two separate paragraphs of mixed indentations", async () => { await testTabulation({ contentBefore: `${oeTab()}${oeTab()}a[b
` + `${oeTab()}c]d
`, stepFunction: keydownShiftTab, contentAfterEdit: `${oeTab(TAB_WIDTH, false)}a[b
` + `c]d
`, contentAfter: `${oeTab(TAB_WIDTH)}a[b
` + `c]d
`, }); await testTabulation({ contentBefore: `a[b
` + `${oeTab()}c]d
`, stepFunction: keydownShiftTab, contentAfter: `a[b
` + `c]d
`, }); }); test("should remove tab characters from the beginning of two separate paragraphs with tabs in them", async () => { const tabAfterA = TAB_WIDTH - getCharWidth("p", "a"); const tabAfterB = TAB_WIDTH - getCharWidth("p", "b"); const tabAfterC = TAB_WIDTH - getCharWidth("p", "c"); const tabAfterD = TAB_WIDTH - getCharWidth("p", "d"); await testTabulation({ contentBefore: `${oeTab(TAB_WIDTH)}a[${oeTab(tabAfterA)}b${oeTab(tabAfterB)}
` + `c${oeTab(tabAfterC)}]d${oeTab(tabAfterD)}
`, stepFunction: keydownShiftTab, contentAfter: `a[${oeTab(tabAfterA)}b${oeTab(tabAfterB)}
` + `c${oeTab(tabAfterC)}]d${oeTab(tabAfterD)}
`, }); }); test("should remove tab characters from the beginning of three separate blocks", async () => { await testEditor({ contentBefore: `xxx
` + `${oeTab()}a[b
` + `${oeTab()}e]f` + `
xxx
` + `a[b
` + `e]f` + `
xxx
` + `${oeTab()}${oeTab()}a[b
` + `e]f` + `
xxx
` + `${oeTab(TAB_WIDTH, false)}a[b
` + `e]f` + `
xxx
` + `${oeTab(TAB_WIDTH)}a[b
` + `e]f` + `
xxx
` + `${oeTab()}a[${oeTab()}b${oeTab()}
` + `${oeTab()}e${oeTab()}]f` + `
xxx
` + `a[${oeTab(tabAfterA, false)}b${oeTab(tabAfterB, false)}
` + `e${oeTab(tabAfterEinBlockquote, false)}]f` + `
xxx
` + `a[${oeTab(tabAfterA)}b${oeTab(tabAfterB)}
` + `e${oeTab(tabAfterEinBlockquote)}]f` + `
${oeTab()}${oeTab()}a[${oeTab()}b${oeTab()}
` + `${oeTab()}f${oeTab()}]g`, stepFunction: keydownShiftTab, contentAfterEdit: `
${oeTab(TAB_WIDTH, false)}a[${oeTab(tabAfterA, false)}b${oeTab( tabAfterB, false )}
` + `f${oeTab(tabAfterFinBlockquote, false)}]g`, contentAfter: `
${oeTab(TAB_WIDTH)}a[${oeTab(tabAfterA)}b${oeTab(tabAfterB)}
` + `f${oeTab(tabAfterFinBlockquote)}]g`, }); }); test("should remove a tab character from formatted text", async () => { await testEditor({ contentBefore: `
${oeTab()}a[]b
`, stepFunction: keydownShiftTab, contentAfter: `a[]b
`, }); }); test("should remove tab characters from the beginning of two separate formatted paragraphs", async () => { await testEditor({ contentBefore: `${oeTab()}a[b
` + `${oeTab()}c]d
`, stepFunction: keydownShiftTab, contentAfter: `a[b
` + `c]d
`, }); }); test("should remove a tab character from styled text", async () => { await testEditor({ contentBefore: `${oeTab()}a[]b
`, stepFunction: keydownShiftTab, contentAfter: `a[]b
`, }); }); }); describe("update tab width", () => { test("should update tab width on content change", async () => { const tabAfterA = TAB_WIDTH - getCharWidth("p", "a"); const tabAfterAA = TAB_WIDTH - 2 * getCharWidth("p", "a"); await testEditor({ contentBefore: `a[]${oeTab(tabAfterA)}
`, stepFunction: async (editor) => { await insertText(editor, "a"); }, contentAfter: `aa[]${oeTab(tabAfterAA)}
`, }); }); });