const assert = require("assert"); Feature("Richtext cases").tag("@regress"); const createConfig = (tag = "Text", granularity = "symbol") => { return ` <${tag} name="text" value="$text" inline="true" ${granularity ? `granularity="${granularity}"` : ""}/> `; }; Scenario("Broken limits", async ({ I, LabelStudio, AtOutliner }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [ { id: "a", from_name: "label", to_name: "text", type: "labels", value: { start: -1, end: 6, labels: ["Label"], text: "Second" }, }, ], }, ], config: createConfig(), data: { text: "First line\nSecond line" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(1); AtOutliner.clickRegion(1); // The potential errors should be caught by `errorsCollector` plugin }); Scenario("The selection in degenerate cases", async ({ I, LabelStudio, AtOutliner, AtRichText }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [{ id: "test", result: [] }], config: createConfig(), data: { text: "\n\nThird line" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(0); I.pressKey("1"); await AtRichText.selectTextByGlobalOffset(0, 2); // The potential errors should be caught by `errorsCollector` plugin }); Scenario("Exactly 1 word", async ({ I, LabelStudio, AtOutliner, AtRichText }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [], }, ], config: createConfig("Text", "word"), data: { text: "Somé wórds\n\nwíth\n\nmultiline" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(0); I.pressKey("1"); AtRichText.dblClickOnWord("Somé"); AtOutliner.see("Somé"); AtOutliner.dontSee("Somé wórds"); // The potential errors should be caught by `errorsCollector` plugin }); Scenario("Trim spaces around the word", async ({ I, LabelStudio, AtOutliner, AtRichText }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [], }, ], config: createConfig("Text", "word"), data: { text: "One two three four five six" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(0); I.pressKey("1"); AtRichText.dblClickOnWord("four"); AtOutliner.see("four"); I.pressKey("1"); await AtRichText.selectTextByGlobalOffset(3, 8); AtOutliner.see("two"); const result = await LabelStudio.serialize(); assert.strictEqual(result[0].value.text, "four"); assert.strictEqual(result[1].value.text, "two"); // The potential errors should be caught by `errorsCollector` plugin }); Scenario("Trim spaces with BRs", async ({ I, LabelStudio, AtOutliner, AtRichText }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [], }, ], config: createConfig("Text", "word"), data: { text: "Three\n\n\nBRs\n\n\ntrim test" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(0); I.pressKey("1"); await AtRichText.selectTextByGlobalOffset(5, 14); AtOutliner.see("BRs"); const result = await LabelStudio.serialize(); assert.strictEqual(result[0].value.text, "BRs"); // The potential errors should be caught by `errorsCollector` plugin }); Scenario("Overlap checks", async ({ I, LabelStudio, AtOutliner, AtRichText }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [], }, ], config: createConfig(), data: { text: "Halfword" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(0); I.pressKey("1"); await AtRichText.selectTextByGlobalOffset(0, 4); AtOutliner.see("Half"); I.pressKey("1"); await AtRichText.selectTextByGlobalOffset(4, 8); I.seeNumberOfElements(AtRichText.locate("span.htx-highlight"), 2); // The potential errors should be caught by `errorsCollector` plugin }); Scenario("Non-standard characters in words", async ({ I, LabelStudio, AtOutliner, AtRichText }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [], }, ], config: createConfig("Text", "word"), data: { text: "Somé wórds" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(0); I.pressKey("1"); await AtRichText.selectTextByGlobalOffset(0, 5); AtOutliner.seeRegions(1); AtOutliner.see("Somé"); // The potential errors should be caught by `errorsCollector` plugin }); Scenario("Should not select words from next line", async ({ I, LabelStudio, AtOutliner, AtRichText }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [], }, ], config: createConfig("Text", "word"), data: { text: "Оne\nline" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(0); I.pressKey("1"); AtRichText.setSelection(AtRichText.locateText(), 0, AtRichText.locateRoot(), 2); AtOutliner.seeRegions(1); AtOutliner.see("Оne"); AtOutliner.dontSee("line"); // The potential errors should be caught by `errorsCollector` plugin }); Scenario("Trying to select alt attr", async ({ I, LabelStudio, AtOutliner, AtRichText }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [], }, ], config: createConfig("HyperText", "word"), data: { text: "The bad image we got here" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(0); I.pressKey("1"); AtRichText.dblClickOnElement('img[alt="image"]'); AtOutliner.seeRegions(0); // The potential errors should be caught by `errorsCollector` plugin }); Scenario("Neighboring nested regions misplacement", async ({ I, LabelStudio, AtOutliner, AtRichText }) => { I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [ { id: "Catfish", from_name: "label", to_name: "html", type: "labels", value: { start: "/div[1]/text()[1]", startOffset: 0, end: "/div[1]/text()[1]", endOffset: 7, labels: ["Fish"], }, }, { id: "Cat", from_name: "label", to_name: "html", type: "labels", value: { start: "/div[1]/text()[1]", startOffset: 0, end: "/div[1]/text()[1]", endOffset: 3, labels: ["Cat"], }, }, { id: "Fish", from_name: "label", to_name: "html", type: "labels", value: { start: "/div[1]/text()[1]", startOffset: 3, end: "/div[1]/text()[1]", endOffset: 7, labels: ["Fish"], }, }, ], }, ], config: ` `, data: { text: "
Catfish
" }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(3); within({ frame: ".lsf-richtext__iframe" }, () => { I.seeElement(locate(".htx-highlight + .htx-highlight").withText("fish")); }); I.say("Delete last region"); AtOutliner.clickRegion(3); I.pressKey("Backspace"); I.say("Create this region again manually"); I.pressKey("2"); AtRichText.selectTextByGlobalOffset(3, 7); AtOutliner.seeRegions(3); within({ frame: ".lsf-richtext__iframe" }, () => { I.seeElement(locate(".htx-highlight + .htx-highlight").withText("fish")); }); }); { const startBeforeEndParams = new DataTable(["tag", "content", "range"]); startBeforeEndParams.add([ "Text", "Beginning Region Ending", { start: 16, end: 10, }, ]); startBeforeEndParams.add([ "Hypertext", "
Beginning Region Ending
", { start: "/div[1]/text()[1]", startOffset: 16, end: "/div[1]/text()[1]", endOffset: 10, }, ]); startBeforeEndParams.add([ "Hypertext", "
Beginning from to Ending
", { start: "/div[1]/span[3]/text()[1]", startOffset: 2, end: "/div[1]/span[2]/text()[1]", endOffset: 0, }, ]); Data(startBeforeEndParams).Scenario("Start before end problem", async ({ I, LabelStudio, AtOutliner, current }) => { const { tag, content, range } = current; I.amOnPage("/"); LabelStudio.init({ annotations: [ { id: "test", result: [ { id: "Region", from_name: "label", to_name: "text", type: "labels", value: { ...range, labels: ["Region"], }, }, ], }, ], config: ` <${tag} name="text" value="$text" /> `, data: { text: content }, }); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(1); switch (tag.toLowerCase()) { case "text": { I.seeElement(locate(".htx-highlight")); const text = await I.grabHTMLFrom(locate(".htx-highlight")); assert.strictEqual(text, "", "Region should be collapsed and the text should be empty"); break; } case "hypertext": { within({ frame: ".lsf-richtext__iframe" }, async () => { const text = await I.grabHTMLFrom(locate(".htx-highlight")); assert.strictEqual(text, "", "Region should be collapsed and the text should be empty"); }); break; } } }); }