import { FocusTools, GeneralSteps, Keyboard, Keys, Logger, Mouse, Step, Touch, UiFinder, Waiter } from '@ephox/agar';
import { UnitTest } from '@ephox/bedrock-client';
import { Arr, Fun, Future, Obj, Optional, Result } from '@ephox/katamari';
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 { Positioning } from 'ephox/alloy/api/behaviour/Positioning';
import * as GuiFactory from 'ephox/alloy/api/component/GuiFactory';
import * as Memento from 'ephox/alloy/api/component/Memento';
import { Container } from 'ephox/alloy/api/ui/Container';
import { Dropdown } from 'ephox/alloy/api/ui/Dropdown';
import { tieredMenu as TieredMenu } from 'ephox/alloy/api/ui/TieredMenu';
import * as TestDropdownMenu from 'ephox/alloy/test/dropdown/TestDropdownMenu';
import * as GuiSetup from 'ephox/alloy/test/GuiSetup';
import * as NavigationUtils from 'ephox/alloy/test/NavigationUtils';
interface TestFocusable {
label: string;
selector: string;
}
UnitTest.asynctest('DropdownMenuTest', (success, failure) => {
const sink = Memento.record(
Container.sketch({
containerBehaviours: Behaviour.derive([
Positioning.config({
useFixed: Fun.always
})
])
})
);
GuiSetup.setup((store, _doc, _body) => {
const makeFlow = (v: string) => Container.sketch({
dom: {
tag: 'span',
innerHtml: ' ' + v + ' ',
classes: [ v ]
},
containerBehaviours: Behaviour.derive([
Focusing.config({ })
])
});
const widget = Container.sketch({
containerBehaviours: Behaviour.derive([
Keying.config({
mode: 'flow',
selector: 'span'
})
]),
components: Arr.map([
'one',
'two',
'three'
], makeFlow)
});
const testData = {
primary: 'tools-menu',
menus: Obj.map({
'tools-menu': {
value: 'tools-menu-value',
text: 'Tools Menu',
items: Arr.map([
{ type: 'item', data: { value: 'packages', meta: { text: 'Packages' }}, hasSubmenu: true },
{ type: 'item', data: { value: 'about', meta: { text: 'About' }}},
{ type: 'widget', widget, data: { value: 'widget', meta: { }}}
], TestDropdownMenu.renderItem)
},
'packages': { // menu name should be triggering parent item so TieredMenuSpec path works
value: 'packages-menu-value',
text: 'Packages Menu',
items: Arr.map([
{ type: 'item', data: { value: 'sortby', meta: { text: 'SortBy' }}, hasSubmenu: true }
], TestDropdownMenu.renderItem)
},
'sortby': {
value: 'sortby-menu-value',
text: 'Sortby Menu',
items: Arr.map([
{ type: 'item', data: { value: 'strings', meta: { text: 'Strings' }}, hasSubmenu: true },
{ type: 'item', data: { value: 'numbers', meta: { text: 'Numbers' }}, hasSubmenu: true }
], TestDropdownMenu.renderItem)
},
'strings': {
value: 'strings-menu-value',
text: 'Strings Menu',
items: Arr.map([
{ type: 'item', data: { value: 'versions', meta: { text: 'Versions', html: 'Versions' }}},
{ type: 'item', data: { value: 'alphabetic', meta: { text: 'Alphabetic' }}}
], TestDropdownMenu.renderItem)
},
'numbers': {
value: 'numbers-menu-value',
text: 'Numbers Menu',
items: Arr.map([
{ type: 'item', data: { value: 'doubled', meta: { text: 'Doubled digits' }}, hasSubmenu: false }
], TestDropdownMenu.renderItem)
}
}, TestDropdownMenu.renderMenu),
expansions: {
packages: 'packages',
sortby: 'sortby',
strings: 'strings',
numbers: 'numbers'
}
};
const c = GuiFactory.build(
Dropdown.sketch({
uid: 'test-dropdown',
dom: {
tag: 'div',
innerHtml: '+',
classes: [ 'dropdown-button' ]
},
toggleClass: 'alloy-selected',
components: [ ],
lazySink: (c) => {
TestDropdownMenu.assertLazySinkArgs('div', 'dropdown-button', c);
return Result.value(sink.get(c));
},
parts: {
menu: TestDropdownMenu.part(store)
},
fetch: () => {
return Future.pure(testData).map((d) => Optional.from(TieredMenu.tieredData(d.primary, d.menus, d.expansions)));
}
})
);
return c;
}, (doc, _body, gui, dropdown, store) => {
gui.add(
GuiFactory.build(sink.asSpec())
);
const focusables: Record = {
toolsMenu: { label: 'tools-menu', selector: '.menu[aria-label="Tools Menu"]' },
packagesMenu: { label: 'packages-menu', selector: '.menu[aria-label="Packages Menu"]' },
sortbyMenu: { label: 'sortby-menu', selector: '.menu[aria-label="Sortby Menu"]' },
stringsMenu: { label: 'strings-menu', selector: '.menu[aria-label="Strings Menu"]' },
numbersMenu: { label: 'numbers-menu', selector: '.menu[aria-label="Numbers Menu"]' },
button: { label: 'dropdown-button', selector: '.dropdown-button' },
packages: { label: 'packages-item', selector: 'li:contains("Packages")' },
about: { label: 'about-item', selector: 'li:contains("About")' },
sortby: { label: 'sortby-item', selector: 'li:contains("SortBy")' },
strings: { label: 'strings-item', selector: 'li:contains("Strings")' },
numbers: { label: 'numbers-item', selector: 'li:contains("Numbers")' },
doubled: { label: 'doubled-item', selector: 'li:contains("Doubled")' },
versions: { label: 'versions-item', selector: 'li:contains("Versions")' },
widget: { label: 'widget-item', selector: '.item-widget' },
widgetOne: { label: 'widget-item:1', selector: '.item-widget .one' },
widgetTwo: { label: 'widget-item:2', selector: '.item-widget .two' },
widgetThree: { label: 'widget-item:3', selector: '.item-widget .three' }
};
const sTestMenus = (label: string, stored: string[], focused: TestFocusable, active: TestFocusable[], background: TestFocusable[], others: TestFocusable[]) => {
const sCheckBackground = GeneralSteps.sequence(
Arr.bind(background, (bg) => [
UiFinder.sExists(gui.element, bg.selector),
UiFinder.sNotExists(gui.element, bg.selector + '.selected-menu')
])
);
const sCheckActive = GeneralSteps.sequence(
Arr.map(active, (o) => UiFinder.sExists(gui.element, o.selector + '.selected-menu'))
);
const sCheckOthers = GeneralSteps.sequence(
Arr.map(others, (o) => UiFinder.sNotExists(gui.element, o.selector))
);
return Logger.t(
label,
GeneralSteps.sequence([
store.sAssertEq('checking store', stored),
FocusTools.sTryOnSelector('Searching for focus on: ' + focused.label, doc, focused.selector),
sCheckActive,
sCheckBackground,
sCheckOthers,
store.sClear,
Step.wait(0)
])
);
};
// A bit of dupe with DropdownButtonSpecTest
return [
Step.sync(() => {
Focusing.focus(dropdown);
}),
sTestMenus(
'Initially',
[ ],
focusables.button,
[ ], [ ], [
focusables.toolsMenu,
focusables.packagesMenu,
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
Keyboard.sKeydown(doc, Keys.enter(), { }),
Waiter.sTryUntil(
'Wait until dropdown content loads',
UiFinder.sExists(gui.element, '.menu')
),
sTestMenus(
'After open',
[ ],
focusables.packages,
[ focusables.toolsMenu ], [ ], [
focusables.packagesMenu,
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Press enter should expand
Keyboard.sKeydown(doc, Keys.enter(), {}),
sTestMenus(
'After expand packages menu',
[ ],
focusables.sortby,
[ focusables.packagesMenu ], [ focusables.toolsMenu ], [
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Press left should collapse
Keyboard.sKeydown(doc, Keys.left(), {}),
sTestMenus(
'After collapse packages menu',
[ ],
focusables.packages,
[ focusables.toolsMenu ], [ ], [
focusables.packagesMenu,
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Press right should expand again
Keyboard.sKeydown(doc, Keys.right(), {}),
sTestMenus(
'After expanding packages menu with right arrow',
[ ],
focusables.sortby,
[ focusables.packagesMenu ], [ focusables.toolsMenu ], [
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Press space should expand again
Keyboard.sKeydown(doc, Keys.space(), {}),
sTestMenus(
'After expanding sortby menu with space arrow',
[ ],
focusables.strings,
[ focusables.sortbyMenu ], [ focusables.toolsMenu, focusables.packagesMenu ], [
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Pressing down should focus on numbers
Keyboard.sKeydown(doc, Keys.down(), { }),
sTestMenus(
'After pressing down in sortby menu',
[ ],
focusables.numbers,
[ focusables.sortbyMenu ], [ focusables.toolsMenu, focusables.packagesMenu ], [
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Pressing escape should focus sortby
Keyboard.sKeyup(doc, Keys.escape(), { }),
sTestMenus(
'After pressing down in sortby menu',
[ ],
focusables.sortby,
[ focusables.packagesMenu ], [ focusables.toolsMenu ], [
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Pressing right should open up sortby menu
Keyboard.sKeydown(doc, Keys.right(), { }),
sTestMenus(
'After pressing right again after Escape',
[ ],
focusables.strings,
[ focusables.sortbyMenu ], [ focusables.toolsMenu, focusables.packagesMenu ], [
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Pressing down, then enter should open up numbers menu
Keyboard.sKeydown(doc, Keys.down(), { }),
Keyboard.sKeydown(doc, Keys.enter(), { }),
sTestMenus(
'After pressing right again after Escape',
[ ],
focusables.doubled,
[ focusables.numbersMenu ], [ focusables.sortbyMenu, focusables.toolsMenu, focusables.packagesMenu ], [
focusables.stringsMenu
]
),
// Pressing enter should trigger doubled
Keyboard.sKeydown(doc, Keys.enter(), { }),
sTestMenus(
'After pressing enter on last level',
[ 'dropdown.menu.execute: doubled' ],
focusables.doubled,
[ focusables.numbersMenu ], [ focusables.sortbyMenu, focusables.toolsMenu, focusables.packagesMenu ], [
focusables.stringsMenu
]
),
// Hover on "strings"
Mouse.sHoverOn(gui.element, focusables.strings.selector),
sTestMenus(
'After hovering on "strings" (should only expand)',
[ ],
focusables.strings,
[ focusables.sortbyMenu ], [ focusables.toolsMenu, focusables.packagesMenu ], [
focusables.numbersMenu
]
),
// Click on "about"
Mouse.sClickOn(gui.element, focusables.about.selector),
// Menus are somewhat irrelevant here, because the hover would have changed them,
// not the click
store.sAssertEq('Checking about fired', [ 'dropdown.menu.execute: about' ]),
store.sClear,
// Tap on "about"
Touch.sTapOn(gui.element, focusables.about.selector),
store.sAssertEq('Checking about fired', [ 'dropdown.menu.execute: about' ]),
store.sClear,
// Hover on "about"
Mouse.sHoverOn(gui.element, focusables.about.selector),
sTestMenus(
'After hovering on "strings"',
[ ],
focusables.about,
[ focusables.toolsMenu ], [ ], [
focusables.packagesMenu,
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Pressing right on "about" does nothing
Keyboard.sKeydown(doc, Keys.right(), { }),
sTestMenus(
'Pressing on "about" does nothing',
[ ],
focusables.about,
[ focusables.toolsMenu ], [ ], [
focusables.packagesMenu,
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Now, let's play with the inline widget
Keyboard.sKeydown(doc, Keys.tab(), { }),
sTestMenus(
'After pressing from about',
[ ],
focusables.widget,
[ focusables.toolsMenu ], [ ], [
focusables.packagesMenu,
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
// Press enter to go into the inline widget
Keyboard.sKeydown(doc, Keys.enter(), { }),
sTestMenus(
'After pressing on inline widget',
[ ],
focusables.widgetOne,
[ focusables.toolsMenu ], [ ], [
focusables.packagesMenu,
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
),
NavigationUtils.sequence(
doc, Keys.right(), {}, [
focusables.widgetTwo,
focusables.widgetThree,
focusables.widgetOne
]
),
// Press escape to exit the inline widget
Keyboard.sKeyup(doc, Keys.escape(), { }),
sTestMenus(
'After pressing inside inline widget',
[ ],
focusables.widget,
[ focusables.toolsMenu ], [ ], [
focusables.packagesMenu,
focusables.sortbyMenu,
focusables.stringsMenu,
focusables.numbersMenu
]
)
];
}, success, failure);
});