import { Assertions, Chain, FocusTools, GeneralSteps, Keyboard, Keys, Logger, Step, UiFinder } from '@ephox/agar'; import { UnitTest } from '@ephox/bedrock-client'; import { Arr } from '@ephox/katamari'; import { Attribute, Focus, SelectorFind, type SugarElement } from '@ephox/sugar'; import * as Behaviour from 'ephox/alloy/api/behaviour/Behaviour'; import { Focusing } from 'ephox/alloy/api/behaviour/Focusing'; import { Keying } from 'ephox/alloy/api/behaviour/Keying'; import { Tabstopping } from 'ephox/alloy/api/behaviour/Tabstopping'; import type { AlloyComponent } from 'ephox/alloy/api/component/ComponentApi'; import * as GuiFactory from 'ephox/alloy/api/component/GuiFactory'; import * as Memento from 'ephox/alloy/api/component/Memento'; import * as GuiSetup from 'ephox/alloy/test/GuiSetup'; UnitTest.asynctest('Browser Test: behaviour.keying.FocusManagersTest', (success, failure) => { const createManager = (prefix: string) => { let active: string | undefined = ''; const set = (_component: AlloyComponent, focusee: SugarElement) => { active = Attribute.get(focusee, 'class'); }; const get = (component: AlloyComponent) => SelectorFind.descendant(component.element, '.' + active); // Test only method const sAssert = (label: string, expected: string) => Step.sync(() => { Assertions.assertEq(prefix + ' ' + label, expected, active); }); return { set, get, sAssert }; }; const makeItems = (tag: string, prefix: string, haveTabstop: boolean, firstNum: number) => Arr.map([ firstNum, firstNum + 1, firstNum + 2 ], (num) => ({ dom: { tag, classes: [ prefix + '-' + num ], innerHtml: prefix + '-' + num }, behaviours: Behaviour.derive( Arr.flatten([ [ Focusing.config({ }) ], haveTabstop ? [ Tabstopping.config({ }) ] : [ ] ]) ) })); const acyclicManager = createManager('acyclic'); const memAcyclic = Memento.record({ dom: { tag: 'div', classes: [ 'acyclic' ] }, components: makeItems('span', 'acyclic', true, 1), behaviours: Behaviour.derive([ Keying.config({ mode: 'acyclic', focusManager: acyclicManager }) ]) }); const cyclicManager = createManager('cyclic'); const memCyclic = Memento.record({ dom: { tag: 'div', classes: [ 'cyclic' ] }, components: makeItems('span', 'cyclic', true, 1), behaviours: Behaviour.derive([ Keying.config({ mode: 'cyclic', focusManager: cyclicManager }) ]) }); const flatgridManager = createManager('flatgrid'); const memFlatgrid = Memento.record({ dom: { tag: 'div', classes: [ 'flatgrid' ] }, components: makeItems('span', 'flatgrid', false, 1), behaviours: Behaviour.derive([ Keying.config({ mode: 'flatgrid', selector: 'span', initSize: { numRows: 2, numColumns: 2 }, focusManager: flatgridManager }) ]) }); const flowManager = createManager('flow'); const memFlow = Memento.record({ dom: { tag: 'div', classes: [ 'flow' ] }, components: makeItems('span', 'flow', false, 1), behaviours: Behaviour.derive([ Keying.config({ mode: 'flow', selector: 'span', focusManager: flowManager }) ]) }); const matrixManager = createManager('matrix'); const memMatrix = Memento.record({ dom: { tag: 'table', classes: [ 'matrix' ] }, components: [ { dom: { tag: 'tr' }, components: makeItems('td', 'matrix', false, 1) }, { dom: { tag: 'tr' }, components: makeItems('td', 'matrix', false, 4) } ], behaviours: Behaviour.derive([ Keying.config({ mode: 'matrix', selectors: { row: 'tr', cell: 'td' }, focusManager: matrixManager }) ]) }); const menuManager = createManager('menu'); const memMenu = Memento.record({ dom: { tag: 'div', classes: [ 'menu' ] }, components: makeItems('span', 'menu', false, 1), behaviours: Behaviour.derive([ Keying.config({ mode: 'menu', selector: 'span', focusManager: menuManager }) ]) }); GuiSetup.setup( (_store, _doc, _body) => GuiFactory.build({ dom: { tag: 'div', classes: [ 'container' ] }, components: [ memAcyclic.asSpec(), memCyclic.asSpec(), memFlatgrid.asSpec(), memFlow.asSpec(), memMatrix.asSpec(), memMenu.asSpec() ], behaviours: Behaviour.derive([ Focusing.config({ }) ]) }), (doc, _body, _gui, component, _store) => { const sFocusStillUnmoved = (label: string) => Logger.t( label, FocusTools.sTryOnSelector('Focus always stays on outer container', doc, '.container') ); const sFocusIn = (component: AlloyComponent) => Step.sync(() => { Keying.focusIn(component); }); const sKeyInside = (comp: AlloyComponent, selector: string, key: number, modifiers: { }) => Chain.asStep(comp.element, [ UiFinder.cFindIn(selector), Chain.op((inside) => { Keyboard.keydown(key, modifiers, inside); }) ]); const acyclic = memAcyclic.get(component); const cyclic = memCyclic.get(component); const flatgrid = memFlatgrid.get(component); const flow = memFlow.get(component); const matrix = memMatrix.get(component); const menu = memMenu.get(component); const sTestKeying = (label: string, comp: AlloyComponent, manager: ReturnType, keyName: keyof typeof Keys) => Logger.t( label + ' Keying', GeneralSteps.sequence([ sFocusIn(comp), manager.sAssert('Focus in ' + label + '-1', label + '-1'), sFocusStillUnmoved(label + '.focusIn'), // Pressing tab will work based on the get from focusManager, so it should move to acyclic-2 sKeyInside(comp, '.' + label + '-1', Keys[keyName](), { }), manager.sAssert('Focus in ' + label + '-2', label + '-2'), sFocusStillUnmoved(label + '.' + keyName) ]) ); return [ Logger.t( 'Initial focus on container', Step.sync(() => { Focus.focus(component.element); }) ), sTestKeying( 'acyclic', acyclic, acyclicManager, 'tab' ), sTestKeying( 'cyclic', cyclic, cyclicManager, 'tab' ), sTestKeying( 'flatgrid', flatgrid, flatgridManager, 'right' ), sTestKeying( 'flow', flow, flowManager, 'right' ), sTestKeying( 'matrix', matrix, matrixManager, 'right' ), sTestKeying( 'menu', menu, menuManager, 'down' ) ]; }, success, failure ); });