diff --git a/src/demo-app/components/dialog-demo/dialog-demo.component.ts b/src/demo-app/components/dialog-demo/dialog-demo.component.ts index a6d749b1f..f71db3a47 100644 --- a/src/demo-app/components/dialog-demo/dialog-demo.component.ts +++ b/src/demo-app/components/dialog-demo/dialog-demo.component.ts @@ -46,13 +46,13 @@ export class DialogDemoComponent { showDialogForm() { // reset error state - this.input.updateErrorState(true); + this.input.setValid(true); this.dialogForm.show(); } updateForm() { // reset error state - this.input.updateErrorState(false); + this.input.setValid(false); if (!this.userForm.valid) { return; } diff --git a/src/demo-app/components/textfield-demo/textfield-demo.component.html b/src/demo-app/components/textfield-demo/textfield-demo.component.html index be1b01f47..da79e199c 100644 --- a/src/demo-app/components/textfield-demo/textfield-demo.component.html +++ b/src/demo-app/components/textfield-demo/textfield-demo.component.html @@ -30,55 +30,55 @@

Text Field

- @Input() id: string + id: string Unique id of the element. - @Input() type: string = 'text' + type: string = 'text' Input type of textfield (e.g. email, password, url). - @Input() name: string + name: string Name of the textfield. - @Input() value: string + value: string The input element's value. - @Input() dense: boolean + dense: boolean Shrinks the font size and height of the input. - @Input() fullwidth: boolean + fullwidth: boolean Use to change element to fullwidth textfield. Not usable with mdc-textfield-box. - @Input() multiline: boolean + multiline: boolean Use to allow multiple lines inside the textfield. Not usable with mdc-textfield-box. - @Input() disabled: boolean + disabled: boolean Disables the component. - @Input() required: boolean + required: boolean Whether the element is required. - @Input() label: string + label: string Shown to the user when there's no focus or values. - @Input() placeholder: string + placeholder: string Placeholder attribute of the element. - @Input() tabindex: number + tabindex: number Tab index of the text element. - @Input() rows: number + rows: number Number of rows for this textarea. Not usable with mdc-textfield-box. @@ -98,8 +98,8 @@

Text Field

- updateErrorState(value?: boolean) - Updates the error state either by value passed, or by checking the validity of the input. + setValid(value?: boolean) + Updates input validity using passed value, or if left empty checking the validity of the input. diff --git a/src/lib/textfield/textfield.component.ts b/src/lib/textfield/textfield.component.ts index f352860ab..be6e66968 100644 --- a/src/lib/textfield/textfield.component.ts +++ b/src/lib/textfield/textfield.component.ts @@ -96,8 +96,8 @@ export class MdcTextfieldLabelDirective { }) export class MdcTextfieldComponent implements AfterViewInit, OnDestroy, ControlValueAccessor { private type_ = 'text'; - private disabled_ = false; - private required_ = false; + private disabled_: boolean = false; + private required_: boolean = false; private controlValueAccessorChangeFn_: (value: any) => void = (value) => { }; onChange = (_: any) => { }; onTouched = () => { }; @@ -121,7 +121,7 @@ export class MdcTextfieldComponent implements AfterViewInit, OnDestroy, ControlV this.required_ = value != null && `${value}` !== 'false'; } @Input() - get type() { return this.type_; } + get type(): string { return this.type_; } set type(value: string) { this.type_ = value || 'text'; this.validateType_(); @@ -131,7 +131,7 @@ export class MdcTextfieldComponent implements AfterViewInit, OnDestroy, ControlV } } @Input() - get value() { return this.inputText.elementRef.nativeElement.value; } + get value(): string { return this.inputText.elementRef.nativeElement.value; } set value(value: string) { if (value !== this.value) { this.inputText.elementRef.nativeElement.value = value; @@ -234,6 +234,7 @@ export class MdcTextfieldComponent implements AfterViewInit, OnDestroy, ControlV destroy: Function, isDisabled: Function, setDisabled: Function, + setValid: Function, } = new MDCTextfieldFoundation(this._mdcAdapter); constructor( @@ -280,32 +281,24 @@ export class MdcTextfieldComponent implements AfterViewInit, OnDestroy, ControlV this.onTouched(); } - isDisabled() { + isDisabled(): boolean { return this._foundation.isDisabled(); } - isBadInput() { + isBadInput(): boolean { return (this.inputText.elementRef.nativeElement as HTMLInputElement).validity.badInput; } - focus() { + focus(): void { this.inputText.focus(); } - updateErrorState(value?: boolean) { - if (value || this.valid) { - this._mdcAdapter.removeClass('mdc-textfield--invalid'); - } else { - this._mdcAdapter.addClass('mdc-textfield--invalid'); - } + setValid(value?: boolean): void { + this._foundation.setValid(value ? value : this.valid); } private isTextarea_() { let nativeElement = this._root.nativeElement; - - // In Universal, we don't have access to `nodeName`, but the same can be achieved with `name`. - // Note that this shouldn't be necessary once Angular switches to an API that resembles the - // DOM closer. let nodeName = isBrowser ? nativeElement.nodeName : nativeElement.name; return nodeName ? nodeName.toLowerCase() === 'textarea' : false; } diff --git a/test/unit/textfield/textarea.test.ts b/test/unit/textfield/textarea.test.ts new file mode 100644 index 000000000..ed0aa7a84 --- /dev/null +++ b/test/unit/textfield/textarea.test.ts @@ -0,0 +1,70 @@ +import { Component, DebugElement } from '@angular/core'; +import { async, ComponentFixture, fakeAsync, flushMicrotasks, TestBed } from '@angular/core/testing'; +import { FormControl, FormsModule, NgModel, ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; + +import { + MdcTextareaComponent, + MdcTextfieldModule +} from '../../../src/lib/public_api'; + +describe('MdcTextareaComponent', () => { + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdcTextfieldModule, FormsModule, ReactiveFormsModule], + declarations: [ + SimpleTextfield, + ] + }); + TestBed.compileComponents(); + })); + + describe('basic behaviors', () => { + let textFieldDebugElement: DebugElement; + let textFieldNativeElement: HTMLElement; + let textFieldInstance: MdcTextareaComponent; + let testComponent: SimpleTextfield; + let inputElement: HTMLInputElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleTextfield); + fixture.detectChanges(); + + textFieldDebugElement = fixture.debugElement.query(By.directive(MdcTextareaComponent)); + textFieldNativeElement = textFieldDebugElement.nativeElement; + textFieldInstance = textFieldDebugElement.componentInstance; + testComponent = fixture.debugElement.componentInstance; + }); + + it('#should apply class multiline on property', () => { + testComponent.isMultiline = true; + fixture.detectChanges(); + expect(textFieldDebugElement.nativeElement.classList.contains('mdc-textfield--multiline')).toBe(true); + }); + }); +}); + +/** Simple component for testing. */ +@Component({ + template: + ` + + +

Comments are required

+ `, +}) +class SimpleTextfield { + username: string = ''; + isDisabled: boolean = false; + isRequired: boolean = false; + isMultiline: boolean = false; +} \ No newline at end of file diff --git a/test/unit/textfield/textfield-box.test.ts b/test/unit/textfield/textfield-box.test.ts new file mode 100644 index 000000000..f2e11d935 --- /dev/null +++ b/test/unit/textfield/textfield-box.test.ts @@ -0,0 +1,76 @@ +import { Component, DebugElement } from '@angular/core'; +import { async, ComponentFixture, fakeAsync, flushMicrotasks, TestBed } from '@angular/core/testing'; +import { FormControl, FormsModule, NgModel, ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; + +import { + MdcTextfieldBoxComponent, + MdcTextfieldInputDirective, + MdcTextfieldModule +} from '../../../src/lib/public_api'; + +describe('MdcTextfieldBoxComponent', () => { + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdcTextfieldModule, FormsModule, ReactiveFormsModule], + declarations: [ + SimpleTextfield, + ] + }); + TestBed.compileComponents(); + })); + + describe('basic behaviors', () => { + let textFieldDebugElement: DebugElement; + let textFieldNativeElement: HTMLElement; + let textFieldInstance: MdcTextfieldBoxComponent; + let testComponent: SimpleTextfield; + let inputElement: HTMLInputElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleTextfield); + fixture.detectChanges(); + + textFieldDebugElement = fixture.debugElement.query(By.directive(MdcTextfieldBoxComponent)); + textFieldNativeElement = textFieldDebugElement.nativeElement; + textFieldInstance = textFieldDebugElement.componentInstance; + testComponent = fixture.debugElement.componentInstance; + inputElement = textFieldInstance.inputText.elementRef.nativeElement; + }); + + it('#should have mdc-textfield--box by default', () => { + expect(textFieldDebugElement.nativeElement.classList) + .toContain('mdc-textfield--box', 'Expected to have mdc-textfield--box class'); + }); + + it('#should preserve the user-provided id', () => { + fixture.detectChanges(); + expect(inputElement.id).toBe('simple-check'); + }); + }); +}); + +/** Simple component for testing. */ +@Component({ + template: + ` + + +

Username is required

+ `, +}) +class SimpleTextfield { + boxId: string = 'simple-check'; + username: string = ''; + isDisabled: boolean = false; + isRequired: boolean = false; +} diff --git a/test/unit/textfield/textfield.test.ts b/test/unit/textfield/textfield.test.ts new file mode 100644 index 000000000..99d2788a1 --- /dev/null +++ b/test/unit/textfield/textfield.test.ts @@ -0,0 +1,111 @@ +import { Component, DebugElement } from '@angular/core'; +import { async, ComponentFixture, fakeAsync, flushMicrotasks, TestBed } from '@angular/core/testing'; +import { FormControl, FormsModule, NgModel, ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; + +import { + MdcTextfieldComponent, + MdcTextfieldModule +} from '../../../src/lib/public_api'; + +describe('MdcTextfieldComponent', () => { + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdcTextfieldModule, FormsModule, ReactiveFormsModule], + declarations: [ + SimpleTextfield, + ] + }); + TestBed.compileComponents(); + })); + + describe('basic behaviors', () => { + let textFieldDebugElement: DebugElement; + let textFieldNativeElement: HTMLElement; + let textFieldInstance: MdcTextfieldComponent; + let testComponent: SimpleTextfield; + let inputElement: HTMLInputElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleTextfield); + fixture.detectChanges(); + + textFieldDebugElement = fixture.debugElement.query(By.directive(MdcTextfieldComponent)); + textFieldNativeElement = textFieldDebugElement.nativeElement; + textFieldInstance = textFieldDebugElement.componentInstance; + testComponent = fixture.debugElement.componentInstance; + }); + + it('#should have mdc-textfield by default', () => { + expect(textFieldDebugElement.nativeElement.classList) + .toContain('mdc-textfield', 'Expected to have mdc-textfield class'); + }); + + it('#should apply class dense on property', () => { + testComponent.isDense = true; + fixture.detectChanges(); + expect(textFieldDebugElement.nativeElement.classList.contains('mdc-textfield--dense')).toBe(true); + }); + + it('#should apply class fullwidth on property', () => { + testComponent.isFullwidth = true; + fixture.detectChanges(); + expect(textFieldDebugElement.nativeElement.classList.contains('mdc-textfield--fullwidth')).toBe(true); + }); + + it('#should remove invalid styling', () => { + fixture.detectChanges(); + textFieldInstance.setValid(false); + expect(textFieldDebugElement.nativeElement.classList.contains('mdc-textfield--invalid')).toBe(false); + }); + + it('#should set validity based on input element validity', () => { + textFieldInstance.setValid(); + fixture.detectChanges(); + expect(textFieldDebugElement.nativeElement.classList.contains('mdc-textfield--invalid')).toBe(false); + }); + + it('#should focus on underlying input element when focus() is called', () => { + expect(document.activeElement).not.toBe(inputElement); + textFieldInstance.focus(); + fixture.detectChanges(); + + expect(document.activeElement).toBe(textFieldInstance.inputText.elementRef.nativeElement); + }); + + it('#should throw an error', () => { + expect(() => { + testComponent.myType = 'button'; fixture.detectChanges() + }).toThrowError('Input type "button" is not supported.'); + }); + }); +}); + +/** Simple component for testing. */ +@Component({ + template: + ` + + +

Username is required

+ `, +}) +class SimpleTextfield { + username: string = ''; + myType: string = 'text'; + isDisabled: boolean = false; + isDense: boolean = false; + isFullwidth: boolean = false; + isRequired: boolean = false; +}