import { createHostFactory , SpectatorHost } from '@ngneat/spectator' ; import { DesignTokenHelper } from '@kirbydesign/core' ; import { PlatformService } from '../../helpers' ; import { TestHelper } from '../../testing/test-helper' ; import { WindowRef } from '../../types' ; import { ItemComponent } from '../item/item.component' ; import { RadioComponent } from '../radio' ; import { RadioGroupComponent } from '../radio/radio-group/radio-group.component' ; import { FormFieldMessageComponent } from './form-field-message/form-field-message.component' ; import { FormFieldComponent } from './form-field.component' ; import { InputCounterComponent } from './input-counter/input-counter.component' ; import { InputComponent } from './input/input.component' ; import { TextareaComponent } from './textarea/textarea.component' ; const { size , fontSize , fontWeight , lineHeight , getElevation } = DesignTokenHelper ; describe ( 'FormFieldComponent' , ( ) => { let spectator ; const createHost = createHostFactory ( { component : FormFieldComponent , declarations : [ FormFieldMessageComponent , InputComponent , TextareaComponent , RadioGroupComponent , RadioComponent , InputCounterComponent , ItemComponent , ] , mocks : [ PlatformService ] , providers : [ { provide : WindowRef , useValue : < WindowRef > { nativeWindow : window } , } , ] , } ) ; describe ( 'by default' , ( ) => { beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field></kirby-form-field>` ) ; } ) ; it ( 'should create' , ( ) => { expect ( spectator . component ) . toBeTruthy ( ) ; } ) ; it ( 'should have correct margin' , ( ) => { expect ( spectator . element ) . toHaveComputedStyle ( { 'margin-bottom' : size ( 's' ) } ) ; } ) ; } ) ; describe ( 'without label' , ( ) => { it ( 'should not render the label' , ( ) => { spectator = createHost ( `<kirby-form-field></kirby-form-field>` ) ; const labelElement = spectator . queryHost ( 'label' ) ; expect ( labelElement ) . toBeNull ( ) ; } ) ; } ) ; describe ( 'when disabled' , ( ) => { it ( 'should not have elevation' , ( ) => { spectator = createHost ( `<kirby-form-field>
        <input kirby-input disabled value="Disabled input" />
      </kirby-form-field>` ) ; const inputElement = spectator . queryHost ( 'input[kirby-input]' ) ; expect ( inputElement ) . toHaveComputedStyle ( { 'box-shadow' : 'none' } ) ; } ) ; } ) ; describe ( 'with label' , ( ) => { let labelElement ; let labelTextElement ; beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field label="Form field with label">
          <input kirby-input />
         </kirby-form-field>` ) ; labelElement = spectator . queryHost ( 'label' ) ; labelTextElement = spectator . queryHost ( 'label .text' ) ; } ) ; it ( 'should render the label' , ( ) => { expect ( labelElement ) . toBeTruthy ( ) ; } ) ; it ( 'should render the label with correct typography' , ( ) => { expect ( labelTextElement ) . toHaveComputedStyle ( { 'font-size' : fontSize ( 's' ) , 'font-weight' : fontWeight ( 'light' ) , 'line-height' : lineHeight ( 's' ) , } ) ; } ) ; it ( 'should render the label with correct padding' , ( ) => { expect ( labelTextElement ) . toHaveComputedStyle ( { 'padding-top' : '0px' , 'padding-left' : size ( 's' ) , 'padding-right' : size ( 's' ) , 'padding-bottom' : '0px' , } ) ; } ) ; it ( 'should render the label with correct margin' , ( ) => { expect ( labelTextElement ) . toHaveComputedStyle ( { 'margin-bottom' : size ( 'xxxs' ) , } ) ; } ) ; } ) ; describe ( 'without message or counter' , ( ) => { it ( 'should not render the message wrapper' , ( ) => { spectator = createHost ( `<kirby-form-field></kirby-form-field>` ) ; const messageWrapperElement = spectator . queryHost ( '.texts' ) ; expect ( messageWrapperElement ) . toBeNull ( ) ; } ) ; } ) ; describe ( 'with message' , ( ) => { it ( 'should render the message wrapper with the correct padding' , ( ) => { spectator = createHost ( `<kirby-form-field message="This is additional info that will be shown below the input">
           <input kirby-input />
         </kirby-form-field>` ) ; const messageWrapperElement = spectator . queryHost ( '.texts' ) ; expect ( messageWrapperElement ) . toHaveComputedStyle ( { 'padding-top' : '2px' , 'padding-left' : size ( 's' ) , 'padding-right' : size ( 's' ) , 'padding-bottom' : '0px' , } ) ; } ) ; const getAvailableTextWidth = ( ) => { const componentWidth = spectator . element . getBoundingClientRect ( ) . width ; const textWrapperElement = spectator . queryHost ( '.texts' ) ; const paddingLeft = parseInt ( TestHelper . getCssProperty ( textWrapperElement , 'padding-left' ) ) ; const paddingRight = parseInt ( TestHelper . getCssProperty ( textWrapperElement , 'padding-right' ) ) ; const availableTextWidth = componentWidth - paddingLeft - paddingRight ; return availableTextWidth ; } ; describe ( 'when having no counter' , ( ) => { let messageElement ; beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field message="This is additional info that will be shown below the input">
             <input kirby-input />
           </kirby-form-field>` ) ; messageElement = spectator . queryHost ( 'kirby-form-field-message' ) ; } ) ; it ( 'should render the message full width' , ( ) => { const messageWidth = messageElement . getBoundingClientRect ( ) . width ; const availableTextWidth = getAvailableTextWidth ( ) ; expect ( messageWidth ) . toEqual ( availableTextWidth ) ; } ) ; } ) ; describe ( 'when having a counter' , ( ) => { let messageElement ; let counterWrapperElement ; beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field message="This is additional info that will be shown below the input">
             <input kirby-input #message/>
             <kirby-input-counter [listenTo]="message"></kirby-input-counter>
           </kirby-form-field>` ) ; messageElement = spectator . queryHost ( 'kirby-form-field-message' ) ; counterWrapperElement = spectator . queryHost ( '.counter' ) ; } ) ; it ( 'should render the message with correct width' , ( ) => { const availableTextWidth = getAvailableTextWidth ( ) ; const expectedMessageWidth = availableTextWidth * 0.75 ; const messageWidth = messageElement . getBoundingClientRect ( ) . width ; expect ( messageWidth ) . toEqual ( expectedMessageWidth ) ; } ) ; it ( 'should render the counter with correct width' , ( ) => { const availableTextWidth = getAvailableTextWidth ( ) ; const expectedCounterWidth = availableTextWidth * 0.25 ; const counterWidth = counterWrapperElement . getBoundingClientRect ( ) . width ; expect ( counterWidth ) . toEqual ( expectedCounterWidth ) ; } ) ; } ) ; } ) ; describe ( 'with slotted input' , ( ) => { it ( 'should render the input with elevation' , ( ) => { spectator = createHost ( `<kirby-form-field>
          <input kirby-input />
        </kirby-form-field>` ) ; const inputElement = spectator . queryHost ( 'input[kirby-input]' ) ; expect ( inputElement ) . toHaveComputedStyle ( { 'box-shadow' : getElevation ( 2 ) } ) ; } ) ; describe ( 'and no label' , ( ) => { let dispatchEventSpy ; beforeEach ( ( ) => { dispatchEventSpy = spyOn ( document , 'dispatchEvent' ) ; spectator = createHost ( `<kirby-form-field>
            <input kirby-input [readonly]="readonly" />
          </kirby-form-field>` , { detectChanges : false , hostProps : { readonly : false } } ) ; spectator . detectChanges ( ) ; } ) ; it ( 'should render the input' , ( ) => { const inputElement = spectator . queryHost ( 'input[kirby-input]' ) ; expect ( inputElement ) . toBeTruthy ( ) ; } ) ; it ( 'should render the input as a direct descendant' , ( ) => { const inputElement = spectator . queryHost ( 'input[kirby-input]' ) ; expect ( inputElement ) . toBeTruthy ( ) ; expect ( inputElement . parentElement ) . toEqual ( spectator . element ) ; } ) ; it ( 'should not render the input within a label' , ( ) => { const inputElement = spectator . queryHost ( 'label input[kirby-input]' ) ; expect ( inputElement ) . toBeNull ( ) ; } ) ; it ( 'should register shims' , ( ) => { spectator . setHostInput ( { readonly : false } ) ; spectator . detectChanges ( ) ; expect ( dispatchEventSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( dispatchEventSpy ) . toHaveBeenCalledWith ( new CustomEvent ( 'ionInputDidLoad' , { detail : spectator . element , } ) ) ; } ) ; it ( 'should NOT register shims if readonly' , ( ) => { spectator . setHostInput ( { readonly : true } ) ; spectator . detectChanges ( ) ; expect ( dispatchEventSpy ) . toHaveBeenCalledTimes ( 0 ) ; } ) ; it ( 'should register shims if changing from readonly to not readonly' , ( ) => { spectator . setHostInput ( { readonly : true } ) ; spectator . detectChanges ( ) ; expect ( dispatchEventSpy ) . toHaveBeenCalledTimes ( 0 ) ; spectator . setHostInput ( { readonly : false } ) ; spectator . detectChanges ( ) ; expect ( dispatchEventSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( dispatchEventSpy ) . toHaveBeenCalledWith ( new CustomEvent ( 'ionInputDidLoad' , { detail : spectator . element , } ) ) ; } ) ; it ( 'should dispatch `ionInputDidUnload` event on destroy' , ( ) => { spectator . fixture . destroy ( ) ; const event = dispatchEventSpy . calls . mostRecent ( ) . args [ 0 ] ; expect ( event ) . toBeInstanceOf ( CustomEvent ) ; expect ( event . type ) . toBe ( 'ionInputDidUnload' ) ; expect ( ( event as CustomEvent ) . detail ) . toEqual ( spectator . element ) ; } ) ; } ) ; describe ( 'and a label' , ( ) => { beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field label="Input with label">
             <input kirby-input/>
           </kirby-form-field>` ) ; } ) ; it ( 'should render the input' , ( ) => { const inputElement = spectator . queryHost ( 'input[kirby-input]' ) ; expect ( inputElement ) . toBeTruthy ( ) ; } ) ; it ( 'should render the input within a label' , ( ) => { const inputElement = spectator . queryHost ( 'label input[kirby-input]' ) ; expect ( inputElement ) . toBeTruthy ( ) ; } ) ; } ) ; } ) ; describe ( 'with slotted textarea' , ( ) => { describe ( 'and no label' , ( ) => { beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field>
             <textarea kirby-textarea></textarea>
           <kirby-form-field>` ) ; } ) ; it ( 'should render the textarea' , ( ) => { const textareaElement = spectator . queryHost ( 'textarea[kirby-textarea]' ) ; expect ( textareaElement ) . toBeTruthy ( ) ; } ) ; it ( 'should render the textarea as a direct descendant' , ( ) => { const textareaElement = spectator . queryHost ( 'textarea[kirby-textarea]' ) ; expect ( textareaElement ) . toBeTruthy ( ) ; expect ( textareaElement . parentElement ) . toEqual ( spectator . element ) ; } ) ; it ( 'should not render the textarea within a label' , ( ) => { const textareaElement = spectator . queryHost ( 'label textarea[kirby-textarea]' ) ; expect ( textareaElement ) . toBeNull ( ) ; } ) ; it ( 'should render the textarea with elevation' , ( ) => { const textareaElement = spectator . queryHost ( 'textarea[kirby-textarea]' ) ; expect ( textareaElement ) . toHaveComputedStyle ( { 'box-shadow' : getElevation ( 2 ) } ) ; } ) ; } ) ; describe ( 'and a label' , ( ) => { beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field label="Textarea with label">
             <textarea kirby-textarea></textarea>
           </kirby-form-field>` ) ; } ) ; it ( 'should render the textarea' , ( ) => { const textareaElement = spectator . queryHost ( 'textarea[kirby-textarea]' ) ; expect ( textareaElement ) . toBeTruthy ( ) ; } ) ; it ( 'should render the textarea within a label' , ( ) => { const textareaElement = spectator . queryHost ( 'label textarea[kirby-textarea]' ) ; expect ( textareaElement ) . toBeTruthy ( ) ; } ) ; } ) ; } ) ; describe ( 'with slotted radio-group' , ( ) => { let radioGroupElement ; let label ; describe ( 'and no label' , ( ) => { beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field>
             <kirby-radio-group
               [items]="['Test 1','Test 2','Test 3']">
             </kirby-radio-group>
           <kirby-form-field>` ) ; radioGroupElement = spectator . queryHost ( 'kirby-radio-group' ) ; label = spectator . queryHost ( 'label' ) ; } ) ; it ( 'should render the radio-group as a direct descendant' , ( ) => { expect ( radioGroupElement ) . toBeTruthy ( ) ; expect ( radioGroupElement . parentElement ) . toEqual ( spectator . element ) ; } ) ; it ( 'should not render the radio-group within a label' , ( ) => { const radioGroupInLabel = spectator . queryHost ( 'label kirby-radio-group' ) ; expect ( radioGroupInLabel ) . toBeNull ( ) ; } ) ; it ( 'should not render a label' , ( ) => { expect ( label ) . toBeNull ( ) ; } ) ; } ) ; describe ( 'and a label' , ( ) => { beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field label="Radio group with label">
            <kirby-radio-group
              [items]="['Test 1','Test 2','Test 3']">
            </kirby-radio-group>
          <kirby-form-field>` ) ; radioGroupElement = spectator . queryHost ( 'kirby-radio-group' ) ; label = spectator . queryHost ( 'label' ) ; } ) ; it ( 'should render the radio-group' , ( ) => { expect ( radioGroupElement ) . toBeTruthy ( ) ; } ) ; it ( 'should not render the radio-group within a label' , ( ) => { const radioGroupInLabel = spectator . queryHost ( 'label kirby-radio-group' ) ; expect ( radioGroupInLabel ) . toBeNull ( ) ; } ) ; it ( 'should render a label' , ( ) => { expect ( label ) . toBeTruthy ( ) ; } ) ; it ( 'should associate the label with the radio group' , ( ) => { expect ( radioGroupElement . getAttribute ( 'aria-labelledby' ) ) . toEqual ( label . id ) ; } ) ; it ( 'should focus the the radio group when clicking the label ' , ( ) => { const radioGroupComponent = spectator . query ( RadioGroupComponent ) ; const focusSpy = spyOn ( radioGroupComponent , 'focus' ) ; spectator . click ( label ) ; expect ( focusSpy ) . toHaveBeenCalled ( ) ; } ) ; } ) ; } ) ; describe ( 'When nested inside a kirby-item' , ( ) => { describe ( 'by default' , ( ) => { beforeEach ( ( ) => { spectator = createHost ( `<kirby-item>
            <kirby-form-field>
              <input kirby-input />
            </kirby-form-field>
          </kirby-item>` ) ; } ) ; it ( 'should render with no bottom margin' , ( ) => { const formFieldElement = spectator . queryHost ( 'kirby-form-field' ) ; expect ( formFieldElement ) . toHaveComputedStyle ( { 'margin-bottom' : '0px' , } ) ; } ) ; } ) ; describe ( 'and slotted end' , ( ) => { beforeEach ( ( ) => { spectator = createHost ( `<kirby-item>
            <kirby-form-field slot="end">
              <input kirby-input type="number"/>
            </kirby-form-field>
          </kirby-item>` ) ; } ) ; it ( 'should render the input with correct text alignment' , ( ) => { const formFieldElement = spectator . queryHost ( 'input[kirby-input]' ) ; expect ( formFieldElement ) . toHaveComputedStyle ( { 'text-align' : 'right' , } ) ; } ) ; describe ( 'when input is type number' , ( ) => { it ( 'should render the input with correct font weight' , ( ) => { const formFieldElement = spectator . queryHost ( 'input[kirby-input]' ) ; expect ( formFieldElement ) . toHaveComputedStyle ( { 'font-weight' : fontWeight ( 'bold' ) , } ) ; } ) ; } ) ; } ) ; } ) ; describe ( 'focus' , ( ) => { let platformServiceSpy ; beforeEach ( ( ) => { spectator = createHost ( `<kirby-form-field>
        <input kirby-input />
      </kirby-form-field>` , { detectChanges : false } ) ; platformServiceSpy = spectator . inject ( PlatformService ) ; } ) ; it ( 'should focus input element if not touch' , ( ) => { platformServiceSpy . isTouch . and . returnValue ( false ) ; spectator . detectChanges ( ) ; spectator . detectChanges ( ) ; const formFieldElement = spectator . queryHost < HTMLInputElement > ( 'input[kirby-input]' ) ; const focusSpy = spyOn ( formFieldElement , 'focus' ) ; spectator . component . focus ( ) ; expect ( focusSpy ) . toHaveBeenCalled ( ) ; } ) ; it ( 'should dispatch touch events if touch' , ( ) => { platformServiceSpy . isTouch . and . returnValue ( true ) ; spectator . detectChanges ( ) ; spectator . detectChanges ( ) ; const inputElement = spectator . queryHost < HTMLInputElement > ( 'input[kirby-input]' ) ; const dispatchEventSpy = spyOn ( inputElement , 'dispatchEvent' ) ; spectator . component . focus ( ) ; expect ( dispatchEventSpy ) . toHaveBeenCalledTimes ( 2 ) ; const firstEvent = dispatchEventSpy . calls . argsFor ( 0 ) [ 0 ] ; expect ( firstEvent ) . toBeInstanceOf ( TouchEvent ) ; expect ( firstEvent . type ) . toBe ( 'touchstart' ) ; const secondEvent = dispatchEventSpy . calls . argsFor ( 1 ) [ 0 ] ; expect ( secondEvent ) . toBeInstanceOf ( TouchEvent ) ; expect ( secondEvent . type ) . toBe ( 'touchend' ) ; } ) ; } ) ; } ) ;