diff --git a/src/dialog/tests/unit/Dialog.ts b/src/dialog/tests/unit/Dialog.ts index 306debceee..23657be769 100644 --- a/src/dialog/tests/unit/Dialog.ts +++ b/src/dialog/tests/unit/Dialog.ts @@ -1,114 +1,210 @@ import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; -import { VNode } from '@dojo/interfaces/vdom'; -import Dialog from '../../Dialog'; +import * as sinon from 'sinon'; + +import { v } from '@dojo/widget-core/d'; +import { assignChildProperties, compareProperty, replaceChild } from '@dojo/test-extras/support/d'; +import harness, { Harness } from '@dojo/test-extras/harness'; + +import Dialog, { DialogProperties } from '../../Dialog'; import * as css from '../../styles/dialog.m.css'; +import * as iconCss from '../../../common/styles/icons.m.css'; +import * as animations from '../../../common/styles/animations.m.css'; + +const compareId = compareProperty((value: any) => { + return typeof value === 'string'; +}); + +const expectedCloseButton = function(widget: any) { + return v('button', { + classes: widget.classes(css.close), + onclick: widget.listener + }, [ + 'close dialog', + v('i', { + classes: widget.classes(iconCss.icon, iconCss.closeIcon), + role: 'presentation', + 'aria-hidden': 'true' + }) + ]); +}; + +const expected = function(widget: any, open = false, closeable = false, children: any[] = []) { + return v('div', { classes: widget.classes(css.root) }, open ? [ + v('div', { + classes: widget.classes(css.underlay), + enterAnimation: animations.fadeIn, + exitAnimation: animations.fadeOut, + key: 'underlay', + onclick: widget.listener + }), + v('div', { + 'aria-labelledby': compareId, + classes: widget.classes(css.main), + enterAnimation: animations.fadeIn, + exitAnimation: animations.fadeOut, + key: 'main', + role: 'dialog' + }, [ + v('div', { + classes: widget.classes(css.title), + key: 'title' + }, [ + v('div', { id: compareId }, [ '' ]), + closeable ? expectedCloseButton(widget) : null + ]), + v('div', { + classes: widget.classes(css.content), + key: 'content' + }, children) + ]) + ] : []); +}; + +let widget: Harness; registerSuite({ name: 'Dialog', - 'Should construct dialog with passed properties'() { - const dialog = new Dialog(); - dialog.__setProperties__({ - key: 'foo', - modal: true, + beforeEach() { + widget = harness(Dialog); + }, + + afterEach() { + widget.destroy(); + }, + + 'default properties'() { + widget.expectRender(expected(widget), 'closed dialog renders correctly'); + + widget.setProperties({ open: true, - title: 'dialog', - underlay: true, + closeable: false + }); + widget.expectRender(expected(widget, true), 'open dialog renders correctly'); + }, + + 'custom properties'() { + // force an initial render so all classes are present + widget.setProperties({ + open: true + }); + widget.getRender(); + + // set tested properties + widget.setProperties({ closeable: true, closeText: 'foo', - role: 'dialog' + enterAnimation: 'fooAnimation', + exitAnimation: 'barAnimation', + open: true, + role: 'alertdialog', + title: 'foo', + underlay: true + }); + + let expectedVdom = expected(widget, true, true); + assignChildProperties(expectedVdom, '0', { + classes: widget.classes(css.underlayVisible, css.underlay) // do this here so the class is present in future renders + }); + expectedVdom = expected(widget, true, true); + replaceChild(expectedVdom, '1,0,1,0', 'foo'); + replaceChild(expectedVdom, '1,0,0,0', 'foo'); + assignChildProperties(expectedVdom, '0', { + classes: widget.classes(css.underlayVisible, css.underlay) + }); + assignChildProperties(expectedVdom, '1', { + enterAnimation: 'fooAnimation', + exitAnimation: 'barAnimation', + role: 'alertdialog' }); - assert.strictEqual(dialog.properties.key, 'foo'); - assert.isTrue(dialog.properties.modal); - assert.isTrue(dialog.properties.open); - assert.strictEqual(dialog.properties.title, 'dialog'); - assert.isTrue(dialog.properties.underlay); - assert.isTrue(dialog.properties.closeable); - assert.strictEqual(dialog.properties.closeText, 'foo'); - assert.strictEqual(dialog.properties.role, 'dialog'); + widget.expectRender(expectedVdom); }, - 'Render correct children'() { - const dialog = new Dialog(); - dialog.__setProperties__({ - enterAnimation: 'enter', - exitAnimation: 'exit', - role: 'dialog', - closeText: 'foo' - }); - let vnode = dialog.__render__(); - assert.strictEqual(vnode.vnodeSelector, 'div', 'tagname should be div'); - assert.property(vnode.properties!.classes!, css.root); - assert.lengthOf(vnode.children, 0); + children() { + const testChildren = [ + v('p', [ 'Lorem ipsum dolor sit amet' ]), + v('a', { href: '#foo' }, [ 'foo' ]) + ]; - dialog.__setProperties__({ - open: true, - underlay: true, - role: 'dialog' + widget.setProperties({ + open: true }); - vnode = dialog.__render__(); - assert.lengthOf(vnode.children, 2); + widget.setChildren(testChildren); + + const expectedVdom = expected(widget, true, true, testChildren); + widget.expectRender(expectedVdom); }, onRequestClose() { - const dialog = new Dialog(); - dialog.__setProperties__({ + const onRequestClose = sinon.stub(); + + widget.setProperties({ + closeable: true, open: true, - onRequestClose: () => { - dialog.__setProperties__({ open: false }); - } + onRequestClose + }); + widget.sendEvent('click', { + selector: `.${css.close}` }); - ( dialog)._onCloseClick(); + assert.isTrue(onRequestClose.calledOnce, 'onRequestClose handler called when close button is clicked'); - assert.isFalse(dialog.properties.open, 'onRequestClose should be called when close button is clicked'); + widget.setProperties({ + closeable: false, + open: true, + onRequestClose + }); + widget.getRender(); + widget.sendEvent('click', { + selector: `.${css.underlay}` + }); + assert.isTrue(onRequestClose.calledOnce, 'onRequestClose handler not called when closeable is false'); }, onOpen() { - let called = false; + const onOpen = sinon.stub(); - const dialog = new Dialog(); - dialog.__setProperties__({ + widget.setProperties({ open: true, - onOpen: () => { - called = true; - } + onOpen }); - dialog.__render__(); + widget.getRender(); + assert.isTrue(onOpen.calledOnce, 'onOpen handler called when open is initially set to true'); - assert.isTrue(called, 'onOpen should be called'); + widget.setProperties({ + closeable: true, + open: true, + onOpen + }); + widget.getRender(); + assert.isTrue(onOpen.calledOnce, 'onOpen handler not called if dialog was previously open'); }, modal() { - const dialog = new Dialog(); - dialog.__setProperties__({ + const onRequestClose = sinon.stub(); + + widget.setProperties({ open: true, modal: true, - onRequestClose: () => { - dialog.__setProperties__({ open: false }); - } + onRequestClose }); - ( dialog)._onUnderlayClick(); - - assert.isTrue(dialog.properties.open, 'dialog should stay open when underlay is clicked and modal is true'); - dialog.__setProperties__({ modal: false }); - ( dialog)._onUnderlayClick(); - - assert.isUndefined(dialog.properties.open, 'dialog should close if underlay is clicked and modal is false'); - }, + widget.sendEvent('click', { + selector: `.${css.underlay}` + }); + assert.isFalse(onRequestClose.called, 'onRequestClose should not be called when the underlay is clicked and modal is true'); - closeable() { - const dialog = new Dialog(); - dialog.__setProperties__({ - closeable: false, + widget.setProperties({ open: true, - title: 'foo' + modal: false, + onRequestClose }); - dialog.__render__(); - ( dialog)._onCloseClick(); + widget.getRender(); - assert.isTrue(dialog.properties.open, 'dialog should not close if closeable is false'); + widget.sendEvent('click', { + selector: `.${css.underlay}` + }); + assert.isTrue(onRequestClose.called, 'onRequestClose is called when the underlay is clicked and modal is false'); } });