From 8ef943057d5b5d6912818220bac7f6db5aa29d50 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Fri, 4 Apr 2014 15:19:17 -0500 Subject: [PATCH] feat(ng-model): support input type=date | datetime and all other date/time variants - Add support for input elements of type `date|datetime|datetime-local|month|time|week`. - Remove non-UTF-8 characters from some other API comments. --- lib/directive/module.dart | 2 + lib/directive/ng_model.dart | 195 +++++++- lib/utils.dart | 7 + test/angular_spec.dart | 1 + test/directive/ng_model_datelike_spec.dart | 489 +++++++++++++++++++++ 5 files changed, 691 insertions(+), 3 deletions(-) create mode 100644 test/directive/ng_model_datelike_spec.dart diff --git a/lib/directive/module.dart b/lib/directive/module.dart index 7bb1f4544..d5187a6b4 100644 --- a/lib/directive/module.dart +++ b/lib/directive/module.dart @@ -62,12 +62,14 @@ class NgDirectiveModule extends Module { value(NgRepeat, null); value(NgShow, null); value(InputTextLike, null); + value(InputDateLike, null); value(InputNumberLike, null); value(InputRadio, null); value(InputCheckbox, null); value(InputSelect, null); value(OptionValue, null); value(ContentEditable, null); + value(NgBindTypeForDateLike, null); value(NgModel, null); value(NgValue, null); value(NgTrueValue, new NgTrueValue()); diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index 661435fc9..42c5bef6e 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -439,6 +439,195 @@ class InputNumberLike { } } +/** + * This directive affects which IDL attribute will be used to read the value of + * date/time related input directives. Recognized values for this directive are: + * + * - [DATE]: [dom.InputElement].valueAsDate will be read. + * - [NUMBER]: [dom.InputElement].valueAsNumber will be read. + * - [STRING]: [dom.InputElement].value will be read. + * + * The default is [DATE]. Use other settings, e.g., when an app needs to support + * browsers that treat date-like inputs as text (in such a case the [STRING] + * kind would be appropriate) or, for browsers that fail to conform to the + * HTML5 standard in their processing of date-like inputs. + */ +@NgDirective(selector: 'input[type=date][ng-model][ng-bind-type]') +@NgDirective(selector: 'input[type=time][ng-model][ng-bind-type]') +@NgDirective(selector: 'input[type=datetime][ng-model][ng-bind-type]') +@NgDirective(selector: 'input[type=datetime-local][ng-model][ng-bind-type]') +@NgDirective(selector: 'input[type=month][ng-model][ng-bind-type]') +@NgDirective(selector: 'input[type=week][ng-model][ng-bind-type]') +class NgBindTypeForDateLike { + static const + DIRECTIVE_NAME = 'ng-bind-type', + DATE = 'date', + NUMBER = 'number', + STRING = 'string', + DEFAULT = DATE; + static const VALID_VALUES = const [DATE, NUMBER, STRING]; + + final dom.InputElement inputElement; + String _idlAttrKind = DEFAULT; + + NgBindTypeForDateLike(dom.Element this.inputElement); + + // Can't use @NgAttr(DIRECTIVE_NAME) + // See: https://github.com/angular/angular.dart/pull/747#issuecomment-39588486 + @NgAttr('ng-bind-type') + void set idlAttrKind(final String _kind) { + String kind = _kind == null ? DEFAULT : _kind.toLowerCase(); + if (!VALID_VALUES.contains(kind)) + throw "Unsupported $DIRECTIVE_NAME attribute value '$_kind'; " + "it should be one of $VALID_VALUES"; + _idlAttrKind = kind; + } + + String get idlAttrKind => _idlAttrKind; + + dynamic get inputTypedValue { + switch (idlAttrKind) { + case DATE: return inputValueAsDate; + case NUMBER: return inputElement.valueAsNumber; + default: return inputElement.value; + } + } + + void set inputTypedValue(dynamic inputValue) { + if (inputValue is DateTime) { + inputValueAsDate = inputValue; + } else if (inputValue is num) { + inputElement.valueAsNumber = inputValue; + } else { + inputElement.value = inputValue; + } + } + + /// Input's `valueAsDate` normalized to UTC (per HTML5 std). + DateTime get inputValueAsDate { + DateTime dt; + // Wrap in try-catch due to + // https://code.google.com/p/dart/issues/detail?id=17625 + try { + dt = inputElement.valueAsDate; + } catch (e) { + dt = null; + } + return (dt != null && !dt.isUtc) ? dt.toUtc() : dt; + } + + /// Set input's `valueAsDate`. Argument is normalized to UTC if necessary + /// (per HTML standard). + void set inputValueAsDate(DateTime dt) { + inputElement.valueAsDate = (dt != null && !dt.isUtc) ? dt.toUtc() : dt; + } +} + +/** + * **Background: Standards and Browsers** + * + * According to the + * [HTML5 Standard](http://www.w3.org/TR/html5/forms.html#the-input-element), + * the [dom.InputElement.valueAsDate] and [dom.InputElement.valueAsNumber] IDL + * attributes should be available for all date/time related input types, + * except for `datetime-local` which is limited to + * [dom.InputElement.valueNumber]. Of course, all input types support + * [dom.InputElement.value] which yields a [String]; + * [dom.InputElement.valueAsDate] yields a [DateTime] and + * [dom.InputElement.valueNumber] yields a [num]. + * + * But not all browsers currently support date/time related inputs and of + * those that do, some deviate from the standard. Hence, this directive + * allows developers to control the IDL attribute that will be used + * to read the value of a date/time input. This is achieved via the subordinate + * 'ng-bind-type' directive; see [NgBindTypeForDateLike] for details. + * + * **Usage**: + * + * + * + * **Model**: + * + * dynamic myModel; // one of DateTime | num | String + * + * This directive creates a two-way binding between the input and a model + * property. The subordinate 'ng-bind-type' directive determines which input + * IDL attribute is read (see [NgBindTypeForDateLike] for details) and + * hence the type of the read values. The type of the model property value + * determines which IDL attribute is written to: [DateTime] and [num] values + * are assigned to [dom.InputElement.valueAsDate] and + * [dom.InputElement.valueNumber], respectively; [String] and `null` values + * are assigned to [dom.InputElement.value]. Setting the model to `null` will + * clear the input if it is currently valid, otherwise, invalid input is left + * untouched (so that the user has an opportunity to correct it). To clear the + * input unconditionally, set the model property to the empty string (''). + * + * **Notes**: + * - As prescribed by the HTML5 standard, [DateTime] values returned by the + * `valueAsDate` IDL attribute are meant to be in UTC. + * - As of the HTML5 Editor's Draft 29 March 2014, datetime-local is no longer + * part of the standard. Other date related input are also at risk of being + * dropped. + */ + +@NgDirective(selector: 'input[type=date][ng-model]', + module: InputDateLike.moduleFactory) +@NgDirective(selector: 'input[type=time][ng-model]', + module: InputDateLike.moduleFactory) +@NgDirective(selector: 'input[type=datetime][ng-model]', + module: InputDateLike.moduleFactory) +@NgDirective(selector: 'input[type=datetime-local][ng-model]', + module: InputDateLike.moduleFactory) +@NgDirective(selector: 'input[type=month][ng-model]', + module: InputDateLike.moduleFactory) +@NgDirective(selector: 'input[type=week][ng-model]', + module: InputDateLike.moduleFactory) +class InputDateLike { + static Module moduleFactory() => new Module()..factory(NgBindTypeForDateLike, + (Injector i) => new NgBindTypeForDateLike(i.get(dom.Element))); + final dom.InputElement inputElement; + final NgModel ngModel; + final Scope scope; + NgBindTypeForDateLike ngBindType; + + InputDateLike(dom.Element this.inputElement, this.ngModel, this.scope, + this.ngBindType) { + if (inputElement.type == 'datetime-local') { + ngBindType.idlAttrKind = NgBindTypeForDateLike.NUMBER; + } + ngModel.render = (value) { + scope.rootScope.domWrite(() { + if (!eqOrNaN(value, typedValue)) { + typedValue = value; + } + }); + }; + inputElement + ..onChange.listen(relaxFnArgs(processValue)) + ..onInput.listen(relaxFnArgs(processValue)) + ..onBlur.listen((e) { + ngModel.markAsTouched(); + }); + } + + dynamic get typedValue => ngBindType.inputTypedValue; + + void set typedValue(dynamic value) { + ngBindType.inputTypedValue = value; + } + + void processValue() { + var value = typedValue; + // print("processValue: value=$value, model=${ngModel.viewValue}"); + if (!eqOrNaN(value, ngModel.viewValue)) { + scope.eval(() => ngModel.viewValue = value); + } + ngModel.validate(); + } +} + class _UidCounter { static final int CHAR_0 = "0".codeUnitAt(0); static final int CHAR_9 = "9".codeUnitAt(0); @@ -548,14 +737,14 @@ class NgFalseValue { * * * This creates a two way databinding between the expression specified in - * ng-model and the range input elements in the DOM.  If the ng-model value is + * ng-model and the range input elements in the DOM. If the ng-model value is * set to a value not corresponding to one of the radio elements, then none of * the radio elements will be check. Otherwise, only the corresponding input * element in the group is checked. Likewise, when a radio button element is * checked, the model is updated with its value. Radio buttons that have a * `name` attribute are left alone. Those that are missing the attribute will * have a unique `name` assigned to them. This sequence goes `001`, `001`, ... - * `009`, `00A`, `00Z`, `010`, … and so on using more than 3 characters for the + * `009`, `00A`, `00Z`, `010`, and so on using more than 3 characters for the * name when the counter overflows. */ @NgDirective( @@ -598,7 +787,7 @@ class InputRadio { * * * This creates a two way databinding between the expression specified in - * ng-model and the html element in the DOM.  If the ng-model value is + * ng-model and the html element in the DOM. If the ng-model value is * `null`, it is treated as equivalent to the empty string for rendering * purposes. */ diff --git a/lib/utils.dart b/lib/utils.dart index 1a476b737..85762086b 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -118,3 +118,10 @@ final Set RESERVED_WORDS = new Set.from(const [ "while", "with" ]); + +/// Returns true iff o is [double.NAN]. +/// In particular, returns false if o is null. +bool isNaN(Object o) => o is num && o.isNaN; + +/// Returns true iff o1 == o2 or both are [double.NAN]. +bool eqOrNaN(Object o1, Object o2) => o1 == o2 || (isNaN(o1) && isNaN(o2)); \ No newline at end of file diff --git a/test/angular_spec.dart b/test/angular_spec.dart index ed38bc475..62f8b0a27 100644 --- a/test/angular_spec.dart +++ b/test/angular_spec.dart @@ -182,6 +182,7 @@ main() { "angular.directive.NgRepeat", "angular.directive.NgShow", "angular.directive.InputTextLike", + "angular.directive.InputDateLike", "angular.directive.InputNumberLike", "angular.directive.InputRadio", "angular.directive.InputCheckbox", diff --git a/test/directive/ng_model_datelike_spec.dart b/test/directive/ng_model_datelike_spec.dart new file mode 100644 index 000000000..d690b0e58 --- /dev/null +++ b/test/directive/ng_model_datelike_spec.dart @@ -0,0 +1,489 @@ +library ng_model_date_like_spec; + +import '../_specs.dart'; +import 'dart:html' as dom; + +/** + * Note: some tests become noops for browsers that do not support the particular + * date-like input being tested. + */ +void main() { + //---------------------------------------------------------------------------- + // Test fixture + TestBed _; + InputElement inputElement; + + //---------------------------------------------------------------------------- + // Utility functions + + /// Wrapper for [valueAsDate] IDL attribute access, necessary due to + /// https://code.google.com/p/dart/issues/detail?id=17625 + DateTime inputValueAsDateWrapper(InputElement inputElement) { + try { + return inputElement.valueAsDate; + } catch (e) { + return null; + } + } + + DateTime inputValueAsDate() { + DateTime dt = inputValueAsDateWrapper(inputElement); + return (dt != null && !dt.isUtc) ? dt.toUtc() : dt; + } + + bool isBrowser(String pattern) => + dom.window.navigator.userAgent.indexOf(pattern) > 0; + + /** Use this function to determine if a non type=text or type=textarea + * input is supported by the browser under test. If [shouldWorkForChrome] + * and then browser is Chrome, then `expect()` the input element to be supported. + */ + bool nonTextInputElementSupported(InputElement input, {bool + shouldWorkForChrome: true}) { + const testValue = '!'; // any string that is not valid for the input. + String savedValue = input.value; + input.value = testValue; + if (input.value == testValue) { + if (shouldWorkForChrome) expect(isBrowser('Chrome')).toBeFalsy(); + return false; + } + input.value = savedValue; + return true; + } + + //---------------------------------------------------------------------------- + // Tests + + describe('ng-model for date-like input', () { + beforeEach((TestBed tb) => _ = tb); + beforeEach(() => inputElement = null); + + describe('type=date', () { + final DateTime dateTime = new DateTime.utc(2014, 3, 29); + final String dtAsString = "2014-03-29"; + + it('should update input value from DateTime model property', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + }); + + it('should update input value from String model property', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + // if(!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.apply(); + expect(inputElement.value).toEqual(''); + _.rootScope.context['model'] = dtAsString; + _.rootScope.apply(); + expect(inputElement.value).toEqual(dtAsString); + }); + + it('should update model from the input "valueAsDate" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.valueAsDate = dateTime; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should update model from the input "value" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.value = dtAsString; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should clear input when model is the empty string', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + + _.rootScope.context['model'] = ''; + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + expect(inputElement.value).toEqual(''); + }); + + it('should clear valid input when model is set to null', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + + _.rootScope.context['model'] = null; + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + expect(inputElement.value).toEqual(''); + }); + }); + + describe('type=time', () { + final DateTime dateTime = new DateTime.utc(1970, 1, 1, 23, 45, 16); + final String dtAsString = "23:45:16"; + + it('should update input value from DateTime model property', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + }); + + it('should update input value from String model property', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + // if(!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.apply(); + expect(inputElement.value).toEqual(''); + _.rootScope.context['model'] = dtAsString; + _.rootScope.apply(); + expect(inputElement.value).toEqual(dtAsString); + }); + + it('should update model from the input "valueAsDate" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.valueAsDate = dateTime; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should update model from the input "value" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.value = dtAsString; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should clear input when model is the empty string', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + + _.rootScope.context['model'] = ''; + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + expect(inputElement.value).toEqual(''); + }); + + it('should clear valid input when model is set to null', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + + _.rootScope.context['model'] = null; + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + expect(inputElement.value).toEqual(''); + }); + }); + + describe('type=datetime-local', () { + final DateTime dt = new DateTime.utc(2014, 03, 30, 23, 45, 16); + final num dateTime = dt.millisecondsSinceEpoch; + final String dtAsString = "2014-03-30T23:45:16"; + + it('should update input value from num model', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.apply(); + expect(inputElement.valueAsNumber.isNaN).toBeTruthy(); + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputElement.valueAsNumber).toEqual(dateTime); + }); + + it('should update input value from String model property', () { + _.compile( + ''); + inputElement = _.rootElement as dom.InputElement; + + _.rootScope.apply(); + expect(inputElement.value).toEqual(''); + _.rootScope.context['model'] = dtAsString; + _.rootScope.apply(); + expect(inputElement.value).toEqual(dtAsString); + }); + + it('should update model from the input "valueAsNumber" IDL attribute', () + { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.valueAsNumber = dateTime; + expect(inputElement.valueAsNumber).toEqual(dateTime); + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should update model from the input "value" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.value = dtAsString; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should clear input when model is the empty string', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputElement.valueAsNumber).toEqual(dateTime); + + _.rootScope.context['model'] = ''; + _.rootScope.apply(); + expect(inputElement.valueAsNumber.isNaN).toBeTruthy(); + expect(inputElement.value).toEqual(''); + }); + + it('should clear valid input when model is set to null', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputElement.valueAsNumber).toEqual(dateTime); + + _.rootScope.context['model'] = null; + _.rootScope.apply(); + expect(inputElement.valueAsNumber.isNaN).toBeTruthy(); + expect(inputElement.value).toEqual(''); + }); + }); + + describe('type=datetime', () { + /* + * Note: no browser that I know of supports type=datetime other than + * treating it as an ordinary type=text input. Hence, no tests + * are added for type=datetime other than accessing its value as a string. + */ + final DateTime dateTime = new DateTime.utc(2014, 03, 30, 23, 45, 16); + final String dtAsString = "2014-03-30T23:45:16"; + + it('should update input value from String model property', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + + _.rootScope.apply(); + expect(inputElement.value).toEqual(''); + _.rootScope.context['model'] = dtAsString; + _.rootScope.apply(); + expect(inputElement.value).toEqual(dtAsString); + }); + + it('should update model from the input "value" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement, shouldWorkForChrome: + false)) return; // skip test + + inputElement.value = dtAsString; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dtAsString); + }); + }); + + describe('type=month', () { + final DateTime dateTime = new DateTime.utc(2014, 3); + final String dtAsString = "2014-03"; + + it('should update input value from DateTime model property', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + }); + + it('should update input value from String model property', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + + _.rootScope.apply(); + expect(inputElement.value).toEqual(''); + _.rootScope.context['model'] = dtAsString; + _.rootScope.apply(); + expect(inputElement.value).toEqual(dtAsString); + }); + + it('should update model from the input "valueAsDate" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.valueAsDate = dateTime; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should update model from the input "value" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.value = dtAsString; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should clear input when model is the empty string', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + + _.rootScope.context['model'] = ''; + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + expect(inputElement.value).toEqual(''); + }); + + it('should clear valid input when model is set to null', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + + _.rootScope.context['model'] = null; + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + expect(inputElement.value).toEqual(''); + }); + }); + + describe('type=week', () { + final DateTime dateTime = new DateTime.utc(2014, 3, 31); + final String dtAsString = "2014-W14"; + + it('should update input value from DateTime model property', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + }); + + it('should update input value from String model property', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + + _.rootScope.apply(); + expect(inputElement.value).toEqual(''); + _.rootScope.context['model'] = dtAsString; + _.rootScope.apply(); + expect(inputElement.value).toEqual(dtAsString); + }); + + it('should update model from the input "valueAsDate" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.valueAsDate = dateTime; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should update model from the input "value" IDL attribute', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + inputElement.value = dtAsString; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.context['model']).toEqual(dateTime); + }); + + it('should clear input when model is the empty string', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + + _.rootScope.context['model'] = ''; + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + expect(inputElement.value).toEqual(''); + }); + + it('should clear valid input when model is set to null', () { + _.compile(''); + inputElement = _.rootElement as dom.InputElement; + if (!nonTextInputElementSupported(inputElement)) return; // skip test + + _.rootScope.context['model'] = dateTime; + _.rootScope.apply(); + expect(inputValueAsDate()).toEqual(dateTime); + + _.rootScope.context['model'] = null; + _.rootScope.apply(); + expect(inputValueAsDate()).toBeNull(); + expect(inputElement.value).toEqual(''); + }); + }); + }); +}