diff --git a/src/common/tests/functional/all.ts b/src/common/tests/functional/all.ts index 3225c83020..900d6cb481 100644 --- a/src/common/tests/functional/all.ts +++ b/src/common/tests/functional/all.ts @@ -12,5 +12,6 @@ import '../../../slider/tests/functional/Slider'; import '../../../tabcontroller/tests/functional/TabController'; import '../../../textarea/tests/functional/Textarea'; import '../../../textinput/tests/functional/TextInput'; +import '../../../timepicker/tests/functional/TimePicker'; import '../../../titlepane/tests/functional/TitlePane'; import '../../../tooltip/tests/functional/Tooltip'; diff --git a/src/timepicker/example/index.ts b/src/timepicker/example/index.ts index 51a55a6372..5929bf92ac 100644 --- a/src/timepicker/example/index.ts +++ b/src/timepicker/example/index.ts @@ -54,207 +54,227 @@ export class App extends ThemedMixin(WidgetBase) { }, [ 'Accepts 24-hour time with a leading zero, rounded to the nearest half hour.' ]), v('h3', [ 'Filter options on input' ]), - w(TimePicker, { - inputProperties: { - describedBy: 'description1', - placeholder: 'Enter a value' - }, - key: '1', - onChange: (value: string) => { - this._setValue('value1', value); - }, - onRequestOptions: (value: string, options: TimeUnits[]) => { - if (!value) { - this._options = options; - } - else { + v('div', { id: 'example-filter-on-input' }, [ + w(TimePicker, { + inputProperties: { + describedBy: 'description1', + placeholder: 'Enter a value' + }, + key: '1', + onChange: (value: string) => { + this._setValue('value1', value); + }, + onRequestOptions: (value: string, options: TimeUnits[]) => { + if (!value) { + this._options = options; + } + else { - const matching = options.filter(option => { - const { hour, minute = 0 } = option; - const hours = hour >= 10 ? hour : `0${hour}`; - const minutes = minute >= 10 ? minute : `0${minute}`; - return `${hours}:${minutes}`.indexOf(value) === 0; - }); + const matching = options.filter(option => { + const { hour, minute = 0 } = option; + const hours = hour >= 10 ? hour : `0${hour}`; + const minutes = minute >= 10 ? minute : `0${minute}`; + return `${hours}:${minutes}`.indexOf(value) === 0; + }); - this._options = matching.length ? matching : options; - } - this.invalidate(); - }, - options: this._options, - step: 1800, - theme: this._theme, - value: this._values['value1'] - }), + this._options = matching.length ? matching : options; + } + this.invalidate(); + }, + options: this._options, + step: 1800, + theme: this._theme, + value: this._values['value1'] + }) + ]), v('h3', [ 'Open on focus' ]), - w(TimePicker, { - inputProperties: { - describedBy: 'description1', - placeholder: 'Enter a value' - }, - key: '2', - openOnFocus: true, - onChange: (value: string) => { - this._setValue('value2', value); - }, - onRequestOptions: this.onRequestOptions, - options: this._options, - step: 1800, - theme: this._theme, - value: this._values['value2'] - }), + v('div', { id: 'example-open-on-focus' }, [ + w(TimePicker, { + inputProperties: { + describedBy: 'description1', + placeholder: 'Enter a value' + }, + key: '2', + openOnFocus: true, + onChange: (value: string) => { + this._setValue('value2', value); + }, + onRequestOptions: this.onRequestOptions, + options: this._options, + step: 1800, + theme: this._theme, + value: this._values['value2'] + }) + ]), v('h3', [ 'Disabled menu items' ]), v('p', { id: 'description2', classes: baseCss.visuallyHidden }, [ 'Accepts 24-hour time with a leading zero, rounded to the nearest hour.' ]), - w(TimePicker, { - inputProperties: { - describedBy: 'description2', - placeholder: 'Enter a value' - }, - isOptionDisabled: (option: TimeUnits) => option.hour >= 12, - key: '3', - onChange: (value: string) => { - this._setValue('value3', value); - }, - onRequestOptions: this.onRequestOptions, - options: this._options, - step: 3600, - theme: this._theme, - value: this._values['value3'] - }), + v('div', { id: 'example-disabled-items' }, [ + w(TimePicker, { + inputProperties: { + describedBy: 'description2', + placeholder: 'Enter a value' + }, + isOptionDisabled: (option: TimeUnits) => option.hour >= 12, + key: '3', + onChange: (value: string) => { + this._setValue('value3', value); + }, + onRequestOptions: this.onRequestOptions, + options: this._options, + step: 3600, + theme: this._theme, + value: this._values['value3'] + }) + ]), v('h3', [ 'Disabled' ]), - w(TimePicker, { - inputProperties: { - describedBy: 'description1', - placeholder: 'Enter a value' - }, - key: '4', - disabled: true, - theme: this._theme - }), + v('div', { id: 'example-disabled' }, [ + w(TimePicker, { + inputProperties: { + describedBy: 'description1', + placeholder: 'Enter a value' + }, + key: '4', + disabled: true, + theme: this._theme + }) + ]), v('h3', [ 'Read Only' ]), - w(TimePicker, { - inputProperties: { - describedBy: 'description1', - placeholder: 'Enter a value' - }, - key: '5', - readOnly: true, - theme: this._theme - }), + v('div', { id: 'example-readonly' }, [ + w(TimePicker, { + inputProperties: { + describedBy: 'description1', + placeholder: 'Enter a value' + }, + key: '5', + readOnly: true, + theme: this._theme + }) + ]), - v('h3', [ 'Label' ]), - w(TimePicker, { - key: '6', - inputProperties: { - describedBy: 'description1' - }, - label: 'Enter a value', - onChange: (value: string) => { - this._setValue('value6', value); - }, - onRequestOptions: this.onRequestOptions, - options: this._options, - step: 1800, - theme: this._theme, - value: this._values['value6'] - }), + v('h3', [ 'Labeled' ]), + v('div', { id: 'example-labeled' }, [ + w(TimePicker, { + key: '6', + inputProperties: { + describedBy: 'description1' + }, + label: 'Enter a value', + onChange: (value: string) => { + this._setValue('value6', value); + }, + onRequestOptions: this.onRequestOptions, + options: this._options, + step: 1800, + theme: this._theme, + value: this._values['value6'] + }) + ]), v('h3', [ 'Required and validated' ]), - w(TimePicker, { - inputProperties: { - describedBy: 'description1', - placeholder: 'Enter a value' - }, - invalid: this._invalid, - key: '7', - required: true, - onBlur: (value: string) => { - this._invalid = value.trim().length === 0; - this.invalidate(); - }, - onChange: (value: string) => { - this._invalid = value.trim().length === 0; - this._setValue('value7', value); - }, - onRequestOptions: this.onRequestOptions, - options: this._options, - step: 1800, - theme: this._theme, - value: this._values['value7'] - }), + v('div', { id: 'example-required-validated' }, [ + w(TimePicker, { + inputProperties: { + describedBy: 'description1', + placeholder: 'Enter a value' + }, + invalid: this._invalid, + key: '7', + required: true, + onBlur: (value: string) => { + this._invalid = value.trim().length === 0; + this.invalidate(); + }, + onChange: (value: string) => { + this._invalid = value.trim().length === 0; + this._setValue('value7', value); + }, + onRequestOptions: this.onRequestOptions, + options: this._options, + step: 1800, + theme: this._theme, + value: this._values['value7'] + }) + ]), v('h3', [ 'One second increment' ]), v('p', { id: 'description8', classes: baseCss.visuallyHidden }, [ 'Accepts 24-hour time with a leading zero, rounded to the nearest second.' ]), - w(TimePicker, { - end: '12:00:59', - inputProperties: { - describedBy: 'description8', - placeholder: 'Enter a value' - }, - key: '8', - options: this._options, - onChange: (value: string) => { - this._setValue('value8', value); - }, - onRequestOptions: this.onRequestOptions, - start: '12:00:00', - step: 1, - theme: this._theme, - value: this._values['value8'] - }), + v('div', { id: 'example-every-second' }, [ + w(TimePicker, { + end: '12:00:59', + inputProperties: { + describedBy: 'description8', + placeholder: 'Enter a value' + }, + key: '8', + options: this._options, + onChange: (value: string) => { + this._setValue('value8', value); + }, + onRequestOptions: this.onRequestOptions, + start: '12:00:00', + step: 1, + theme: this._theme, + value: this._values['value8'] + }) + ]), v('h3', [ 'Use 12-hour time' ]), v('p', { id: 'description9', classes: baseCss.visuallyHidden }, [ 'Accepts 12-hour time without a leading zero, rounded to the nearest half hour.' ]), - w(TimePicker, { - getOptionLabel: (option: TimeUnits) => { - TODAY.setHours(option.hour); - TODAY.setMinutes(option.minute as number); - return getEnglishTime(TODAY); - }, - inputProperties: { - describedBy: 'description9', - placeholder: 'Enter a value' - }, - key: '9', - onChange: (value: string) => { - this._setValue('value9', value ); - }, - onRequestOptions: this.onRequestOptions, - options: this._options, - step: 1800, - theme: this._theme, - value: this._values['value9'] - }), + v('div', { id: 'example-12-hour' }, [ + w(TimePicker, { + getOptionLabel: (option: TimeUnits) => { + TODAY.setHours(option.hour); + TODAY.setMinutes(option.minute as number); + return getEnglishTime(TODAY); + }, + inputProperties: { + describedBy: 'description9', + placeholder: 'Enter a value' + }, + key: '9', + onChange: (value: string) => { + this._setValue('value9', value ); + }, + onRequestOptions: this.onRequestOptions, + options: this._options, + step: 1800, + theme: this._theme, + value: this._values['value9'] + }) + ]), v('h3', [ 'Native ``' ]), - w(TimePicker, { - key: '10', - inputProperties: { - describedBy: 'description', - placeholder: 'Enter a value' - }, - onChange: (value: string) => { - this._setValue('value10', value); - }, - step: 1800, - theme: this._theme, - useNativeElement: true, - invalid: true, - label: 'foo', - value: this._values['value10'] - }) + v('div', { id: 'example-native' }, [ + w(TimePicker, { + key: '10', + inputProperties: { + describedBy: 'description', + placeholder: 'Enter a value' + }, + onChange: (value: string) => { + this._setValue('value10', value); + }, + step: 1800, + theme: this._theme, + useNativeElement: true, + invalid: true, + label: 'foo', + value: this._values['value10'] + }) + ]) ]); } } diff --git a/src/timepicker/tests/functional/TimePicker.ts b/src/timepicker/tests/functional/TimePicker.ts new file mode 100644 index 0000000000..219b27a6ab --- /dev/null +++ b/src/timepicker/tests/functional/TimePicker.ts @@ -0,0 +1,217 @@ +const { registerSuite } = intern.getInterface('object'); +const { assert } = intern.getPlugin('chai'); + +import { Remote } from 'intern/lib/executors/Node'; +import Command from '@theintern/leadfoot/Command'; +import Element from '@theintern/leadfoot/Element'; +import keys from '@theintern/leadfoot/keys'; +import * as comboBoxCss from '../../../combobox/styles/comboBox.m.css'; +import * as listboxCss from '../../../listbox/styles/listbox.m.css'; +import * as textinputCss from '../../../textinput/styles/textinput.m.css'; + +const DELAY = 300; + +function getPage(remote: Remote, exampleId: string) { + return remote + .get('http://localhost:9000/_build/common/example/?module=timepicker') + .setFindTimeout(5000) + .findById(exampleId); +} + +function testDisabledPicker(remote: Remote, exampleId: string) { + return getPage(remote, exampleId) + .findByCssSelector(`.${comboBoxCss.controls} .${textinputCss.input}`) + .click() + .sleep(DELAY) + .execute(`return document.activeElement === document.querySelector('#${exampleId} .${comboBoxCss.controls} .${textinputCss.input}');`) + .then(function (this: Command, isEqual) { + if (isEqual) { + return (> this.parent) + .type('1') + .sleep(DELAY) + .getProperty('value') + .then((value) => { + assert.strictEqual(value, '', 'The input should not allow text entry.'); + }); + } + }) + .end() + .setFindTimeout(100) + .findAllByCssSelector(`.${comboBoxCss.dropdown}`) + .then((elements) => { + assert.strictEqual(elements.length, 0); + }) + .end() + .setFindTimeout(5000) + .findByCssSelector(`.${comboBoxCss.controls} .${comboBoxCss.trigger}`) + .click() + .end() + .sleep(DELAY) + .execute(`return document.activeElement === document.querySelector('#${exampleId} .${comboBoxCss.controls} .${textinputCss.input}');`) + .then(isEqual => { + assert.isFalse(isEqual, 'Input should not gain focus when dropdown trigger is clicked.'); + }) + .setFindTimeout(100) + .findAllByCssSelector(`.${comboBoxCss.dropdown}`) + .then((elements) => { + assert.strictEqual(elements.length, 0); + }) + .end(); +} + +registerSuite('TimePicker', { + 'picker opens on input'() { + const exampleId = 'example-filter-on-input'; + return getPage(this.remote, exampleId) + .findByCssSelector(`.${comboBoxCss.controls} .${textinputCss.input}`) + .type('1') + .end() + .sleep(DELAY) + .execute(`return document.activeElement === document.querySelector('#${exampleId} .${comboBoxCss.controls} .${textinputCss.input}');`) + .then(isEqual => { + assert.isTrue(isEqual); + }) + .findByCssSelector(`.${comboBoxCss.dropdown}`) + .getSize() + .then(({ height }) => { + assert.isAbove(height, 0); + }) + .end(); + }, + 'picker opens on focus'() { + const { browserName = '' } = this.remote.session.capabilities; + if (browserName.toLowerCase() === 'microsoftedge') { + this.skip('Edge driver does not handle focus on click'); + } + + const exampleId = 'example-open-on-focus'; + return getPage(this.remote, exampleId) + .findByCssSelector(`.${comboBoxCss.controls} .${textinputCss.input}`) + .click() + .end() + .sleep(DELAY) + .execute(`return document.activeElement === document.querySelector('#${exampleId} .${comboBoxCss.controls} .${textinputCss.input}');`) + .then(isEqual => { + assert.isTrue(isEqual); + }) + .findByCssSelector(`.${comboBoxCss.dropdown}`) + .getSize() + .then(({ height }) => { + assert.isAbove(height, 0); + }) + .end(); + }, + 'disabled menu items cannot be clicked'() { + const exampleId = 'example-disabled-items'; + return getPage(this.remote, exampleId) + .findByCssSelector(`.${comboBoxCss.controls} .${comboBoxCss.trigger}`) + .click() + .end() + .sleep(DELAY) + .execute(`return document.activeElement === document.querySelector('#${exampleId} .${comboBoxCss.controls} .${textinputCss.input}');`) + .then(isEqual => { + assert.isTrue(isEqual); + }) + .findByCssSelector(`.${comboBoxCss.dropdown}`) + .getSize() + .then(({ height }) => { + assert.isAbove(height, 0); + }) + .end() + .findByCssSelector(`.${comboBoxCss.dropdown} .${listboxCss.disabledOption}`) + .click() + .end() + .findByCssSelector(`.${comboBoxCss.controls} .${textinputCss.input}`) + .getProperty('value') + .then((value) => { + assert.strictEqual(value, '', 'The input value should not contain the disabled value.'); + }) + .end() + .findByCssSelector(`.${comboBoxCss.dropdown}`) + .getSize() + .then(({ height }) => { + assert.isAbove(height, 0, 'The dropdown should remain open.'); + }) + .end(); + }, + 'disabled timepickers cannot be opened'() { + const { browserName } = this.remote.session.capabilities; + if (browserName === 'firefox') { + this.skip('Firefox does not like clicking on disabled things.'); + } + + return testDisabledPicker(this.remote, 'example-disabled'); + }, + 'readonly timepickers cannot be opened'() { + return testDisabledPicker(this.remote, 'example-readonly'); + }, + 'validated inputs update on input'() { + const { browserName } = this.remote.session.capabilities; + if (browserName === 'internet explorer') { + this.skip('Test does not work on Internet Explorer'); + } + + const exampleId = 'example-required-validated'; + return getPage(this.remote, exampleId) + .findByCssSelector(`.${comboBoxCss.controls} .${textinputCss.root}`) + .findByCssSelector(`.${textinputCss.input}`) + .click() + .end() + .sleep(DELAY) + .getProperty('className') + .then((className: string) => { + assert.notInclude(className, textinputCss.invalid); + }) + .findByCssSelector(`.${textinputCss.input}`) + .type('1') + .end() + .sleep(DELAY) + .getProperty('className') + .then((className: string) => { + assert.notInclude(className, textinputCss.invalid); + }) + .findByCssSelector(`.${textinputCss.input}`) + .type(keys.BACKSPACE) + .end() + .sleep(DELAY) + .getProperty('className') + .then((className: string) => { + assert.include(className, textinputCss.invalid); + }) + .end(); + }, + 'validated inputs update on focus change'() { + const { browserName = '' } = this.remote.session.capabilities; + if (browserName.toLowerCase() === 'microsoftedge') { + this.skip('Edge driver does not handle focus on click'); + } + if (browserName === 'internet explorer') { + this.skip('Test does not work on Internet Explorer'); + } + + const exampleId = 'example-required-validated'; + return getPage(this.remote, exampleId) + .findByCssSelector(`.${comboBoxCss.controls} .${textinputCss.root}`) + .findByCssSelector(`.${textinputCss.input}`) + .click() + .end() + .sleep(DELAY) + .getProperty('className') + .then((className: string) => { + assert.notInclude(className, textinputCss.invalid); + }) + .end() + .end() + .findByCssSelector(`#example-filter-on-input .${comboBoxCss.controls} .${textinputCss.root}`) + .click() + .end() + .sleep(DELAY) + .findById(exampleId) + .findByCssSelector(`.${comboBoxCss.controls} .${textinputCss.root}`) + .getProperty('className') + .then((className: string) => { + assert.include(className, textinputCss.invalid); + }) + .end(); + } +});