import { Assert, UnitTest } from '@ephox/bedrock-client'; import { Fun, Unicode } from '@ephox/katamari'; import { KAssert } from '@ephox/katamari-assertions'; import { Spot } from '@ephox/phoenix'; import { Pattern } from '@ephox/polaris'; import { Compare, Html, Insert, InsertAll, SugarElement } from '@ephox/sugar'; import * as DomTextSearch from 'ephox/robin/api/dom/DomTextSearch'; import type { TextSeekerOutcome, TextSeekerPhaseConstructor } from 'ephox/robin/textdata/TextSeeker'; UnitTest.test('DomTextSearchTest', () => { const wordbreaker = () => { return new RegExp(Pattern.wordbreak(), 'i'); }; const wordfinder = () => { return new RegExp(Pattern.wordchar(), 'i'); }; const stopAtGap = (phase: TextSeekerPhaseConstructor, element: E, text: string, index: number) => { return phase.finish(Spot.point(element, index)); }; const checkInfo = (result: TextSeekerOutcome, expectedElement: SugarElement, expectedOffset: number) => { result.fold(() => { Assert.fail('Unexpected abort'); }, () => { Assert.fail('Unexpected edge'); }, (info) => { const isSame = Compare.eq(info.element, expectedElement); Assert.eq('eq', true, isSame); Assert.eq('eq', info.offset, expectedOffset); }); }; const checkEdge = (result: TextSeekerOutcome, expectedElement: SugarElement) => { result.fold(() => { Assert.fail('Unexpected abort'); }, (edge) => { const isSame = Compare.eq(edge, expectedElement); Assert.eq('eq', true, isSame); }, () => { Assert.fail('Unexpected info'); }); }; const checkAbort = (result: TextSeekerOutcome) => { result.fold(Fun.noop, () => { Assert.fail('Unexpected edge'); }, () => { Assert.fail('Unexpected info found'); }); }; // const outcome = Adt.generate([ // { aborted: [] }, // { edge: [ 'element' ] }, // { success: [ 'info' ] } // ]); let element = SugarElement.fromTag('div'); const text = SugarElement.fromText('@maurizio@ '); Insert.append(element, text); Assert.eq('eq', 1, element.dom.childNodes.length); // Range offsets [0, 1) Assert.eq('eq', 11, text.dom.length); // Range offsets [0, 11) const elemResult = DomTextSearch.expandRight(element, 0, { regex: wordbreaker, attempt: stopAtGap }); checkAbort(elemResult); const elemResultB = DomTextSearch.expandRight(element, 8, { regex: wordbreaker, attempt: stopAtGap }); checkAbort(elemResultB); const textResult1 = DomTextSearch.expandRight(text, 0, { regex: wordbreaker, attempt: stopAtGap }); checkInfo(textResult1, text, 0); const textResult2 = DomTextSearch.expandRight(text, 1, { regex: wordbreaker, attempt: stopAtGap }); checkInfo(textResult2, text, 9); const textResult = DomTextSearch.expandRight(text, 8, { regex: wordbreaker, attempt: stopAtGap }); checkInfo(textResult, text, 9); const textResult3 = DomTextSearch.expandRight(text, 9, { regex: wordbreaker, attempt: stopAtGap }); checkInfo(textResult3, text, 9); const textB = SugarElement.fromText('@one '); const textBResult = DomTextSearch.expandRight(textB, 0, { regex: wordbreaker, attempt: stopAtGap }); checkInfo(textBResult, textB, 0); const textBResult1 = DomTextSearch.expandRight(textB, 1, { regex: wordbreaker, attempt: stopAtGap }); checkInfo(textBResult1, textB, 4); checkAbort(DomTextSearch.expandLeft(element, 0, { regex: wordbreaker, attempt: stopAtGap })); checkAbort(DomTextSearch.expandLeft(element, 1, { regex: wordbreaker, attempt: stopAtGap })); checkAbort(DomTextSearch.expandLeft(element, 2, { regex: wordbreaker, attempt: stopAtGap })); checkEdge(DomTextSearch.expandLeft(text, 0, { regex: wordbreaker, attempt: stopAtGap }), text); checkInfo(DomTextSearch.expandLeft(text, 1, { regex: wordbreaker, attempt: stopAtGap }), text, 0); // before the first '@' checkInfo(DomTextSearch.expandLeft(text, 3, { regex: wordbreaker, attempt: stopAtGap }), text, 0); // before the first '@' checkInfo(DomTextSearch.expandLeft(text, '@maurizio@'.length, { regex: wordbreaker, attempt: stopAtGap }), text, '@maurizio@'.length - 1); // before the last '@' checkInfo(DomTextSearch.expandLeft(text, '@maurizio@ '.length, { regex: wordbreaker, attempt: stopAtGap }), text, '@maurizio@ '.length - 1); // before the ' ' // // tests left and right looking for words or spaces // const textR = SugarElement.fromText(' words'); // Pos: 0 23 8 Assert.eq('eq', 8, textR.dom.length); checkInfo(DomTextSearch.expandRight(textR, 0, { regex: wordfinder, attempt: stopAtGap }), textR, 3); // 3 is the location after the last space, starting from the left checkInfo(DomTextSearch.expandLeft(textR, 8, { regex: wordbreaker, attempt: stopAtGap }), textR, 2); // 2 is the location after the last character, starting from the right const textL = SugarElement.fromText('words '); // Pos: 0 45 8 Assert.eq('eq', 8, textL.dom.length); checkInfo(DomTextSearch.expandRight(textL, 0, { regex: wordbreaker, attempt: stopAtGap }), textL, 5); // 5 is the location after the last character, starting from the left checkInfo(DomTextSearch.expandLeft(textL, 8, { regex: wordfinder, attempt: stopAtGap }), textL, 4); // 4 is the location after the last space, starting from the right // // tests moving right and left by words // element = SugarElement.fromTag('div'); const span2 = SugarElement.fromTag('span'); // Pos: 0123456789 const w1 = SugarElement.fromText(' wordy '); const w2 = SugarElement.fromText(' words '); const w3 = SugarElement.fromText(' wordd '); Insert.append(span2, w2); InsertAll.append(element, [ w1, span2, w3 ]); Assert.eq('eq', 3, element.dom.childNodes.length); // Range offsets [0, 3) Assert.eq('eq', 1, span2.dom.childNodes.length); // Range offsets [0, 1) Assert.eq('eq', 9, w1.dom.length); // Range offsets [0, 7) Assert.eq('eq', '
wordy words wordd
', Html.getOuter(element)); const r0 = DomTextSearch.expandRight(w1, 0, { regex: wordfinder, attempt: stopAtGap }); const r1 = DomTextSearch.expandRight(w1, 3, { regex: wordbreaker, attempt: stopAtGap }); checkInfo(r0, w1, 2); checkInfo(r1, w1, 7); const r2 = DomTextSearch.expandRight(w1, 7, { regex: wordfinder, attempt: stopAtGap }); const r3 = DomTextSearch.expandRight(w2, 2, { regex: wordbreaker, attempt: stopAtGap }); checkInfo(r2, w2, 2); checkInfo(r3, w2, 7); const r4 = DomTextSearch.expandRight(w2, 7, { regex: wordfinder, attempt: stopAtGap }); const r5 = DomTextSearch.expandRight(w3, 2, { regex: wordbreaker, attempt: stopAtGap }); checkInfo(r4, w3, 2); checkInfo(r5, w3, 7); const r6 = DomTextSearch.expandRight(w3, 7, { regex: wordfinder, attempt: stopAtGap }); const r7 = DomTextSearch.expandRight(w3, 9, { regex: wordbreaker, attempt: stopAtGap }); checkEdge(r6, w3); // hit the edge without finding a word so return 'edge' element not 'success' point checkEdge(r7, w3); // expandLeft, starting from the RHS: // '
wordy words wordd
' // ^ const l1 = DomTextSearch.expandLeft(w3, 9, { regex: wordfinder, attempt: stopAtGap }); // '
wordy words wordd
' // ^ checkInfo(l1, w3, 6); const l2 = DomTextSearch.expandLeft(w3, 6, { regex: wordbreaker, attempt: stopAtGap }); // '
wordy words wordd
' // ^ checkInfo(l2, w3, 1); const l3 = DomTextSearch.expandLeft(w3, 1, { regex: wordfinder, attempt: stopAtGap }); // '
wordy words wordd
' // ^ checkInfo(l3, w2, 6); const l4 = DomTextSearch.expandLeft(w2, 6, { regex: wordbreaker, attempt: stopAtGap }); // '
wordy words wordd
' // ^ checkInfo(l4, w2, 1); const l5 = DomTextSearch.expandLeft(w2, 1, { regex: wordfinder, attempt: stopAtGap }); // '
wordy words wordd
' // ^ checkInfo(l5, w1, 6); const l6 = DomTextSearch.expandLeft(w1, 6, { regex: wordbreaker, attempt: stopAtGap }); // '
wordy words wordd
' // ^ checkInfo(l6, w1, 1); const l7 = DomTextSearch.expandLeft(w1, 1, { regex: wordfinder, attempt: stopAtGap }); // '
wordy words wordd
' // ^ checkEdge(l7, w1); // hit the edge looking for a word // // scanRight returns Optional({element, offset}) // (() => { const container = SugarElement.fromTag('div'); const alphaText = SugarElement.fromText('alpha'); const betaSpan = SugarElement.fromTag('span'); const betaText1 = SugarElement.fromText('be'); const betaText2 = SugarElement.fromText('ta'); const gammaText = SugarElement.fromText(''); const deltaText = SugarElement.fromText(Unicode.zeroWidth); const epsilonText = SugarElement.fromText('epsilon'); InsertAll.append(container, [ alphaText, betaSpan, gammaText, deltaText, epsilonText ]); InsertAll.append(betaSpan, [ betaText1, betaText2 ]); const checkNoneScan = (label: string, start: SugarElement, offset: number) => { KAssert.eqNone('There should be no scanning (' + label + ')', DomTextSearch.scanRight(start, offset)); }; const checkScan = (label: string, expected: { element: SugarElement; offset: number }, start: SugarElement, offset: number) => { const actual = DomTextSearch.scanRight(start, offset).getOrDie('Could not find scan result for: ' + label); Assert.eq('eq', expected.offset, actual.offset); Assert.eq('Element did not match scan: (' + label + ')', true, Compare.eq(expected.element, actual.element)); }; checkNoneScan('Alpha:exceed', alphaText, 'alphabeta\uFEFFepisilon!'.length); checkScan('Alpha:eof', { element: epsilonText, offset: 'epsilon'.length }, alphaText, 'alphabeta\uFEFFepsilon'.length); checkScan('Alpha:2', { element: alphaText, offset: 2 }, alphaText, 2); checkScan('Alpha:into beta:2', { element: betaText1, offset: 'be'.length }, alphaText, 'alphabe'.length); checkScan('Alpha:into beta:3', { element: betaText2, offset: 't'.length }, alphaText, 'alphabet'.length); checkScan('Beta:0', { element: betaText1, offset: 0 }, betaText1, ''.length); })(); });