import { RenderResult , WNode , DefaultMiddlewareResult } from '@dojo/framework/core/interfaces' ; const { describe , it , afterEach } = intern . getInterface ( 'bdd' ) ; const { assert } = intern . getPlugin ( 'chai' ) ; import * as sinon from 'sinon' ; import { tsx , create } from '@dojo/framework/core/vdom' ; import assertionTemplate from '@dojo/framework/testing/harness/assertionTemplate' ; import select from '@dojo/framework/testing/harness/support/selector' ; import focus from '@dojo/framework/core/middleware/focus' ; import { compareTheme , createHarness , stubEvent } from '../../../common/tests/support/test-helpers' ; import { Keys } from '../../../common/util' ; import Calendar from '../../../calendar' ; import TriggerPopup from '../../../trigger-popup' ; import TextInput from '../../../text-input' ; import DateInput from '../../index' ; import { formatDate , formatDateISO } from '../../date-utils' ; import bundle from '../../nls/DateInput' ; import * as css from '../../../theme/default/date-input.m.css' ; const { messages } = bundle ; const now = new Date ( ) ; const today = new Date ( now . getFullYear ( ) , now . getMonth ( ) , now . getDate ( ) ) ; const noop = ( ) => { } ; const harness = createHarness ( [ compareTheme ] ) ; function createFocusMock ( { shouldFocus = false , focused = false , isFocused = false , focus = ( ) => { } } = { } ) { const factory = create ( ) ; return ( ) => factory ( ( ) => ( { shouldFocus : ( ) => shouldFocus , focused : ( ) => focused , isFocused : ( ) => isFocused , focus } ) ) ( ) ; } const baseTemplate = ( date ? ) => assertionTemplate ( ( ) => { return ( < div classes = { [ undefined , css . root ] } > < input assertion - key = "input" type = "hidden" name = "dateInput" value = { date ? formatDateISO ( date ) : '' } aria - hidden = "true" required = { undefined } / > < TriggerPopup variant = { undefined } classes = { undefined } theme = { undefined } key = "popup" > { { trigger : ( ) => < button />, content : ( ) => < div /> } } < /TriggerPopup> < /div>); } ) ; const textInputTemplate = assertionTemplate ( ( ) => { return ( < div classes = { css . input } > < TextInput key = "input" disabled = { false } required = { undefined } readOnly = { false } focus = { ( ) => false } theme = { { } } type = "text" onFocus = { noop } onBlur = { noop } onValue = { noop } initialValue = { undefined } valid = { undefined } onKeyDown = { noop } variant = { undefined } classes = { undefined } kind = { undefined } > { { trailing : undefined } } < /TextInput> < /div>); } ) ; const calendarTemplate = assertionTemplate ( ( ) => { return ( < div classes = { css . popup } > < Calendar key = "calendar" focus = { ( ) => false } maxDate = { undefined } minDate = { undefined } onValue = { noop } initialValue = { today } theme = { undefined } variant = { undefined } classes = { undefined } / > < /div>); } ) ; /** Example custom parser for YYYY|MM|DD date strings */ function customDateParser ( input ? ) { if ( ! input ) { return undefined ; } const parts = input . split ( '|' ) ; return new Date ( Number ( parts [ 0 ] ) , Number ( parts [ 1 ] ) - 1 , Number ( parts [ 2 ] ) ) ; } function customDateFormatter ( input ? ) { if ( ! input || isNaN ( input . valueOf ( ) ) ) { return undefined ; } return [ input . getFullYear ( ) , input . getMonth ( ) + 1 , input . getDate ( ) ] . join ( '|' ) ; } describe ( 'DateInput' , ( ) => { const onValue = sinon . stub ( ) ; afterEach ( ( ) => { onValue . resetHistory ( ) ; } ) ; it ( 'renders with default date when provided an invalid initial date' , ( ) => { const h = harness ( ( ) => ( < DateInput name = "dateInput" initialValue = "not a date" onValue = { onValue } / > ) ) ; h . expect ( baseTemplate ( ) ) ; } ) ; it ( 'does not render a default date when value is provided and invalid' , ( ) => { const h = harness ( ( ) => ( < DateInput name = "dateInput" value = "not a date" onValue = { onValue } / > ) ) ; h . expect ( baseTemplate ( ) . setProperty ( '@input' , 'value' , '' ) ) ; } ) ; it ( 'renders with initial value' , ( ) => { const initialValue = new Date ( 2019 , 11 , 4 ) ; const h = harness ( ( ) => ( < DateInput name = "dateInput" onValue = { onValue } initialValue = { formatDateISO ( initialValue ) } / > ) ) ; h . expect ( baseTemplate ( initialValue ) ) ; } ) ; it ( 'renders with an initial value in a custom format' , ( ) => { const h = harness ( ( ) => ( < DateInput name = "dateInput" onValue = { onValue } initialValue = "2020|5|1" dateParser = { customDateParser } dateFormatter = { customDateFormatter } / > ) ) ; h . expect ( baseTemplate ( new Date ( 2020 , 4 , 1 ) ) ) ; } ) ; it ( 'shows calendar when triggered via icon' , ( ) => { const h = harness ( ( ) => < DateInput name = "dateInput" / > ) ; const toggleOpen = sinon . stub ( ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , toggleOpen ) ; h . expect ( textInputTemplate , ( ) => triggerResult ) ; const [ dateIcon ] = select ( '@dateIcon' , ( select ( '@input' , triggerResult ) [ 0 ] . children ! [ 0 ] as any ) . trailing ) ; dateIcon . properties . onclick ( stubEvent ) ; h . expect ( baseTemplate ( ) ) ; sinon . assert . calledOnce ( toggleOpen ) ; } ) ; it ( 'renders disabled' , ( ) => { const h = harness ( ( ) => < DateInput disabled = { true } > Label < /DateInput>); const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , ( ) => { } ) ; h . expect ( textInputTemplate . setProperty ( '@input' , 'disabled' , true ) , ( ) => triggerResult ) ; } ) ; it ( 'renders readonly' , ( ) => { const h = harness ( ( ) => < DateInput readOnly = { true } > Label < /DateInput>); const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , ( ) => { } ) ; h . expect ( textInputTemplate . setProperty ( '@input' , 'readOnly' , true ) , ( ) => triggerResult ) ; } ) ; it ( 'renders with a label as the only child' , ( ) => { const h = harness ( ( ) => < DateInput name = "dateInput" > Label < /DateInput>); const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , ( ) => { } ) ; h . expect ( textInputTemplate , ( ) => triggerResult ) ; assert . equal ( h . trigger ( '@popup' , ( node ) => ( ) => ( node . children as any ) [ 0 ] . trigger ( ) . children [ 0 ] . children [ 0 ] . label ) , 'Label' ) ; } ) ; it ( 'renders with a named label child' , ( ) => { const h = harness ( ( ) => < DateInput name = "dateInput" > { { label : 'Label' } } < /DateInput>); const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , ( ) => { } ) ; h . expect ( textInputTemplate , ( ) => triggerResult ) ; assert . equal ( h . trigger ( '@popup' , ( node ) => ( ) => ( node . children as any ) [ 0 ] . trigger ( ) . children [ 0 ] . children [ 0 ] . label ) , 'Label' ) ; } ) ; it ( 'label interface edge case' , ( ) => { type WeirdNode = WNode & { label : RenderResult ; } ; function makeWeirdNode ( ) { const node = < div > The label < /div>; ( node as WeirdNode ) . label = 'Not the label' ; return node as WeirdNode ; } const myWeirdNode = makeWeirdNode ( ) ; const h = harness ( ( ) => < DateInput name = "dateInput" > { myWeirdNode } < /DateInput>); const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , ( ) => { } ) ; h . expect ( textInputTemplate , ( ) => triggerResult ) ; assert . equal ( h . trigger ( '@popup' , ( node ) => ( ) => ( node . children as any ) [ 0 ] . trigger ( ) . children [ 0 ] . children [ 0 ] . label . children [ 0 ] ) , 'The label' ) ; } ) ; it ( 'shows calendar when triggered via keyboard' , ( ) => { const h = harness ( ( ) => < DateInput name = "dateInput" / > ) ; const toggleOpen = sinon . stub ( ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , toggleOpen ) ; h . expect ( textInputTemplate , ( ) => triggerResult ) ; const [ input ] = select ( '@input' , triggerResult ) ; input . properties . onKeyDown ( Keys . Enter ) ; sinon . assert . calledOnce ( toggleOpen ) ; } ) ; it ( 'focus popup content on trigger' , ( ) => { const focusStub = sinon . stub ( ) ; const focusMock = createFocusMock ( { focus : focusStub } ) ; const h = harness ( ( ) => < DateInput name = "dateInput" / > , { middleware : [ [ focus , focusMock ] ] } ) ; h . expect ( baseTemplate ( ) ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , noop ) ; select ( '@input' , triggerResult ) [ 0 ] . properties . onKeyDown ( Keys . Enter ) ; sinon . assert . calledOnce ( focusStub ) ; } ) ; it ( 'allows manual date entry' , ( ) => { const expected = new Date ( 2019 , 11 , 19 ) ; const h = harness ( ( ) => < DateInput name = "dateInput" onValue = { onValue } / > ) ; const toggleOpen = sinon . stub ( ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , toggleOpen ) ; h . expect ( textInputTemplate , ( ) => triggerResult ) ; const [ input ] = select ( '@input' , triggerResult ) ; onValue . resetHistory ( ) ; input . properties . onFocus ( ) ; input . properties . onValue ( formatDate ( expected ) ) ; h . expect ( baseTemplate ( ) ) ; sinon . assert . notCalled ( onValue ) ; input . properties . onBlur ( ) ; h . expect ( baseTemplate ( expected ) ) ; sinon . assert . calledWith ( onValue , formatDateISO ( expected ) ) ; sinon . assert . notCalled ( toggleOpen ) ; } ) ; it ( 'uses custom date parsers' , ( ) => { const expected = new Date ( 2019 , 11 , 19 ) ; const inputString = '2019|12|19' ; const h = harness ( ( ) => ( < DateInput name = "dateInput" onValue = { onValue } dateParser = { customDateParser } dateFormatter = { customDateFormatter } / > ) ) ; const toggleOpen = sinon . stub ( ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , toggleOpen ) ; h . expect ( textInputTemplate , ( ) => triggerResult ) ; const [ input ] = select ( '@input' , triggerResult ) ; onValue . resetHistory ( ) ; input . properties . onFocus ( ) ; input . properties . onValue ( inputString ) ; h . expect ( baseTemplate ( ) ) ; sinon . assert . notCalled ( onValue ) ; input . properties . onBlur ( ) ; h . expect ( baseTemplate ( expected ) ) ; sinon . assert . calledWith ( onValue , formatDateISO ( expected ) ) ; sinon . assert . notCalled ( toggleOpen ) ; } ) ; it ( 'allows date picker entry' , ( ) => { const expected = new Date ( 2019 , 11 , 19 ) ; const h = harness ( ( ) => < DateInput name = "dateInput" onValue = { onValue } / > ) ; h . expect ( baseTemplate ( ) ) ; const onClose = sinon . stub ( ) ; const contentResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . content , onClose ) ; h . expect ( calendarTemplate , ( ) => contentResult ) ; const [ calendar ] = select ( '@calendar' , contentResult ) ; onValue . resetHistory ( ) ; calendar . properties . onValue ( expected ) ; h . expect ( baseTemplate ( expected ) ) ; const [ input ] = select ( '@input' , h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger ) ) ; assert ( input . properties . initialValue , formatDate ( expected ) ) ; sinon . assert . calledWith ( onValue , formatDateISO ( expected ) ) ; sinon . assert . calledOnce ( onClose ) ; } ) ; it ( 'ignores internal value changes when controlled' , ( ) => { const expected = new Date ( 2019 , 11 , 19 ) ; const newDate = new Date ( 2018 , 11 , 19 ) ; const h = harness ( ( ) => ( < DateInput value = { formatDateISO ( expected ) } name = "dateInput" onValue = { onValue } / > ) ) ; h . expect ( baseTemplate ( expected ) ) ; const onClose = sinon . stub ( ) ; const contentResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . content , onClose ) ; h . expect ( calendarTemplate . setProperty ( '@calendar' , 'initialValue' , expected ) , ( ) => contentResult ) ; const [ calendar ] = select ( '@calendar' , contentResult ) ; onValue . resetHistory ( ) ; calendar . properties . onValue ( newDate ) ; h . expect ( baseTemplate ( expected ) ) ; const [ input ] = select ( '@input' , h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger ) ) ; assert ( input . properties . initialValue , formatDate ( expected ) ) ; sinon . assert . calledWith ( onValue , formatDateISO ( newDate ) ) ; sinon . assert . calledOnce ( onClose ) ; } ) ; it ( 'validates date input' , ( ) => { const onValidate = sinon . stub ( ) ; const h = harness ( ( ) => ( < DateInput name = "dateInput" onValue = { onValue } onValidate = { onValidate } / > ) ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , noop ) ; sinon . assert . calledWith ( onValidate , true , '' ) ; let [ input ] = select ( '@input' , triggerResult ) ; onValidate . resetHistory ( ) ; onValue . resetHistory ( ) ; input . properties . onFocus ( ) ; input . properties . onValue ( 'foobar' ) ; input . properties . onBlur ( ) ; h . expect ( baseTemplate ( ) ) ; sinon . assert . notCalled ( onValue ) ; sinon . assert . calledWith ( onValidate , false , messages . invalidDate ) ; [ input ] = select ( '@input' , h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , noop ) ) ; assert . deepEqual ( input . properties . valid , { valid : false , message : messages . invalidDate } ) ; } ) ; it ( 'validates manual date entry range' , ( ) => { const onValidate = sinon . stub ( ) ; const tooEarly = new Date ( 2019 , 10 , 11 ) ; const tooLate = new Date ( 2020 , 0 , 22 ) ; const initialValue = new Date ( 2019 , 11 , 15 ) ; const max = new Date ( 2019 , 11 , 31 ) ; const min = new Date ( 2019 , 11 , 1 ) ; const h = harness ( ( ) => ( < DateInput name = "dateInput" onValue = { onValue } max = { formatDateISO ( max ) } min = { formatDateISO ( min ) } initialValue = { formatDateISO ( initialValue ) } onValidate = { onValidate } / > ) ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , noop ) ; sinon . assert . calledWith ( onValidate , true , '' ) ; onValidate . resetHistory ( ) ; let [ input ] = select ( '@input' , triggerResult ) ; onValue . resetHistory ( ) ; input . properties . onFocus ( ) ; input . properties . onValue ( formatDate ( tooEarly ) ) ; input . properties . onBlur ( ) ; h . expect ( baseTemplate ( initialValue ) ) ; sinon . assert . calledWith ( onValidate , false , messages . tooEarly ) ; sinon . assert . notCalled ( onValue ) ; [ input ] = select ( '@input' , h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , noop ) ) ; assert . deepEqual ( input . properties . valid , { valid : false , message : messages . tooEarly } ) ; onValidate . resetHistory ( ) ; onValue . resetHistory ( ) ; input . properties . onFocus ( ) ; input . properties . onValue ( formatDate ( tooLate ) ) ; input . properties . onBlur ( ) ; h . expect ( baseTemplate ( initialValue ) ) ; [ input ] = select ( '@input' , h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , noop ) ) ; assert . deepEqual ( input . properties . valid , { valid : false , message : messages . tooLate } ) ; sinon . assert . calledWith ( onValidate , false , messages . tooLate ) ; } ) ; it ( 'validates range inputs' , ( ) => { const onValidate = sinon . stub ( ) ; const max = new Date ( 2019 , 11 , 1 ) ; const min = new Date ( 2019 , 11 , 31 ) ; const h = harness ( ( ) => ( < DateInput name = "dateInput" onValue = { onValue } max = { formatDateISO ( max ) } min = { formatDateISO ( min ) } onValidate = { onValidate } / > ) ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , noop ) ; h . expect ( textInputTemplate . setProperty ( '@input' , 'valid' , { valid : false , message : messages . invalidProps } ) , ( ) => triggerResult ) ; sinon . assert . calledWith ( onValidate , false , messages . invalidProps ) ; } ) ; it ( 'requires date input' , ( ) => { const onValidate = sinon . stub ( ) ; const h = harness ( ( ) => ( < DateInput name = "dateInput" onValue = { onValue } onValidate = { onValidate } required = { true } / > ) ) ; const toggleOpen = sinon . stub ( ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , toggleOpen ) ; h . expect ( textInputTemplate . setProperty ( '@input' , 'required' , true ) , ( ) => triggerResult ) ; const [ input ] = select ( '@input' , triggerResult ) ; onValue . resetHistory ( ) ; input . properties . onValue ( '' ) ; h . expect ( baseTemplate ( ) . setProperty ( '@input' , 'required' , true ) ) ; input . properties . onBlur ( ) ; h . expect ( baseTemplate ( ) . setProperty ( '@input' , 'required' , true ) ) ; sinon . assert . calledWith ( onValidate , false , messages . requiredDate ) ; } ) ; it ( 'renders as outlined date input' , ( ) => { const h = harness ( ( ) => < DateInput name = "dateInput" kind = "outlined" / > ) ; const toggleOpen = sinon . stub ( ) ; const triggerResult = h . trigger ( '@popup' , ( node ) => ( node . children as any ) [ 0 ] . trigger , toggleOpen ) ; h . expect ( textInputTemplate . setProperty ( '@input' , 'kind' , 'outlined' ) , ( ) => triggerResult ) ; } ) ; } ) ;