import { ApproxStructure, Assertions, Chain, GeneralSteps, Logger, Mouse, Step, Touch, UiFinder, Waiter } from '@ephox/agar'; import { UnitTest } from '@ephox/bedrock-client'; import { Arr, Future, Optional, Result } from '@ephox/katamari'; import { Compare, Css, Html } from '@ephox/sugar'; import * as GuiFactory from 'ephox/alloy/api/component/GuiFactory'; import { Button } from 'ephox/alloy/api/ui/Button'; import { Container } from 'ephox/alloy/api/ui/Container'; import { Dropdown } from 'ephox/alloy/api/ui/Dropdown'; import { InlineView } from 'ephox/alloy/api/ui/InlineView'; 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'; import * as Sinks from 'ephox/alloy/test/Sinks'; import * as TestBroadcasts from 'ephox/alloy/test/TestBroadcasts'; UnitTest.asynctest('InlineViewTest', (success, failure) => { GuiSetup.setup((_store, _doc, _body) => Sinks.relativeSink(), (_doc, _body, gui, component, store) => { const inline = GuiFactory.build( InlineView.sketch({ dom: { tag: 'div', classes: [ 'test-inline' ] }, lazySink: (comp) => { Assertions.assertEq('Checking InlineView passed through to lazySink', true, Compare.eq(inline.element, comp.element)); return Result.value(component); }, getRelated: () => { return Optional.some(related); } // onEscape: store.adderH('inline.escape') }) ); const related = GuiFactory.build({ dom: { tag: 'div', classes: [ 'related-to-inline' ], styles: { background: 'blue', width: '50px', height: '50px' } } }); gui.add(related); const sCheckOpen = (label: string) => Logger.t( label, GeneralSteps.sequence([ Waiter.sTryUntil( 'Test inline should not be DOM', UiFinder.sExists(gui.element, '.test-inline') ), Step.sync(() => { Assertions.assertEq('Checking isOpen API', true, InlineView.isOpen(inline)); }) ]) ); const sCheckClosed = (label: string) => Logger.t( label, GeneralSteps.sequence([ Waiter.sTryUntil( 'Test inline should not be in DOM', UiFinder.sNotExists(gui.element, '.test-inline') ), Step.sync(() => { Assertions.assertEq('Checking isOpen API', false, InlineView.isOpen(inline)); }) ]) ); return [ UiFinder.sNotExists(gui.element, '.test-inline'), Logger.t( 'Check that getContent is none for an inline menu that has not shown anything', Step.sync(() => { const contents = InlineView.getContent(inline); Assertions.assertEq('Should be none', true, contents.isNone()); }) ), Step.sync(() => { InlineView.showAt(inline, Container.sketch({ dom: { innerHtml: 'Inner HTML' } }), { anchor: { type: 'selection', root: gui.element } }); }), sCheckOpen('After show'), Logger.t( 'Check that getContent is some now that the inline menu has shown something', Step.sync(() => { const contents = InlineView.getContent(inline); Assertions.assertEq('Checking HTML of inline contents', 'Inner HTML', Html.get(contents.getOrDie( 'Could not find contents' ).element)); }) ), Logger.t( 'Change the content using setContent and check it is there', Step.sync(() => { InlineView.setContent(inline, { dom: { tag: 'div', innerHtml: 'changed-html' } }); const contents = InlineView.getContent(inline); Assertions.assertEq('Checking HTML of inline contents has changed', 'changed-html', Html.get(contents.getOrDie( 'Could not find contents' ).element)); }) ), Logger.t( 'Check that changed content is in the DOM', Chain.asStep(component.element, [ UiFinder.cFindIn('.test-inline'), Assertions.cAssertStructure( 'Checking structure of changed content', ApproxStructure.build((s, str, arr) => s.element('div', { classes: [ arr.has('test-inline') ], children: [ s.element('div', { html: str.is('changed-html') }) ] })) ) ]) ), Step.sync(() => { InlineView.hide(inline); }), Logger.t( 'Check that getContent is none not that inline view has been hidden again', Step.sync(() => { const contents = InlineView.getContent(inline); Assertions.assertEq('Should be none', true, contents.isNone()); }) ), sCheckClosed('After hide'), Step.sync(() => { InlineView.showAt(inline, Container.sketch({ dom: { innerHtml: 'Inner HTML' } }), { anchor: { type: 'makeshift', x: 50, y: 50 } }); }), sCheckOpen('After show'), Logger.t( 'Check that inline view has a top and left', Chain.asStep(gui.element, [ UiFinder.cFindIn('.test-inline'), Chain.op((value) => { Assertions.assertEq('Check view CSS top is 50px', '50px', Css.getRaw(value, 'top').getOr('no top found')); Assertions.assertEq('Check view CSS left is 50px', '50px', Css.getRaw(value, 'left').getOr('no left found')); }) ]) ), Step.sync(() => { InlineView.hide(inline); }), sCheckClosed('After hide'), Logger.t( 'Show inline view again, this time with buttons', Step.sync(() => { const buildDropdown = (buttonText: string, optionPrefix: string) => Dropdown.sketch({ dom: { tag: 'button', innerHtml: buttonText }, components: [], toggleClass: 'alloy-selected', lazySink: () => { return Result.value(component); }, parts: { menu: TestDropdownMenu.part(store) }, fetch: () => { const future = Future.pure([ { type: 'item', data: { value: optionPrefix.toLowerCase() + '-1', meta: { text: optionPrefix + '-1' }}}, { type: 'item', data: { value: optionPrefix.toLowerCase() + '-2' + buttonText, meta: { text: optionPrefix + '-2' }}} ]); return future.map((f) => { const menu = TestDropdownMenu.renderMenu({ value: 'inline-view-test', items: Arr.map(f, TestDropdownMenu.renderItem) }); return Optional.some(TieredMenu.singleData('test', menu)); }); } }); InlineView.showAt(inline, Container.sketch({ components: [ Button.sketch({ uid: 'bold-button', dom: { tag: 'button', innerHtml: 'B', classes: [ 'bold-button' ] }, action: store.adder('bold') }), Button.sketch({ uid: 'italic-button', dom: { tag: 'button', innerHtml: 'I', classes: [ 'italic-button' ] }, action: store.adder('italic') }), Button.sketch({ uid: 'underline-button', dom: { tag: 'button', innerHtml: 'U', classes: [ 'underline-button' ] }, action: store.adder('underline') }), buildDropdown('+', 'Option'), buildDropdown('-', 'Item') ] }), { anchor: { type: 'selection', root: gui.element } }); }) ), sCheckOpen('Should still be open with buttons and a dropdown'), TestBroadcasts.sDismissOn( 'toolbar: should not close', gui, '.bold-button' ), sCheckOpen('Broadcasting dismiss on button should not close inline toolbar'), store.sAssertEq('Check that the store is empty initially', [ ]), Mouse.sClickOn(gui.element, 'button:contains("B")'), store.sAssertEq('Check that bold activated', [ 'bold' ]), store.sClear, store.sAssertEq('Check that the store is empty initially', [ ]), Touch.sTapOn(gui.element, 'button:contains("B")'), store.sAssertEq('Check that bold activated', [ 'bold' ]), // TODO: Make it not close if the inline toolbar had a dropdown, and the dropdown // item was selected. Requires composition of "isPartOf" Logger.t( 'Check that clicking on a dropdown item in the inline toolbar does not dismiss popup', GeneralSteps.sequence([ // Click on the dropdown Mouse.sClickOn(gui.element, 'button:contains(+)'), // Wait until dropdown loads. Waiter.sTryUntil( 'Waiting for dropdown list to appear', UiFinder.sExists(gui.element, 'li:contains("Option-1")') ), TestBroadcasts.sDismissOn( 'dropdown item: should not close', gui, 'li:contains("Option-2")' ), sCheckOpen('Broadcasting dismiss on a dropdown item should not close inline toolbar') ]) ), Logger.t( 'Check that tapping on a dropdown item in the inline toolbar does not dismiss popup', GeneralSteps.sequence([ // Tap on the dropdown Touch.sTapOn(gui.element, 'button:contains(-)'), // Wait until dropdown loads. Waiter.sTryUntil( 'Waiting for dropdown list to appear', UiFinder.sExists(gui.element, 'li:contains("Item-1")') ), TestBroadcasts.sDismissOn( 'dropdown item: should not close', gui, 'li:contains("Item-2")' ), sCheckOpen('Broadcasting dismiss on a dropdown item should not close inline toolbar') ]) ), TestBroadcasts.sDismiss( 'related element: should not close', gui, related.element ), sCheckOpen('The inline view should not have closed when broadcasting on related'), TestBroadcasts.sDismiss( 'outer gui element: should close', gui, gui.element ), sCheckClosed('Broadcasting dismiss on a external element should close inline toolbar') ]; }, success, failure); });