import { ApproxStructure, Assertions, FocusTools, Keyboard, Keys, Mouse, Touch, UiFinder, Waiter } from '@ephox/agar'; import { UnitTest } from '@ephox/bedrock-client'; import { Arr, Fun, Future, Optional, Result } from '@ephox/katamari'; import { Attribute } from '@ephox/sugar'; import * as Behaviour from 'ephox/alloy/api/behaviour/Behaviour'; import { Positioning } from 'ephox/alloy/api/behaviour/Positioning'; 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 AlloyTriggers from 'ephox/alloy/api/events/AlloyTriggers'; import * as SystemEvents from 'ephox/alloy/api/events/SystemEvents'; import { Container } from 'ephox/alloy/api/ui/Container'; import { SplitDropdown } from 'ephox/alloy/api/ui/SplitDropdown'; import { tieredMenu as TieredMenu } from 'ephox/alloy/api/ui/TieredMenu'; import type { TestItem } from 'ephox/alloy/test/dropdown/TestDropdownMenu'; import * as TestDropdownMenu from 'ephox/alloy/test/dropdown/TestDropdownMenu'; import * as GuiSetup from 'ephox/alloy/test/GuiSetup'; UnitTest.asynctest('SplitDropdown List', (success, failure) => { const sink = Memento.record( Container.sketch({ containerBehaviours: Behaviour.derive([ Positioning.config({ useFixed: Fun.always }) ]) }) ); GuiSetup.setup((store, _doc, _body) => { const c = GuiFactory.build( SplitDropdown.sketch({ dom: { tag: 'span', classes: [ 'test-split-dropdown' ], attributes: { 'data-test-id': 'split-dropdown' } }, toggleClass: 'test-selected-dropdown', onExecute: (dropdown, button) => { const arg0Name = Attribute.get(dropdown.element, 'data-test-id'); const arg1Name = Attribute.get(button.element, 'data-test-id'); store.adderH('dropdown.execute(' + arg0Name + ', ' + arg1Name + ')')(); }, onItemExecute: (dropdown, tieredMenu, item) => { const arg0Name = Attribute.get(dropdown.element, 'data-test-id'); const arg1Name = Attribute.get(tieredMenu.element, 'data-test-id'); const arg2Name = Attribute.get(item.element, 'data-test-id'); AlloyTriggers.emit(item, SystemEvents.sandboxClose()); store.adderH('dropdown.item.execute(' + [ arg0Name, arg1Name, arg2Name ].join(', ') + ')')(); }, components: [ SplitDropdown.parts.button({ dom: { tag: 'button', classes: [ 'test-split-button-action' ], attributes: { 'data-test-id': 'split-dropdown-button' } }, components: [ { dom: { tag: 'div', innerHtml: 'hi' } } ] }), SplitDropdown.parts.arrow({ dom: { tag: 'button', innerHtml: 'v', classes: [ 'test-split-button-arrow' ], attributes: { 'data-test-id': 'split-dropdown-arrow' } } }) ], lazySink: (c: AlloyComponent) => { TestDropdownMenu.assertLazySinkArgs('span', 'test-split-dropdown', c); // Only gets here is the above assertion passes. return Result.value(sink.get(c)); }, parts: { menu: { dom: { tag: 'div', attributes: { 'data-test-id': 'split-tiered-menu' } }, markers: TestDropdownMenu.markers() } }, fetch: () => { const future = Future.pure([ { type: 'item', data: { value: 'alpha', meta: { text: 'Alpha' }}}, { type: 'item', data: { value: 'beta', meta: { text: 'Beta' }}} ]); return future.map((f) => { const menu = TestDropdownMenu.renderMenu({ value: 'split-dropdown-test', items: Arr.map(f, TestDropdownMenu.renderItem) }); return Optional.some(TieredMenu.singleData('test', menu)); }); } }) ); return c; }, (doc, _body, gui, component, store) => { gui.add( GuiFactory.build(sink.asSpec()) ); return [ Assertions.sAssertStructure( 'Check basic initial structure', ApproxStructure.build((s, str, _arr) => s.element('span', { attrs: { 'role': str.is('button'), 'aria-expanded': str.is('false'), 'aria-haspopup': str.is('true'), 'tabindex': str.is('-1') }, children: [ s.element('span', { attrs: { role: str.is('presentation'), tabindex: str.none() } }), s.element('span', { attrs: { role: str.is('presentation'), tabindex: str.none() } }) ] })), component.element ), store.sClear, store.sAssertEq('Should be empty', [ ]), Mouse.sClickOn(gui.element, '.test-split-button-action'), store.sAssertEq('After clicking on action', [ 'dropdown.execute(split-dropdown, split-dropdown-button)' ]), UiFinder.sNotExists(gui.element, '[role="menu"]'), store.sClear, Mouse.sClickOn(gui.element, '.test-split-button-arrow'), store.sAssertEq('After clicking on action', [ ]), Waiter.sTryUntil( 'Waiting until menu appears', UiFinder.sExists(gui.element, '[role="menu"]') ), FocusTools.sTryOnSelector('Focus should be on alpha', doc, 'li:contains("Alpha")'), Assertions.sAssertStructure( 'Check menu opened structure', ApproxStructure.build((s, str, arr) => s.element('span', { attrs: { 'role': str.is('button'), 'aria-expanded': str.is('true'), 'aria-haspopup': str.is('true'), 'aria-controls': str.contains('aria-controls') }, children: [ s.element('span', { attrs: { role: str.is('presentation') } }), s.element('span', { attrs: { role: str.is('presentation') }, classes: [ arr.has('test-selected-dropdown') ] }) ] })), component.element ), Keyboard.sKeyup(doc, Keys.escape(), { }), UiFinder.sNotExists(gui.element, '[role="menu"]'), // Now, let's do some keyboard testing. Pressing space and enter should trigger execute store.sAssertEq('Before keyboard testing: should be clear', [ ]), Keyboard.sKeydown(doc, Keys.space(), { }), store.sAssertEq('After space on button', [ 'dropdown.execute(split-dropdown, split-dropdown-button)' ]), // NOTE: this sNotExists isn't 100% fool-proof. We should probably wait first because // it is async ... however, how long to wait? UiFinder.sNotExists(gui.element, '[role="menu"]'), store.sClear, Keyboard.sKeydown(doc, Keys.enter(), { }), store.sAssertEq('After enter on button', [ 'dropdown.execute(split-dropdown, split-dropdown-button)' ]), UiFinder.sNotExists(gui.element, '[role="menu"]'), store.sClear, Keyboard.sKeydown(doc, Keys.down(), { }), store.sAssertEq('After down on button', [ ]), Waiter.sTryUntil( 'Waiting until menu appears', UiFinder.sExists(gui.element, '[role="menu"]') ), FocusTools.sTryOnSelector('Focus should be on alpha', doc, 'li:contains("Alpha")'), // Now press enter on one of the items Keyboard.sKeydown(doc, Keys.enter(), { }), store.sAssertEq('After enter on item', [ 'dropdown.item.execute(split-dropdown, split-tiered-menu, item-alpha)' ]), // NOTE: This is due to the itemExecute handler here. UiFinder.sNotExists(gui.element, '[role="menu"]'), store.sClear, // Test to make sure tapping on the button/arrow also works store.sAssertEq('Should be empty', [ ]), Touch.sTapOn(gui.element, '.test-split-button-action'), store.sAssertEq('After tapping on action', [ 'dropdown.execute(split-dropdown, split-dropdown-button)' ]), UiFinder.sNotExists(gui.element, '[role="menu"]'), store.sClear, Touch.sTapOn(gui.element, '.test-split-button-arrow'), store.sAssertEq('After tapping on action', [ ]), Waiter.sTryUntil( 'Waiting until menu appears', UiFinder.sExists(gui.element, '[role="menu"]') ), FocusTools.sTryOnSelector('Focus should be on alpha', doc, 'li:contains("Alpha")'), store.sClear ]; }, success, failure); });