import { ApproxStructure, Assertions, Keyboard, Keys, Mouse, Step, type StructAssert } from '@ephox/agar'; import { UnitTest } from '@ephox/bedrock-client'; import { Objects } from '@ephox/boulder'; import { Arr, Obj } from '@ephox/katamari'; import { Class } from '@ephox/sugar'; import * as AddEventsBehaviour from 'ephox/alloy/api/behaviour/AddEventsBehaviour'; import * as Behaviour from 'ephox/alloy/api/behaviour/Behaviour'; import { Keying } from 'ephox/alloy/api/behaviour/Keying'; import { Representing } from 'ephox/alloy/api/behaviour/Representing'; import * as GuiFactory from 'ephox/alloy/api/component/GuiFactory'; import * as AlloyEvents from 'ephox/alloy/api/events/AlloyEvents'; import { Menu } from 'ephox/alloy/api/ui/Menu'; import { tieredMenu as TieredMenu } from 'ephox/alloy/api/ui/TieredMenu'; import * as MenuEvents from 'ephox/alloy/menu/util/MenuEvents'; import * as TestDropdownMenu from 'ephox/alloy/test/dropdown/TestDropdownMenu'; import * as GuiSetup from 'ephox/alloy/test/GuiSetup'; UnitTest.asynctest('TieredMenuTest', (success, failure) => { const tmenuClass = 'tiered-menu-decoration'; GuiSetup.setup((store, _doc, _body) => GuiFactory.build( TieredMenu.sketch({ uid: 'uid-test-menu-1', dom: { tag: 'div', classes: [ 'test-menu', tmenuClass ] }, components: [ Menu.parts.items({ }) ], markers: TestDropdownMenu.markers(), data: { primary: 'menu-a', menus: Obj.map({ 'menu-a': { value: 'menu-a', items: Arr.map([ { type: 'item', data: { value: 'a-alpha', meta: { text: 'a-Alpha' }}, hasSubmenu: false }, { type: 'item', data: { value: 'a-beta', meta: { text: 'a-Beta' }}, hasSubmenu: true }, { type: 'item', data: { value: 'a-gamma', meta: { text: 'a-Gamma', disabled: true }}, hasSubmenu: true } ], TestDropdownMenu.renderItem) }, 'a-beta': { // menu name should be triggering parent item so TieredMenuSpec path works value: 'menu-b', items: Arr.map([ { type: 'item', data: { value: 'b-alpha', meta: { text: 'b-Alpha' }}, hasSubmenu: false }, ], TestDropdownMenu.renderItem) }, 'a-gamma': { value: 'menu-c', items: Arr.map([ { type: 'item', data: { value: 'c-alpha', meta: { text: 'c-Alpha' }}, hasSubmenu: false }, ], TestDropdownMenu.renderItem) } }, TestDropdownMenu.renderMenu), expansions: { 'a-beta': 'a-beta', 'a-gamma': 'a-gamma', } }, tmenuBehaviours: Behaviour.derive([ AddEventsBehaviour.config('tiered-menu-test', [ AlloyEvents.run(MenuEvents.focus(), store.adder('menu.events.focus')) ]) ]), eventOrder: Objects.wrapAll([ { key: MenuEvents.focus(), value: [ 'alloy.base.behaviour', 'tiered-menu-test' ] } ]), onExecute: store.adderH('onExecute'), onEscape: store.adderH('onEscape'), onOpenMenu: store.adderH('onOpenMenu'), onOpenSubmenu: store.adderH('onOpenSubmenu'), onRepositionMenu: store.adderH('onRepositionMenu'), onHighlightItem: (tmenuComp, menuComp, itemComp) => { // Make assertions about the arguments passed in here. To get the most // useful feedback, we'll add errors to store. if (!Class.has(tmenuComp.element, tmenuClass)) { store.adder('ERROR. First argument to onHighlightItem was not the tmenu')(); } if (!Class.has(menuComp.element, TestDropdownMenu.markers().menu)) { store.adder('ERROR. Second argument to onHighlightItem was not the menu')(); } store.adder( `onHighlightItem: ${Representing.getValue(itemComp).value}` )(); }, onDehighlightItem: (tmenuComp, menuComp, itemComp) => { // Make assertions about the arguments passed in here. To get the most // useful feedback, we'll add errors to store. if (!Class.has(tmenuComp.element, tmenuClass)) { store.adder('ERROR. First argument to onDehighlightItem was not the tmenu')(); } if (!Class.has(menuComp.element, TestDropdownMenu.markers().menu)) { store.adder('ERROR. Second argument to onDehighlightItem was not the menu')(); } store.adder( `onDehighlightItem: ${Representing.getValue(itemComp).value}` )(); }, }) ), (doc, _body, _gui, component, store) => { const structureMenu = (selected: boolean, itemSelections: boolean[], hasPopups: boolean[], isExpandeds: boolean[], disabled: boolean[]) => (s: ApproxStructure.StructApi, str: ApproxStructure.StringApi, arr: ApproxStructure.ArrayApi) => s.element('ol', { classes: [ arr.has('menu'), (selected ? arr.has : arr.not)('selected-menu') ], children: Arr.map(itemSelections, (sel, i) => s.element('li', { classes: [ arr.has('item'), (sel ? arr.has : arr.not)('selected-item') ], attrs: { 'aria-haspopup': str.is(hasPopups[i].toString()), ...hasPopups[i] ? { 'aria-expanded': str.is(isExpandeds[i].toString()) } : { 'aria-expanded': str.none('aria-expanded should not exist') }, 'aria-disabled': str.is(disabled[i].toString()), } })) }); const sAssertMenu = (label: string, structureMenus: Array>) => Assertions.sAssertStructure( label, ApproxStructure.build((s, str, arr) => s.element('div', { classes: [ arr.has('test-menu') ], children: Arr.map(structureMenus, (sm) => sm(s, str, arr)) })), component.element ); return [ Assertions.sAssertStructure( 'Original structure test', ApproxStructure.build((s, str, arr) => s.element('div', { classes: [ arr.has('test-menu') ], children: [ structureMenu(true, [ true, false, false ], [ false, true, true ], [ false, false, false ], [ false, false, true ])(s, str, arr) ] })), component.element ), Step.sync(() => { Keying.focusIn(component); }), store.sAssertEq('Focus is fired as soon as the tiered menu is active', [ 'onOpenMenu', 'onHighlightItem: a-alpha', 'menu.events.focus' ]), store.sClear, Keyboard.sKeydown(doc, Keys.down(), { }), Keyboard.sKeydown(doc, Keys.right(), { }), store.sAssertEq('Check events after navigation', [ 'onDehighlightItem: a-alpha', 'onHighlightItem: a-beta', 'menu.events.focus', 'onOpenSubmenu', 'onHighlightItem: b-alpha', 'menu.events.focus' ]), sAssertMenu( 'Post expansion of submenu with structure test', [ structureMenu(false, [ false, true, false ], [ false, true, true ], [ false, true, false ], [ false, false, true ]), structureMenu(true, [ true ], [ false ], [ false ], [ false ]) ] ), Keyboard.sKeydown(doc, Keys.left(), { }), sAssertMenu( 'Post collapse of submenu with structure test', [ structureMenu(true, [ false, true, false ], [ false, true, true ], [ false, false, false ], [ false, false, true ]) ] ), Keyboard.sKeydown(doc, Keys.enter(), { }), sAssertMenu( 'Post exansion of submenu with structure test', [ structureMenu(false, [ false, true, false ], [ false, true, true ], [ false, true, false ], [ false, false, true ]), structureMenu(true, [ true ], [ false ], [ false ], [ false ]) ] ), Keyboard.sKeyup(doc, Keys.escape(), { }), sAssertMenu( 'Post collapse of submenu with structure test', [ structureMenu(true, [ false, true, false ], [ false, true, true ], [ false, false, false ], [ false, false, true ]) ] ), Mouse.sHoverOn(component.element, 'li:contains("a-Beta")'), sAssertMenu( 'Post hover on item with submenu structure test', [ structureMenu(true, [ false, true, false ], [ false, true, true ], [ false, true, false ], [ false, false, true ]), structureMenu(false, [ false ], [ false ], [ false ], [ false ]) ] ), Keyboard.sKeydown(doc, Keys.right(), { }), sAssertMenu( 'Post right on item with expanded submenu structure test', [ structureMenu(false, [ false, true, false ], [ false, true, true ], [ false, true, false ], [ false, false, true ]), structureMenu(true, [ true ], [ false ], [ false ], [ false ]) ] ), Mouse.sHoverOn(component.element, 'li:contains("a-Gamma")'), sAssertMenu( 'Post hover on disabled item with submenu structure test', [ structureMenu(true, [ false, false, true ], [ false, true, true ], [ false, false, false ], [ false, false, true ]), ] ), // TODO: Beef up tests ]; }, success, failure); });