test(select/segment): adds unit test for select and segment

This commit is contained in:
Manuel Mtz-Almeida
2017-03-28 17:57:16 +02:00
parent 9be5751eeb
commit 3d569eb88a
20 changed files with 197 additions and 112 deletions

View File

@ -58,13 +58,13 @@ export class PageOne {
buttons: [ buttons: [
{ {
text: 'Cancel', text: 'Cancel',
handler: (data: any) => { handler: (data) => {
console.log('Cancel clicked'); console.log('Cancel clicked');
} }
}, },
{ {
text: 'Save', text: 'Save',
handler: (data: any) => { handler: (data) => {
console.log('Saved clicked'); console.log('Saved clicked');
} }
} }

View File

@ -28,5 +28,5 @@ export interface AlertButton {
text?: string; text?: string;
role?: string; role?: string;
cssClass?: string; cssClass?: string;
handler?: Function; handler?: (value: any) => boolean|void;
}; };

View File

@ -11,9 +11,7 @@ import { PageOneModule } from '../pages/page-one/page-one.module';
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
IonicModule.forRoot(AppComponent, { IonicModule.forRoot(AppComponent, {}),
mode: 'ios'
}),
PageOneModule PageOneModule
], ],
bootstrap: [IonicApp] bootstrap: [IonicApp]

View File

@ -94,8 +94,7 @@ export class Checkbox extends BaseInput<boolean> implements IonicTapInput, After
renderer: Renderer, renderer: Renderer,
private _cd: ChangeDetectorRef private _cd: ChangeDetectorRef
) { ) {
super(config, elementRef, renderer, 'checkbox', form, item, null); super(config, elementRef, renderer, 'checkbox', false, form, item, null);
this._value = false;
} }
/** /**
@ -110,7 +109,6 @@ export class Checkbox extends BaseInput<boolean> implements IonicTapInput, After
*/ */
@HostListener('click', ['$event']) @HostListener('click', ['$event'])
_click(ev: UIEvent) { _click(ev: UIEvent) {
console.debug('checkbox, checked');
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.value = !this.value; this.value = !this.value;
@ -122,6 +120,7 @@ export class Checkbox extends BaseInput<boolean> implements IonicTapInput, After
_inputNormalize(val: any): boolean { _inputNormalize(val: any): boolean {
return isTrueProperty(val); return isTrueProperty(val);
} }
/** /**
* @hidden * @hidden
*/ */
@ -129,11 +128,4 @@ export class Checkbox extends BaseInput<boolean> implements IonicTapInput, After
this._item && this._item.setElementClass('item-checkbox-checked', val); this._item && this._item.setElementClass('item-checkbox-checked', val);
} }
/**
* @hidden
*/
_inputUpdated() {
this._cd.detectChanges();
}
} }

View File

@ -273,12 +273,11 @@ export const DATETIME_VALUE_ACCESSOR: any = {
providers: [DATETIME_VALUE_ACCESSOR], providers: [DATETIME_VALUE_ACCESSOR],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
}) })
export class DateTime extends BaseInput<any> implements AfterContentInit, ControlValueAccessor, OnDestroy { export class DateTime extends BaseInput<DateTimeData> implements AfterContentInit, ControlValueAccessor, OnDestroy {
_text: string = ''; _text: string = '';
_min: DateTimeData; _min: DateTimeData;
_max: DateTimeData; _max: DateTimeData;
_timeValue: DateTimeData = {};
_locale: LocaleData = {}; _locale: LocaleData = {};
_picker: Picker; _picker: Picker;
@ -426,7 +425,7 @@ export class DateTime extends BaseInput<any> implements AfterContentInit, Contro
@Optional() item: Item, @Optional() item: Item,
@Optional() private _pickerCtrl: PickerController @Optional() private _pickerCtrl: PickerController
) { ) {
super(config, elementRef, renderer, 'datetime', form, item, null); super(config, elementRef, renderer, 'datetime', {}, form, item, null);
} }
/** /**
@ -448,10 +447,17 @@ export class DateTime extends BaseInput<any> implements AfterContentInit, Contro
* @hidden * @hidden
*/ */
_inputUpdated() { _inputUpdated() {
updateDate(this._timeValue, this.value);
this.updateText(); this.updateText();
} }
/**
* @hidden
*/
_inputNormalize(val: any): DateTimeData {
updateDate(this._value, val);
return this._value;
}
/** /**
* @hidden * @hidden
*/ */
@ -743,7 +749,7 @@ export class DateTime extends BaseInput<any> implements AfterContentInit, Contro
* @hidden * @hidden
*/ */
getValue(): DateTimeData { getValue(): DateTimeData {
return this._timeValue; return this._value;
} }
/** /**

View File

@ -604,10 +604,6 @@ describe('DateTime', () => {
datetime.setValue(null); datetime.setValue(null);
expect(datetime.getValue()).toEqual({}); expect(datetime.getValue()).toEqual({});
datetime.setValue('1994-12-15T13:47:20.789Z');
datetime.setValue(undefined);
expect(datetime.getValue()).toEqual({});
datetime.setValue('1994-12-15T13:47:20.789Z'); datetime.setValue('1994-12-15T13:47:20.789Z');
datetime.setValue(''); datetime.setValue('');
expect(datetime.getValue()).toEqual({}); expect(datetime.getValue()).toEqual({});

View File

@ -132,7 +132,7 @@ export class TextInput extends BaseInput<string> implements IonicFormInput {
@Optional() public ngControl: NgControl, @Optional() public ngControl: NgControl,
private _dom: DomController private _dom: DomController
) { ) {
super(config, elementRef, renderer, 'input', form, item, ngControl); super(config, elementRef, renderer, 'input', '', form, item, ngControl);
this._nav = <NavControllerBase>nav; this._nav = <NavControllerBase>nav;

View File

@ -10,7 +10,6 @@ import { BaseInput } from '../../util/base-input';
import { Item } from '../item/item'; import { Item } from '../item/item';
import { Platform } from '../../platform/platform'; import { Platform } from '../../platform/platform';
import { PointerCoordinates, pointerCoord } from '../../util/dom'; import { PointerCoordinates, pointerCoord } from '../../util/dom';
import { TimeoutDebouncer } from '../../util/debouncer';
import { UIEventManager } from '../../gestures/ui-event-manager'; import { UIEventManager } from '../../gestures/ui-event-manager';
@ -139,7 +138,6 @@ export class Range extends BaseInput<any> implements AfterViewInit, ControlValue
_barL: string; _barL: string;
_barR: string; _barR: string;
_debouncer: TimeoutDebouncer = new TimeoutDebouncer(0);
_events: UIEventManager; _events: UIEventManager;
@ViewChild('slider') public _slider: ElementRef; @ViewChild('slider') public _slider: ElementRef;
@ -268,9 +266,8 @@ export class Range extends BaseInput<any> implements AfterViewInit, ControlValue
private _dom: DomController, private _dom: DomController,
private _cd: ChangeDetectorRef private _cd: ChangeDetectorRef
) { ) {
super(config, elementRef, renderer, 'range', form, item, null); super(config, elementRef, renderer, 'range', 0, form, item, null);
this._events = new UIEventManager(_plt); this._events = new UIEventManager(_plt);
this._value = 0;
} }
/** /**

View File

@ -7,6 +7,7 @@ import { commonInputTest, NUMBER_CORPUS } from '../../../util/input-tester';
describe('Range', () => { describe('Range', () => {
it('should pass common test', () => { it('should pass common test', () => {
// TODO, validate range inside bounds
const range = createRange(); const range = createRange();
range._slider = mockElementRef(); range._slider = mockElementRef();
commonInputTest(range, { commonInputTest(range, {

View File

@ -62,7 +62,6 @@ export class Searchbar extends BaseInput<string> {
_autocomplete: string = 'off'; _autocomplete: string = 'off';
_autocorrect: string = 'off'; _autocorrect: string = 'off';
_isActive: boolean = false; _isActive: boolean = false;
_debouncer: TimeoutDebouncer = new TimeoutDebouncer(250);
_showCancelButton: boolean = false; _showCancelButton: boolean = false;
_animated: boolean = false; _animated: boolean = false;
@ -165,7 +164,8 @@ export class Searchbar extends BaseInput<string> {
renderer: Renderer, renderer: Renderer,
@Optional() ngControl: NgControl @Optional() ngControl: NgControl
) { ) {
super(config, elementRef, renderer, 'searchbar', null, null, ngControl); super(config, elementRef, renderer, 'searchbar', '', null, null, ngControl);
this.debounce = 250;
} }
@ViewChild('searchbarInput') _searchbarInput: ElementRef; @ViewChild('searchbarInput') _searchbarInput: ElementRef;

View File

@ -77,7 +77,7 @@ export class Segment extends BaseInput<string> {
renderer: Renderer, renderer: Renderer,
@Optional() ngControl: NgControl @Optional() ngControl: NgControl
) { ) {
super(config, elementRef, renderer, 'segment', null, null, ngControl); super(config, elementRef, renderer, 'segment', null, null, null, ngControl);
} }
/** /**

View File

@ -0,0 +1,31 @@
import { QueryList } from '@angular/core';
import { Segment } from '../segment';
import { SegmentButton } from '../segment-button';
import { mockConfig, mockElementRef, mockRenderer } from '../../../util/mock-providers';
import { commonInputTest } from '../../../util/input-tester';
describe('Segment', () => {
it('should pass common test', () => {
const config = mockConfig();
const elementRef = mockElementRef();
const renderer = mockRenderer();
const segment = new Segment(config, elementRef, renderer, null);
segment._buttons = new QueryList<SegmentButton>();
commonInputTest(segment, {
defaultValue: null,
corpus: [
['option1', 'option1'],
['option2', 'option2'],
['option3', 'option3'],
['option4', 'option4'],
['', ''],
]
});
});
});

View File

@ -7,7 +7,7 @@ import { App } from '../app/app';
import { Config } from '../../config/config'; import { Config } from '../../config/config';
import { Form } from '../../util/form'; import { Form } from '../../util/form';
import { BaseInput } from '../../util/base-input'; import { BaseInput } from '../../util/base-input';
import { isCheckedProperty, isTrueProperty, isBlank, deepCopy } from '../../util/util'; import { isCheckedProperty, isTrueProperty, isBlank, deepCopy, deepEqual } from '../../util/util';
import { Item } from '../item/item'; import { Item } from '../item/item';
import { NavController } from '../../navigation/nav-controller'; import { NavController } from '../../navigation/nav-controller';
import { Option } from '../option/option'; import { Option } from '../option/option';
@ -195,8 +195,7 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
@Optional() item: Item, @Optional() item: Item,
@Optional() private _nav: NavController @Optional() private _nav: NavController
) { ) {
super(config, elementRef, renderer, 'select', form, item, null); super(config, elementRef, renderer, 'select', [], form, item, null);
this._value = [];
} }
@ -259,7 +258,7 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
this.interface = 'alert'; this.interface = 'alert';
} }
let overlay: any; let overlay: ActionSheet | Alert;
if (this.interface === 'action-sheet') { if (this.interface === 'action-sheet') {
selectOptions.buttons = selectOptions.buttons.concat(options.map(input => { selectOptions.buttons = selectOptions.buttons.concat(options.map(input => {
return { return {
@ -321,7 +320,7 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
overlay.addButton({ overlay.addButton({
text: this.okText, text: this.okText,
handler: (selectedValues: any) => this.value = selectedValues handler: (selectedValues) => this.value = selectedValues
}); });
} }
@ -373,20 +372,17 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
} }
_inputNormalize(val: any): string[] { _inputNormalize(val: any): string[] {
if (val === null) { if (isBlank(val)) {
return []; return [];
} }
if (Array.isArray(val)) { if (Array.isArray(val)) {
return val; return val;
} }
return isBlank(val) ? [] : [val]; return [val + ''];
} }
_inputShouldChange(val: string[]): boolean { _inputShouldChange(val: string[]): boolean {
if (val.length === 0 && this._value.length === 0) { return !deepEqual(this._value, val);
return false;
}
return super._inputShouldChange(val);
} }
/** /**

View File

@ -1,7 +1,7 @@
import { Select } from '../select'; import { Select } from '../select';
import { mockApp, mockConfig, mockElementRef, mockRenderer, mockItem, mockForm } from '../../../util/mock-providers'; import { mockApp, mockConfig, mockElementRef, mockRenderer, mockItem, mockForm } from '../../../util/mock-providers';
import { commonInputTest, BOOLEAN_CORPUS } from '../../../util/input-tester'; import { commonInputTest } from '../../../util/input-tester';
describe('Select', () => { describe('Select', () => {
@ -16,8 +16,13 @@ describe('Select', () => {
const select = new Select(app, form, config, elementRef, renderer, item, null); const select = new Select(app, form, config, elementRef, renderer, item, null);
commonInputTest(select, { commonInputTest(select, {
defaultValue: false, defaultValue: [],
corpus: BOOLEAN_CORPUS corpus: [
[['hola'], ['hola']],
[null, []],
['hola', ['hola']],
[['hola', 'adios'], ['hola', 'adios']]
]
}); });
}); });

View File

@ -20,7 +20,7 @@ describe('Toggle', () => {
commonInputTest(toggle, { commonInputTest(toggle, {
defaultValue: false, defaultValue: false,
corpus: BOOLEAN_CORPUS corpus: BOOLEAN_CORPUS,
}); });
}); });

View File

@ -109,8 +109,7 @@ export class Toggle extends BaseInput<boolean> implements IonicTapInput, AfterVi
private _domCtrl: DomController, private _domCtrl: DomController,
private _cd: ChangeDetectorRef private _cd: ChangeDetectorRef
) { ) {
super(config, elementRef, renderer, 'toggle', form, item, null); super(config, elementRef, renderer, 'toggle', false, form, item, null);
this._value = false;
} }
/** /**

View File

@ -2,7 +2,7 @@ import { ElementRef, EventEmitter, Input, Output, Renderer } from '@angular/core
import { ControlValueAccessor } from '@angular/forms'; import { ControlValueAccessor } from '@angular/forms';
import { NgControl } from '@angular/forms'; import { NgControl } from '@angular/forms';
import { isPresent, isArray, isTrueProperty, assert } from './util'; import { isPresent, isUndefined, isArray, isTrueProperty, deepCopy, assert } from './util';
import { Ion } from '../components/ion'; import { Ion } from '../components/ion';
import { Config } from '../config/config'; import { Config } from '../config/config';
import { Item } from '../components/item/item'; import { Item } from '../components/item/item';
@ -30,13 +30,13 @@ export interface CommonInput<T> extends ControlValueAccessor {
export class BaseInput<T> extends Ion implements CommonInput<T> { export class BaseInput<T> extends Ion implements CommonInput<T> {
_value: T = null; _value: T;
_onChanged: Function; _onChanged: Function;
_onTouched: Function; _onTouched: Function;
_isFocus: boolean = false; _isFocus: boolean = false;
_labelId: string; _labelId: string;
_disabled: boolean = false; _disabled: boolean = false;
_debouncer: TimeoutDebouncer; _debouncer: TimeoutDebouncer = new TimeoutDebouncer(0);
_init: boolean = false; _init: boolean = false;
id: string; id: string;
@ -66,18 +66,19 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
this.setDisabledState(val); this.setDisabledState(val);
} }
constructor( constructor(
config: Config, config: Config,
elementRef: ElementRef, elementRef: ElementRef,
renderer: Renderer, renderer: Renderer,
name: string, name: string,
private _defaultValue: T,
public _form: Form, public _form: Form,
public _item: Item, public _item: Item,
ngControl: NgControl ngControl: NgControl
) { ) {
super(config, elementRef, renderer, name); super(config, elementRef, renderer, name);
_form && _form.register(this); _form && _form.register(this);
this._value = deepCopy(this._defaultValue);
if (_item) { if (_item) {
this.id = name + '-' + _item.registerInput(name); this.id = name + '-' + _item.registerInput(name);
@ -103,7 +104,7 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
// 1. Updates the value // 1. Updates the value
// 2. Calls _inputUpdated() // 2. Calls _inputUpdated()
// 3. Dispatch onChange events // 3. Dispatch onChange events
setValue(val: T) { setValue(val: any) {
this.value = val; this.value = val;
} }
@ -123,20 +124,27 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
} }
_writeValue(val: any): boolean { _writeValue(val: any): boolean {
const normalized = this._inputNormalize(val); if (isUndefined(val)) {
const shouldUpdate = this._inputShouldChange(normalized); return false;
if (shouldUpdate) { }
const normalized = (val === null)
? deepCopy(this._defaultValue)
: this._inputNormalize(val);
const notUpdate = isUndefined(normalized) || !this._inputShouldChange(normalized);
if (notUpdate) {
return false;
}
console.debug('BaseInput: value changed:', normalized, this); console.debug('BaseInput: value changed:', normalized, this);
this._value = normalized; this._value = normalized;
this._inputCheckHasValue(normalized); this._inputCheckHasValue(normalized);
this._inputUpdated(); this._inputUpdated();
if (this._init) { if (this._init) {
this.ionChange.emit(this); this._debouncer.debounce(() => this.ionChange.emit(this));
} }
return true; return true;
} }
return false;
}
/** /**
* @hidden * @hidden
@ -224,12 +232,11 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
if (!this._item) { if (!this._item) {
return; return;
} }
let hasValue: boolean;
if (isArray(val)) { const hasValue = isArray(val)
hasValue = val.length > 0; ? val.length > 0
} else { : isPresent(val);
hasValue = isPresent(val);
}
this._item.setElementClass('input-has-value', hasValue); this._item.setElementClass('input-has-value', hasValue);
} }
@ -249,7 +256,7 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
* @hidden * @hidden
*/ */
_inputShouldChange(val: T): boolean { _inputShouldChange(val: T): boolean {
return (typeof val !== 'undefined') && this._value !== val; return this._value !== val;
} }
/** /**

View File

@ -21,7 +21,7 @@ export const NUMBER_CORPUS: any[] = [
[123456789, 123456789], [123456789, 123456789],
['1.1234', 1.1234], ['1.1234', 1.1234],
['123456789', 123456789], ['123456789', 123456789],
['-123456789', -123456789], ['-123456789', -123456789]
]; ];
export const BOOLEAN_CORPUS: any[] = [ export const BOOLEAN_CORPUS: any[] = [
@ -30,13 +30,11 @@ export const BOOLEAN_CORPUS: any[] = [
['', true], ['', true],
['false', false], ['false', false],
['true', true], ['true', true],
['hola', false]
]; ];
export const ANY_CORPUS: any[] = [ export const ANY_CORPUS: any[] = [
[true, true], [true, true],
[false, false], [false, false],
[null, null],
[0, 0], [0, 0],
['', ''], ['', ''],
[' ', ' '], [' ', ' '],
@ -68,16 +66,36 @@ function testInput<T>(input: BaseInput<T>, config: TestConfig, isInit: boolean)
} }
function testState<T>(input: BaseInput<T>, config: TestConfig, isInit: boolean) { function testState<T>(input: BaseInput<T>, config: TestConfig, isInit: boolean) {
assert(input._init === isInit, 'input must be init'); assertEqual(input._init, isInit, 'input must be init');
assert(!input._isFocus && !input.isFocus(), 'should not be focus'); assertEqual(input._isFocus, false, 'should not be focus');
assert(input.value === config.defaultValue, 'default value is wrong'); assertEqual(input.isFocus(), false, 'should not be focus');
assertEqual(input.value, config.defaultValue, 'default value is wrong');
let blurCount = 0;
let focusCount = 0;
const subBlur = input.ionBlur.subscribe((ev: any) => {
assertEqual(ev, input, 'ionBlur argument is wrong');
blurCount++;
});
const subFocus = input.ionFocus.subscribe((ev: any) => {
assertEqual(ev, input, 'ionFocus argument is wrong');
focusCount++;
});
input._setFocus(); input._setFocus();
assert(input._isFocus && input.isFocus(), 'should be focus'); assertEqual(input._isFocus, true, 'should be focus');
input._setFocus(); // it should not crash assertEqual(input.isFocus(), true, 'should be focus');
input._setFocus();
input._setBlur(); input._setBlur();
assert(!input._isFocus && !input.isFocus(), 'should not be focus'); assertEqual(input._isFocus, false, 'should be not focus');
assertEqual(input.isFocus(), false, 'should be not focus');
input._setBlur(); // it should not crash input._setBlur(); // it should not crash
assertEqual(focusCount, 1, 'ionFocus was not called correctly');
assertEqual(blurCount, 1, 'ionBlur was not called correctly');
subBlur.unsubscribe();
subFocus.unsubscribe();
} }
function testWriteValue<T>(input: BaseInput<T>, config: TestConfig, isInit: boolean) { function testWriteValue<T>(input: BaseInput<T>, config: TestConfig, isInit: boolean) {
@ -87,27 +105,28 @@ function testWriteValue<T>(input: BaseInput<T>, config: TestConfig, isInit: bool
let OnChangeCalled = 0; let OnChangeCalled = 0;
let OnTouchedCalled = 0; let OnTouchedCalled = 0;
input.value = config.defaultValue;
// Test ionChange // Test ionChange
let sub = input.ionChange.subscribe((ev: any) => { let sub = input.ionChange.subscribe((ev: any) => {
assert(ionChangeCalled === 0, 'internal error'); assertEqual(ionChangeCalled, 0, 'ionChange: internal error');
assert(ev === input, 'ev is not the input'); assertEqual(ev, input, 'ionChange: ev is not the input');
assert(test[1] === ev.value, 'value does not match'); assertEqual(ev.value, test[1], 'ionChange: value does not match');
ionChangeCalled++; ionChangeCalled++;
}); });
// Test registerOnChange // Test registerOnChange
input.registerOnChange((ev: any) => { input.registerOnChange((ev: any) => {
assert(OnChangeCalled === 0, 'internal error'); assertEqual(OnChangeCalled, 0, 'registerOnChange: internal error');
assert(ev === input.value, 'ev output does not match'); assertEqual(input.value, ev, 'registerOnChange: ev output does not match');
assert(test[1] === input.value, 'value does not match'); assertEqual(input.value, test[1], 'registerOnChange: value does not match');
OnChangeCalled++; OnChangeCalled++;
}); });
// Test registerOnChange // Test registerOnChange
input.registerOnTouched(() => { input.registerOnTouched(() => {
assert(OnTouchedCalled === 0, 'internal error'); assertEqual(OnTouchedCalled, 0, 'registerOnTouched: internal error');
OnTouchedCalled++; OnTouchedCalled++;
}); });
@ -115,28 +134,45 @@ function testWriteValue<T>(input: BaseInput<T>, config: TestConfig, isInit: bool
for (i = 0; i < config.corpus.length; i++) { for (i = 0; i < config.corpus.length; i++) {
test = config.corpus[i]; test = config.corpus[i];
input.value = test[0]; input.value = test[0];
assert(input.value === test[1], 'input/output does not match'); assertEqual(input.value, test[1], 'loop: input/output does not match');
if (isInit) { if (isInit) {
assert(ionChangeCalled === 1, 'ionChange error'); assertEqual(ionChangeCalled, 1, 'loop: ionChange error');
} else { } else {
assert(ionChangeCalled === 0, 'ionChange error'); assertEqual(ionChangeCalled, 0, 'loop: ionChange error');
} }
assert(OnChangeCalled === 1, 'OnChangeCalled was not called'); assertEqual(OnChangeCalled, 1, 'loop: OnChangeCalled was not called');
assert(OnTouchedCalled === 1, 'OnTouchedCalled was not called'); assertEqual(OnTouchedCalled, 1, 'loop: OnTouchedCalled was not called');
OnTouchedCalled = OnChangeCalled = ionChangeCalled = 0; OnTouchedCalled = OnChangeCalled = ionChangeCalled = 0;
console.log(test[0], input.value);
// Set same value (it should not redispatch) // Set same value (it should not redispatch)
input.value = test[0]; input.value = test[0];
assert(ionChangeCalled === 0, 'ionChange should not be called'); assertEqual(ionChangeCalled, 0, 'loop: ionChange should not be called');
assert(OnChangeCalled === 0, 'OnChangeCalled should not be called'); assertEqual(OnChangeCalled, 0, 'loop: OnChangeCalled should not be called');
// TODO OnTouchedCalled? // TODO OnTouchedCalled?
OnTouchedCalled = OnChangeCalled = ionChangeCalled = 0; OnTouchedCalled = OnChangeCalled = ionChangeCalled = 0;
} }
// Test undefined
input.value = undefined;
assertEqual(input.value, test[1], 'undefined should not change the value');
assertEqual(ionChangeCalled, 0, 'undefined: ionChange should not be called');
assertEqual(OnChangeCalled, 0, 'undefined: OnChangeCalled should not be called');
assertEqual(OnTouchedCalled, 0, 'undefined: OnTouchedCalled should not be called');
// Test null (reset)
test = [null, config.defaultValue];
input.value = null;
assertEqual(input.value, config.defaultValue, 'null: wrong default value');
assertEqual(OnChangeCalled, 1, 'null: OnChangeCalled was not called');
assertEqual(OnTouchedCalled, 1, 'null: OnTouchedCalled was not called');
input.registerOnChange(null); input.registerOnChange(null);
input.registerOnTouched(null); input.registerOnTouched(null);
sub.unsubscribe(); sub.unsubscribe();
input.value = config.defaultValue;
} }
function testNgModelChange<T>(input: BaseInput<T>, config: TestConfig, isInit: boolean) { function testNgModelChange<T>(input: BaseInput<T>, config: TestConfig, isInit: boolean) {
@ -148,9 +184,10 @@ function testNgModelChange<T>(input: BaseInput<T>, config: TestConfig, isInit: b
// Test ionChange // Test ionChange
let sub = input.ionChange.subscribe((ev: any) => { let sub = input.ionChange.subscribe((ev: any) => {
assert(ionChangeCalled === 0, 'internal error'); assertEqual(ionChangeCalled, 0, 'internal error');
assert(ev === input, 'ev output does not match'); assertEqual(ev, input, 'ev output does not match');
assert(test[1] === ev.value, 'value does not match'); assertEqual(test[1], ev.value, 'value does not match');
ionChangeCalled++; ionChangeCalled++;
}); });
@ -169,21 +206,21 @@ function testNgModelChange<T>(input: BaseInput<T>, config: TestConfig, isInit: b
test = config.corpus[i]; test = config.corpus[i];
input.writeValue(test[0]); input.writeValue(test[0]);
assert(input.value === test[1], 'input/output does not match'); assertEqual(input.value, test[1], 'input/output does not match');
if (isInit) { if (isInit) {
assert(ionChangeCalled === 1, 'ionChange error'); assertEqual(ionChangeCalled, 1, 'ionChange error');
} else { } else {
assert(ionChangeCalled === 0, 'ionChange error'); assertEqual(ionChangeCalled, 0, 'ionChange error');
} }
assert(OnChangeCalled === 0, 'OnChangeCalled should not be called'); assertEqual(OnChangeCalled, 0, 'OnChangeCalled should not be called');
assert(OnTouchedCalled === 0, 'OnTouchedCalled should not be called'); assertEqual(OnTouchedCalled, 0, 'OnTouchedCalled should not be called');
OnTouchedCalled = OnChangeCalled = ionChangeCalled = 0; OnTouchedCalled = OnChangeCalled = ionChangeCalled = 0;
// Set same value (it should not redispatch) // Set same value (it should not redispatch)
input.writeValue(test[0]); input.writeValue(test[0]);
input.value = test[0]; input.value = test[0];
assert(ionChangeCalled === 0, 'ionChange should not be called'); assertEqual(ionChangeCalled, 0, 'ionChange should not be called');
assert(OnChangeCalled === 0, 'OnChangeCalled should not be called'); assertEqual(OnChangeCalled, 0, 'OnChangeCalled should not be called');
// TODO OnTouchedCalled? // TODO OnTouchedCalled?
OnTouchedCalled = OnChangeCalled = ionChangeCalled = 0; OnTouchedCalled = OnChangeCalled = ionChangeCalled = 0;
@ -195,7 +232,19 @@ function testNgModelChange<T>(input: BaseInput<T>, config: TestConfig, isInit: b
input.value = config.defaultValue; input.value = config.defaultValue;
} }
function assertEqual(a: any, b: any, message: string) {
if (!equal(a, b)) {
assert(false, a + ' != ' + b + ' ' + message);
}
}
function equal(a: any, b: any): boolean {
if (a === b) {
return true;
}
// return false;
return JSON.stringify(a) === JSON.stringify(b);
}

View File

@ -51,5 +51,5 @@ function mockInput(form: any, item: any, ngControl: any): BaseInput<any> {
config = mockConfig(null, '/', platform); config = mockConfig(null, '/', platform);
elementRef = mockElementRef(); elementRef = mockElementRef();
renderer = mockRenderer(); renderer = mockRenderer();
return new BaseInput(config, elementRef, renderer, 'input', form, item, ngControl); return new BaseInput(config, elementRef, renderer, 'input', null, form, item, ngControl);
} }

View File

@ -16,6 +16,14 @@ export function deepCopy(obj: any) {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
/** @hidden */
export function deepEqual(a: any, b: any) {
if (a === b) {
return true;
}
return JSON.stringify(a) === JSON.stringify(b);
}
/** @hidden */ /** @hidden */
export function debounce(fn: Function, wait: number, immediate: boolean = false): any { export function debounce(fn: Function, wait: number, immediate: boolean = false): any {
var timeout: number, args: any, context: any, timestamp: number, result: any; var timeout: number, args: any, context: any, timestamp: number, result: any;