import { Assert, UnitTest } from '@ephox/bedrock-client'; import { PlatformDetection } from '@ephox/sand'; import * as Compare from 'ephox/sugar/api/dom/Compare'; import * as Hierarchy from 'ephox/sugar/api/dom/Hierarchy'; import * as Insert from 'ephox/sugar/api/dom/Insert'; import * as Remove from 'ephox/sugar/api/dom/Remove'; import * as SugarBody from 'ephox/sugar/api/node/SugarBody'; import { SugarElement } from 'ephox/sugar/api/node/SugarElement'; import * as Attribute from 'ephox/sugar/api/properties/Attribute'; import * as Class from 'ephox/sugar/api/properties/Class'; import * as Html from 'ephox/sugar/api/properties/Html'; import { SimSelection } from 'ephox/sugar/api/selection/SimSelection'; import { Situ } from 'ephox/sugar/api/selection/Situ'; import * as WindowSelection from 'ephox/sugar/api/selection/WindowSelection'; interface VariantRange { start: number[]; soffset: number; finish: number[]; foffset: number; } interface Variants { fallback: VariantRange; [key: string]: VariantRange; } UnitTest.test('WindowSelectionTest', () => { const container = SugarElement.fromTag('div'); Class.add(container, 'window-selection-test'); Attribute.set(container, 'contenteditable', 'true'); const body = SugarBody.body(); Insert.append(body, container); Html.set(container, '

This world is not what I

wanted


And even more

'); const find = (path: number[]) => Hierarchy.follow(container, path).getOrDie('invalid path'); const detection = PlatformDetection.detect(); const detector = (variants: Variants): VariantRange => { if (detection.browser.isFirefox() && variants.firefox !== undefined) { return variants.firefox; } else if (detection.browser.isSafari() && variants.safari !== undefined) { return variants.safari; } else if (detection.browser.isChromium() && variants.chromium !== undefined) { return variants.chromium; } else { return variants.fallback; } }; const checkSelection = (label: string, variants: Variants, start: Situ, finish: Situ) => { const expected = detector(variants); WindowSelection.setRelative(window, start, finish); const actual = WindowSelection.getExact(window).getOrDie('No selection after selection'); const expStart = find(expected.start); const expFinish = find(expected.finish); Assert.eq('Start element different', true, Compare.eq(expStart, actual.start)); Assert.eq('Finish element different', true, Compare.eq(expFinish, actual.finish)); Assert.eq('', expected.soffset, actual.soffset); Assert.eq('', expected.foffset, actual.foffset); }; const checkUniCodeSelection = (content: string) => { Remove.empty(container); Html.set(container, content); return checkSelection; }; const checkStringAt = (label: string, expectedStr: string, start: Situ, finish: Situ) => { // dont need to set a selection range, just extract the Situ.on() element/offset pair const actual = WindowSelection.getAsString(window, SimSelection.relative(start, finish)); Assert.eq('Actual was not expected [' + expectedStr + '|' + actual + ']', expectedStr, actual); }; checkSelection( 'LTR selection (o)', { // '

This w[o]rld is not what I

wanted


And even more

'; fallback: { start: [ 0, 1, 0 ], soffset: 'w'.length, finish: [ 0, 1, 0 ], foffset: 'wo'.length } }, Situ.on(find( [ 0, 1, 0 ]), 'w'.length), Situ.on(find( [ 0, 1, 0 ]), 'wo'.length) ); checkSelection( 'RTL selection: (o)', { // '

This w]o[rld is not what I

wanted


And even more

'; fallback: { start: [ 0, 1, 0 ], soffset: 'wo'.length, finish: [ 0, 1, 0 ], foffset: 'w'.length } }, Situ.on(find( [ 0, 1, 0 ]), 'wo'.length), Situ.on(find( [ 0, 1, 0 ]), 'w'.length) ); checkSelection( 'RTL selection (orld)', { // '

This w]orld[ is not what I

wanted


And even more

'; firefox: { start: [ 0 ], soffset: 2, finish: [ 0, 1, 0 ], foffset: 'w'.length }, chromium: { start: [ 0 ], soffset: 2, finish: [ 0, 1, 0 ], foffset: 'w'.length }, safari: { start: [ 0 ], soffset: 2, finish: [ 0, 1, 0 ], foffset: 'w'.length }, fallback: { start: [ 0, 1, 0 ], soffset: 'world'.length, finish: [ 0, 1, 0 ], foffset: 'w'.length } }, Situ.before(find( [ 0, 2 ])), Situ.on(find( [ 0, 1, 0 ]), 'w'.length) ); checkSelection( 'LTR selection (This world is not what I wanted)', { // '

[This world is not what I

wanted]


And even more

'; fallback: { start: [ 0 ], soffset: 0, finish: [ 0 ], foffset: 7 } }, Situ.on(find( [ 0 ]), 0), Situ.on(find( [ 0 ]), 7) ); checkSelection( 'RTL SimSelection (This world is not what I wanted)', { // '

]This world is not what I

wanted[


And even more

'; fallback: { start: [ 0 ], soffset: 7, finish: [ 0 ], foffset: 0 }, chromium: { start: [ 0 ], soffset: 7, finish: [ 0 ], foffset: 0 } }, Situ.on(find( [ 0 ]), 7), Situ.on(find( [ 0 ]), 0) ); checkSelection( 'LTR selection (t I)', { fallback: { start: [ 0, 3, 1 ], soffset: 1, finish: [ 0 ], foffset: 6 } }, Situ.after(find( [ 0, 3, 1, 0 ]) ), Situ.before(find( [ 0, 6 ]) ) ); checkSelection( 'RTL SimSelection (t I)', { // '

This world is not wha]t I

[wanted


And even more

'; fallback: { finish: [ 0, 3, 2 ], foffset: ''.length, start: [ 0 ], soffset: 6 }, firefox: { finish: [ 0, 3, 1 ], foffset: 1, start: [ 0 ], soffset: 6 }, chromium: { finish: [ 0, 3, 1 ], foffset: 1, start: [ 0 ], soffset: 6 }, safari: { finish: [ 0, 3, 1 ], foffset: 1, start: [ 0 ], soffset: 6 }, spartan: { finish: [ 0, 3, 1 ], foffset: 1, start: [ 0 ], soffset: 6 } }, Situ.before(find( [ 0, 6 ]) ), Situ.after(find( [ 0, 3, 1, 0 ]) ) ); checkStringAt( 'LTR stringAt (This world is not what I)', // checkSelection above has error in what it thinks 0/7 is: // expects: '

[This world is not what I

wanted]


And even more

'; // but actual: '

[This world is not what I
]
wanted


And even more

'; 'This world is not what I', Situ.on(find( [ 0 ]), 0), Situ.on(find( [ 0 ]), 7) ); checkStringAt( 'RTL SimSelection (This world is not what I)', 'This world is not what I', Situ.on(find( [ 0 ]), 7), Situ.on(find( [ 0 ]), 0) ); // Test that proves safari will always normalise a selection to the end leaf // when we set the selection to the span, then get selection, all browsers will return the span // safari will return the textnode inside the span if one exists. checkUniCodeSelection('\uFEFF')( 'TBIO-3883: Unicode position', { fallback: { start: [ 0 ], soffset: 0, finish: [ 0 ], foffset: 0 } }, Situ.on(find( [ 0 ]), 0 ), Situ.on(find( [ 0 ]), 0 ) ); checkUniCodeSelection('^')( 'TBIO-3883: Any Character', { fallback: { start: [ 0 ], soffset: 0, finish: [ 0 ], foffset: 0 } }, Situ.on(find( [ 0 ]), 0 ), Situ.on(find( [ 0 ]), 0 ) ); Remove.remove(container); });