import { Assertions, Log, Pipeline, Step } from '@ephox/agar'; import { UnitTest } from '@ephox/bedrock-client'; import { Gene, TestUniverse, TextGene } from '@ephox/boss'; import { Arr, Fun } from '@ephox/katamari'; import { ZonePosition } from 'ephox/robin/api/general/ZonePosition'; import type { ZoneViewports } from 'ephox/robin/api/general/ZoneViewports'; import { WordDecision } from 'ephox/robin/words/WordDecision'; import type { ZoneDetails } from 'ephox/robin/zone/LanguageZones'; import * as ZoneWalker from 'ephox/robin/zone/ZoneWalker'; UnitTest.asyncTest('atomic.robin.zone.InViewportTest', (success, failure) => { const doc = TestUniverse(Gene('root', 'root', [ Gene('a/p1', 'p', [ TextGene('a/text1.1', 'Some text to walk'), TextGene('a/text1.2', 'Other text') ]), Gene('b/p2', 'p', [ TextGene('b/text2.2', 'Text to walk'), Gene('b/p2.2', 'p', [ TextGene('b/text2.2.1', 'Hallo'), TextGene('b/text2.2.2', 'Auf wiedersehen') ], {}, { lang: 'de' }) ]), Gene('b/p3', 'p', [ TextGene('b/text3.1', 'Different block, still within b') ]), Gene('c/p4', 'p', [ TextGene('c/text4.1', 'Last block'), Gene('c/span4.2', 'span', [ TextGene('c/text4.2.1', 'Last one') ]) ]) ])); // Decides whether an element is above, inside, or below the viewport based on comparing the first letters of the IDs const firstLetterViewport = (start: string, end: string): ZoneViewports => ({ assess: (item) => { const letter = item.id.charAt(0); if (item.id === 'root') { return ZonePosition.inView(item); } if (letter < start) { return ZonePosition.aboveView(item); } else if (letter > end) { return ZonePosition.belowView(item); } else { return ZonePosition.inView(item); } } }); const assertZones = (startId: string, endId: string, viewport: ZoneViewports) => { const start = doc.find(doc.get(), startId).getOrDie(); const end = doc.find(doc.get(), endId).getOrDie(); const transform = WordDecision.fromItem; const zones = ZoneWalker.walk(doc, start, end, 'en', transform, viewport); const indexedZones = new Map>(); Arr.each(zones, (zone) => Arr.each(zone.details, (detail) => indexedZones.set(detail.item.id, zone))); let elements: Gene[] = []; // TODO: see if there's an existing function for this so we can lose the cheap / nasty DFS const visit = (el: Gene) => { const children = doc.property().children(el); elements = elements.concat(children); Arr.each(children, visit); }; visit(doc.get()); Arr.each(elements, (el) => { const isText = doc.property().isText(el); const withinViewport = viewport.assess(el).fold(Fun.never, Fun.always, Fun.never); const equalsOrInside = (g1: Gene, g2Id: string): boolean => { if (g1.id === g2Id) { return true; } if (doc.up().predicate(g1, (g2) => g2.id === g2Id).isSome()) { return true; } if (doc.down().predicate(g1, (g2) => g2.id === g2Id).length !== 0) { return true; } return false; }; const afterOrEqualsStart = equalsOrInside(el, startId) || doc.query().comparePosition(el, start) === 2; const beforeOrEqualsEnd = equalsOrInside(el, endId) || doc.query().comparePosition(el, end) === 4; if (isText && withinViewport && afterOrEqualsStart && beforeOrEqualsEnd) { Assertions.assertEq('Ensuring element ' + el.name + ' is reached by the walker', true, indexedZones.has(el.id)); } else { Assertions.assertEq('Ensuring element ' + el.name + ' is not reached by the walker', false, indexedZones.has(el.id)); } }); }; const sAssertZones = (startId: string, endId: string, viewportStart: string, viewportEnd: string) => Step.sync(() => assertZones(startId, endId, firstLetterViewport(viewportStart, viewportEnd)) ); Pipeline.async({}, [ Log.step('TINY-6412', 'Can it walk the whole document', sAssertZones('a/p1', 'c/p4', 'a', 'c') ), Log.step('TINY-6412', 'Can it walk all nodes in a viewport', sAssertZones('a/p1', 'c/p4', 'b', 'b') ), Log.stepsAsStep('TINY-6412', 'Will it skip if the viewport and the start/end points have no overlap', [ sAssertZones('b/p3', 'c/p4', 'a', 'a'), sAssertZones('a/p1', 'b/p2', 'c', 'c') ]), Log.stepsAsStep('TINY-6412', 'Can it handle partial overlaps between the viewport and the start/end points', [ sAssertZones('a/p1', 'b/p2', 'b', 'b'), sAssertZones('b/p3', 'c/p4', 'b', 'b'), sAssertZones('b/p2', 'b/p3', 'a', 'b'), sAssertZones('b/p2', 'b/p3', 'b', 'c') ]) ], success, failure); });