Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(datepicker): validate that input actually parses #5711

Merged
merged 10 commits into from Aug 1, 2017

Conversation

mmalerba
Copy link
Contributor

@mmalerba mmalerba commented Jul 12, 2017

BREAKING CHANGE: You must now use an actual Date object rather than a
string when setting the value of the datepicker programmatically
(through value, ngModel, or formControl).

BREAKING CHNAGE: DateAdapter subclasses must now provide an implementation for isValidDate

fixes #5417
fixes #4978

@mmalerba mmalerba requested a review from jelbourn July 12, 2017 23:18
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Jul 12, 2017
@@ -192,6 +192,10 @@ export class NativeDateAdapter extends DateAdapter<Date> {
].join('-');
}

isDateObject(value: any) {
return value instanceof Date;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add unit test for this?

@@ -99,12 +99,14 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
this._dateFormats.parse.dateInput);
}
set value(value: D | null) {
let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);
if (value != null && !this._dateAdapter.isDateObject(value)) {
throw new Error('Datepicker: value not recognized as a date object by DateAdapter.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hot tip: with Error you don't need new (just throw Error('...'))

@mmalerba
Copy link
Contributor Author

comments addressed

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jelbourn jelbourn added pr: lgtm action: merge The PR is ready for merge by the caretaker merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note and removed pr: needs review labels Jul 13, 2017
@jelbourn
Copy link
Member

jelbourn commented Jul 15, 2017

Caretaker note: one google app already had an invalid value, will need to fix

@mmalerba mmalerba removed pr: lgtm action: merge The PR is ready for merge by the caretaker labels Jul 25, 2017
@mmalerba
Copy link
Contributor Author

un-lgtm'ing this so I can reconcile with what we talked about in #4978

@mmalerba mmalerba changed the title fix(datepicker): throw when value is set to invalid type fix(datepicker): validate that input actually parses Jul 25, 2017
@mmalerba mmalerba requested a review from kara July 25, 2017 21:40
@mmalerba
Copy link
Contributor Author

@jelbourn Changed this PR a bit based on the discussion @kara and I had about #4978. I think just throwing an error like I was doing before is problematic if we want to do parse validation.

Couple of questions:

  1. How should the calendar behave when the input has an invalid value. Currently it acts as if the input's value were null, another option would be to act as if the value had never changed from its last valid value
  2. How should inputs behave when their from control changes to an invalid value (currently they show "INVALID DATE" which seems kind of weird...

@@ -192,6 +197,16 @@ export class NativeDateAdapter extends DateAdapter<Date> {
].join('-');
}

isValidDate(value: any) {
if (value == null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jelbourn It kind of feels wrong that people extending DateAdapter even have the ability to say "null is invalid" maybe an indicator that I should move all the null checks into the datepicker code itself, WDYT?

@mmalerba mmalerba force-pushed the dp-err branch 2 times, most recently from 9fd24e0 to 34bc1be Compare July 26, 2017 00:22
@mmalerba
Copy link
Contributor Author

Ok answering my own questions:

  • Looked at the way type="number" inputs work when you type stuff that doesn't parse (e.g. "2e"). It looks like they are pretty much treated as being null for all purposes, so did the same.
  • Decided users shouldn't be able to declare null to be an invalid date, refactored it so they can't

}

format(date: Date, displayFormat: Object): string {
if (!this.isValid(date)) {
return 'INVALID DATE';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what the best thing to do here is (probably not this). Datepicker should never call format on an invalid date, but someone could in their code, maybe just throw an error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing an error seems reasonable to me

abstract isValid(date: D): boolean;

/**
* @param {obj} The object to check.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omit braces on obj

*/
getValidDateOrNull(obj: any): D | null {
return (this.isDateInstance(obj) && this.isValid(obj)) ? obj : null;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be be more appropriate to have this in the component code than in the adapter?

@mmalerba
Copy link
Contributor Author

comments adressed, also meant to include a demo link: https://mmalerba-demo2.firebaseapp.com/datepicker

@mmalerba
Copy link
Contributor Author

@kara demo behavior is still a little different from how number inputs work (compare demo link above with this plunker) http://plnkr.co/edit/GoutPJd2AXx7KPD4d0XB?p=preview any advice on how to make it a little closer to what number input does?

@mmalerba
Copy link
Contributor Author

@kara @jelbourn Ok I think I've finally gotten the behavior exactly how I want it now. The material input and the native input under the "Result" section of this demo are wired up to the same ngModel. Typing an unparseable date into the material one activates the md-error but doesn't cause the value to change on the other input and doesn't emit change or input events since the error value is still treated as null. Opening the calendar when the input value is unparesable still allows choosing a date as normal and just acts as if the input were null.

Copy link
Contributor

@kara kara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just nits

it('should parse invalid value as invalid', () => {
let d = adapter.parse('hello');
expect(d).not.toBeNull();
expect(adapter.isDateInstance(d)).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: add a failure message here? Seems odd without context that 'hello' would be a Date instance. e.g. Expected string to be fed through Date.parse()

it('should throw when given wrong data type', () => {
testComponent.date = '1/1/2017' as any;

expect(() => fixture.detectChanges()).toThrow();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Add a regex to make this clear which error should surface? e.g. toThrowError(new RegExp(...)

}

format(date: Date, displayFormat: Object): string {
if (!this.isValid(date)) {
return 'INVALID DATE';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing an error seems reasonable to me

@mmalerba
Copy link
Contributor Author

Comments addressed

@kara
Copy link
Contributor

kara commented Jul 31, 2017

LGTM

@kara kara added pr: lgtm action: merge The PR is ready for merge by the caretaker and removed pr: needs review labels Jul 31, 2017
@kara kara removed their assignment Jul 31, 2017
@tinayuangao tinayuangao merged commit 8bb54ca into angular:master Aug 1, 2017
@uvconnects
Copy link

I also get this when I was not getting this before date format is 2017-08-02T00:00:00

@jbojcic1
Copy link
Contributor

@mmalerba in your demo, validation is passing if I type 34 and I get 1/1/2034

@mmalerba
Copy link
Contributor Author

@jbojcic1 that's because Date.parse('34') parses to 1/1/2034. The NativeDateAdapter simply delegates parsing to Date.parse. If you want to change the parsing behavior you can create a custom DateAdapter

@mmalerba mmalerba deleted the dp-err branch April 3, 2018 15:18
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

md-datepicker throw exception Datepicker not validating to a valid date
7 participants