Skip to content

Conversation

fnagel
Copy link
Member

@fnagel fnagel commented Sep 30, 2016

  • Fix datepicker min / max attribute parsing
  • Add change event incl. basic tests
  • Add unit tests for calendar select event
  • Add unit tests for calendar value option
  • Add unit tests for datepicker min / max option
  • Introduce dateEqual assertion as proposed in wiki
  • Make use of setup / teardown methods and remove helper file as proposed in wiki

@fnagel
Copy link
Member Author

fnagel commented Sep 30, 2016

@scottgonzalez @jzaefferer FYI

@fnagel
Copy link
Member Author

fnagel commented Sep 30, 2016

Unit tests work fine in browser and cli when running locally. Any ideas?

Copy link
Member

@jzaefferer jzaefferer left a comment

Choose a reason for hiding this comment

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

It would be a bit easier to get these landed with a smaller scope. Mixing refactoring unit tests and implementation changes isn't necessary, as far as I can tell.

As for the failing tests, that looks like a timezone issue. I can reproduce it locally by running tests in a different timezone, like this:

TZ=:/usr/share/zoneinfo/America/Sao_Paulo npm test

Specifically for calendar/datepicker, we should start doing this by default.

module( "calendar: core", {
setup: function() {
element = $( "#calendar" ).calendar();
widget = element.calendar( "widget" );
Copy link
Member

Choose a reason for hiding this comment

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

You could assign this.widget = here, then reference this.widget inside the test, to avoid the top-level vars.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

}, 50 );
}

step1();
Copy link
Member

Choose a reason for hiding this comment

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

If this is called immediately, the function seems unnecessary. Could inline the setTimeout call here.

Copy link
Member

Choose a reason for hiding this comment

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

We do this a lot because it makes it easier to read the order of the test from top to bottom.

_handleKeydown: function( event ) {
var pageAltKey = ( event.altKey || event.ctrlKey && event.shiftKey );

switch ( event.keyCode ) {
case $.ui.keyCode.ENTER:
this.activeDescendant.mousedown();
case $.ui.keyCode.ENTER:
Copy link
Member

Choose a reason for hiding this comment

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

The indent shouldn't change.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

_select: function( event ) {
var oldValue = this.options.value ? this.options.value.getTime() : "";

this._setOption( "value", new Date( $( event.currentTarget ).data( "timestamp" ) ) );
Copy link
Member

Choose a reason for hiding this comment

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

Could this access this.activeDescendant instead? Would make the ENTER key event handler simpler, since it wouldn't need to pass the event along.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're suggesting to replace new Date( $( event.currentTarget ).data( "timestamp" ) ) with this.activeDescendant right?

Don't think so as this.activeDescendant would be the current day, not the selected one. this.activeDescendant will be updated in the next line, in _updateDayElement.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, makes sense.


if ( $.type( this.options.max ) === "string" ) {
this.options.max = Globalize.parseDate( this.options.max, { raw: "yyyy-MM-dd" } );
this.options.max = this._parse( this.options.max );
Copy link
Member

Choose a reason for hiding this comment

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

According to http://wiki.jqueryui.com/w/page/12137778/Datepicker both min and max should be date, not string. Is that document out of date?

If so, wasn't it on purpose that we used a fixed iso format for min/max as string, instead of matching the dateFormat option? With this change, the developer would need to update min/max whenever the (user facing) dateFormat option changes.

Copy link
Member

Choose a reason for hiding this comment

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

With the parsing happening in _getCreateOptions, I'm even more confused, since that uses a different format.

Since I doubt these changes are happening by accident, let's update the spec.

Copy link
Member Author

Choose a reason for hiding this comment

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

The current implementation is:

  1. min / max option passed as date
  2. min / max option passed as string: format value according to dateFormat option
  3. min / max attributes on input element: format value as yyyy-MM-dd (according to HTML specs)

I'm not able to find the reference in the GH comments, but we wanted it this way and I guess we need to update the wiki specs.

Copy link
Member

Choose a reason for hiding this comment

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

I recall the discussions about this, but I don't recall where the discussions occurred. The main thing we want to support is Date instances. I believe what we wanted to do for strings was parse the string into a date and store the date, not the string. For attributes, in order to support valid HTML5, we must follow the spec, and not support custom formatting; we'd still store the options as Date instances.

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe what we wanted to do for strings was parse the string into a date and store the date, not the string. For attributes, in order to support valid HTML5, we must follow the spec, and not support custom formatting; we'd still store the options as Date instances.

That's what we have now. We only store date objects and attributes formatting is hard coded.

Changing the wiki specs then, right?

Copy link
Member

Choose a reason for hiding this comment

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

The above makes sense. Let's update the spec with that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@@ -172,6 +176,9 @@ var widget = $.widget( "ui.datepicker", {
},
blur: function() {
this.suppressExpandOnFocus = false;
},
change: function( event ) {
Copy link
Member

Choose a reason for hiding this comment

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

Is the native change event reliable enough? Usually the answer to that is "no", may need to do a manual dirty-check in blur and trigger change from there.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure. Had massive issues creating reliable tests for this use case.

Checking the blur events is komplex due to the timeout handling. I wasn't able to prevent additional events from firing.

Copy link
Member

Choose a reason for hiding this comment

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

The native change event should be fine for user input. It's only programmatic value changes that would cause the event to not be fired.

@fnagel
Copy link
Member Author

fnagel commented Oct 3, 2016

It would be a bit easier to get these landed with a smaller scope. Mixing refactoring unit tests and implementation changes isn't necessary, as far as I can tell.

Totally agree. I will make sure next PRs are more specific.

As for the failing tests, that looks like a timezone issue. I can reproduce it locally by running tests in a different timezone [...]

Mhh, as always, it's not that easy when using windows. Not sure if it's even possible without changing system time globally... :-/

More important would be how to deal with this for the travis tests? Do we want to test multiple zones? Or just setting a specific one for the tests?

@jzaefferer
Copy link
Member

Mhh, as always, it's not that easy when using windows. Not sure if it's even possible without changing system time globally... :-/

Does Git Bash help? https://git-for-windows.github.io/

More important would be how to deal with this for the travis tests? Do we want to test multiple zones? Or just setting a specific one for the tests?

On another project where I ran into a similar problem, we decided to run the test suite twice, once on the CI machine's local timezone, once on a specific, different one. That way we make sure that the tests pass in different timezones. Not necessarily in all of them, but testing 2 is much better than just the default.

Its also possible that these issues only affect the testsuite, in which case it might be much more pragmatic to just set a fixed timezone.

} );

test( "change", function() {
expect( 6 );
Copy link
Member

Choose a reason for hiding this comment

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

This is using globals. Should use assert.expect() (assert gets passed as a parameter to the test() callback).

Looks like the datepicker branch is missing some updates to not allow the use of the QUnit globals. I guess we'll address that separately.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, as I'm already doing this for the custom dateEqual assert this makes sense to me.

Looks like the datepicker branch is missing some updates to not allow the use of the QUnit globals.

A merge with master is needed anyway when this PR is merged.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

equal(
event.originalEvent.type,
eventType,
"select originalEvent on calendar button " + eventType
Copy link
Member

Choose a reason for hiding this comment

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

s/select/change/

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

message = "on calendar button " + eventType;
element.find( "table button:eq(1)" ).simulate( eventType );
step2();
}, 50 );
Copy link
Member

Choose a reason for hiding this comment

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

Unwrap the timeouts inside the steps. Use the timeout for invoking the next step:

function step1() {
    eventType = "mousedown";
    message = "on calendar button " + eventType;
    element.find( "table button:eq(1)" ).simulate( eventType );
    setTimeout( step2, 50 );
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.


var date = new Date( 2015, 8 - 1, 1 );

// Number of month option does not work after init
Copy link
Member

@scottgonzalez scottgonzalez Oct 4, 2016

Choose a reason for hiding this comment

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

This needs to work. Can you just make this a TODO for now?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.


element.calendar( "option", "value", date );
assert.dateEqual( element.calendar( "option", "value" ), date, "Value set" );
equal( widget.find( "table button.ui-state-active" ).data( "timestamp" ), 1463954400000, "Active button timestamp" );
Copy link
Member

Choose a reason for hiding this comment

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

We need to prefix our data keys, so this should be ui-calendar-timestamp.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

equal( widget.find( "table button.ui-state-active" ).data( "timestamp" ), 1463954400000, "Active button timestamp" );

element.calendar( "option", "value", "invalid" );
assert.dateEqual( element.calendar( "option", "value" ), date, "Value after invalid parameter" );
Copy link
Member

@scottgonzalez scottgonzalez Oct 4, 2016

Choose a reason for hiding this comment

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

Perhaps we've discussed this before, but I don't remember talking through this. Is there a reason we leave the previous value instead of reverting to null? Native date inputs will clear their value if you try to set an invalid value.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, no reason afaik and I don't remember talking trough this. I'll see if this can be done easily within the _setOption method.

Copy link
Member Author

Choose a reason for hiding this comment

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

@scottgonzalez What about dates invalid due to min / max option? Upcoming change will include resetting value to null when it's not within min / max limit.

Copy link
Member

Choose a reason for hiding this comment

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

Those get reverted to null as well.

@@ -13,8 +13,7 @@
<div id="qunit"></div>
<div id="qunit-fixture">

<input type="text" id="datepicker">
<input type="text" id="datepicker2">
<input type="text" id="datepicker" />
Copy link
Member

Choose a reason for hiding this comment

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

Removing the trailing slash. We don't use XHTML.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@@ -79,30 +81,33 @@ var widget = $.widget( "ui.datepicker", {
_getCreateOptions: function() {
var max = this.element.attr( "max" ),
min = this.element.attr( "min" ),
parser = new Globalize( "en" ).dateParser( { raw: "yyyy-MM-dd" } ),
Copy link
Member

Choose a reason for hiding this comment

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

Not directly related to this PR, but does Globalize always contain the en data or do we need to tell users to always include this to handle the attribute parsing?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure, but I guess it needs to be included.

@rxaviers Can you help us out here?

Copy link
Member

Choose a reason for hiding this comment

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

For this raw pattern, the locale might not be required, since there's nothing locale-specific, I think. In general, Globalize doesn't bundle any locales.

Copy link
Member

@rxaviers rxaviers Oct 13, 2016

Choose a reason for hiding this comment

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

Actually, there's one detail.

since there's nothing locale-specific

For the above pattern, some stuff are locale independent, but some are locale-specific.

Locale independent:

  • Your pattern will result in numeric output only and given you're passing a raw format, the order of the fields are independent of any locale preferences.

Locale dependent:

  • The only difference you would get is numbering system, for example, by using Arabic you would can expect something like '٢٠١٦-١٠-١٣'.

Copy link
Member

Choose a reason for hiding this comment

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

Please, refresh my mind here? Is this parser being used to parse developer input? If so, I believe it's expected to be in English (or English POSIX, or root) form.

By the way, this is a very simple parsing, so it's also worth considering not using Globalize and instead split the string at '-' and constructing the Date using the array pieces.

Copy link
Member

Choose a reason for hiding this comment

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

+1 for manual parsing without Globalize

Copy link
Member Author

@fnagel fnagel Oct 24, 2016

Choose a reason for hiding this comment

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

When using something like

parser = function( value ) {
    var exploded = value.split( "-" );

    return new Date( exploded[ 0 ], exploded[ 1 ], exploded[ 2 ] );
},

I'm running into timezone issues:
image

Any ideas?

Copy link
Member

Choose a reason for hiding this comment

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

I think you need to set the correct offset for the month, since it is defined as "Integer value representing the month, beginning with 0 for January to 11 for December."

Copy link
Member

Choose a reason for hiding this comment

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

Like tests are littered with x - 1 for the month everywhere.

Copy link
Member Author

Choose a reason for hiding this comment

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

No idea why, but I was focused on the missing hour not the month. Stupid me.

Pushed a new commit with this.

@@ -172,6 +176,9 @@ var widget = $.widget( "ui.datepicker", {
},
blur: function() {
this.suppressExpandOnFocus = false;
},
change: function( event ) {
Copy link
Member

Choose a reason for hiding this comment

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

The native change event should be fine for user input. It's only programmatic value changes that would cause the event to not be fired.

},

_setOption: function( key, value ) {
if ( key === "max" || key === "min" ) {
if ( $.type( value ) === "string" ) {
Copy link
Member

Choose a reason for hiding this comment

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

Just use typeof.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure. Just for my curiosity: Why not using the internals?

Copy link
Member

Choose a reason for hiding this comment

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

jQuery's type detection is mostly unneeded in modern browsers. For strings, it was never needed. $.type() was just an abstraction away from the $.is*() methods which were limited to types that mattered.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the info.

Done.

@fnagel
Copy link
Member Author

fnagel commented Oct 4, 2016

Does Git Bash help? https://git-for-windows.github.io/

@jzaefferer Already using git bash, but tried power shell as well :-/

@fnagel
Copy link
Member Author

fnagel commented Oct 5, 2016

Added timezone / travis functionality as a todo task in wiki.

@jzaefferer
Copy link
Member

Can you get the test to fail locally by changing your system timezone? Have you tried running tests inside a Docker container? If not, I can help set that up.

@fnagel
Copy link
Member Author

fnagel commented Oct 12, 2016

Oh man, changing the system timezone via the UI works (but does not using similar console command). Seems this messes with some of local tools but for a quick test this seems to work.

@fnagel
Copy link
Member Author

fnagel commented Oct 12, 2016

Have you tried running tests inside a Docker container? If not, I can help set that up.

I'm using VirtualBox / Vagrant VMs for other projects but always tried to get around this for jQuery UI.
And hey, perhaps it's a good idea to have at least a one developer building the project on Windows, right?

@fnagel fnagel force-pushed the datepicker-working branch from aaac0d5 to 0891b50 Compare October 12, 2016 09:49
@fnagel
Copy link
Member Author

fnagel commented Oct 12, 2016

@jzaefferer @scottgonzalez Updated the branch. Let my know what you think!

@@ -56,7 +56,7 @@ test( "value", function( assert ) {
equal( this.element.calendar( "value" ), "1/1/14", "getter" );

this.element.calendar( "value", "abc" );
equal( this.element.calendar( "value" ), "1/1/14", "Setting invalid values should be ignored." );
equal( this.element.calendar( "value" ), null, "Setting invalid values." );
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be assert.equal?

Copy link
Member Author

@fnagel fnagel Oct 12, 2016

Choose a reason for hiding this comment

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

We are currently using this for assert.equalDate and assert.expect only. Do we want this for all qunit globals?

@@ -646,7 +646,8 @@ return $.widget( "ui.calendar", {
if ( arguments.length ) {
this.valueAsDate( this._parse( value ) );
} else {
return this._format( this.option( "value" ) );
return this.option( "value" ) === null ?
Copy link
Member

Choose a reason for hiding this comment

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

How about return this.option( "value" ) === null ? null : ...?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@jzaefferer
Copy link
Member

Updates look (mostly) good. Still need to fix the CI-failing test.

@fnagel fnagel force-pushed the datepicker-working branch from 0891b50 to bd488f9 Compare October 12, 2016 16:47
@scottgonzalez
Copy link
Member

You wan't me to reset the min / max option when trying to change their values to an invalid one? So basically the same functionality for min / max option, we now have for value option and method. Correct?

Correct.

@jzaefferer jzaefferer dismissed their stale review October 19, 2016 18:42

Probably outdated

@jzaefferer
Copy link
Member

I've looked at the timezone-dependent failure. When running the calendar tests with Sao Paulo timezone, the actual/expected values are only 4 seconds apart:

>> calendar: options - value
>> Message: Active button timestamp
>> Actual: 1463972400000
>> Expected: 1463954400000

> Date(1463972400000)
'Tue Oct 25 2016 12:07:23 GMT+0200 (CEST)'
> Date(1463954400000)
'Tue Oct 25 2016 12:07:27 GMT+0200 (CEST)'

Still not sure what's causing this. If we can't figure it out, we can probably make the test a little less precise. We don't care about the time component here, only the date, right?

@fnagel
Copy link
Member Author

fnagel commented Oct 25, 2016

We don't care about the time component here, only the date, right?

Correct, calendar and datepicker don't care about time at the moment, but we should keep in mind that timepicker addon is a widely used one.

@fnagel
Copy link
Member Author

fnagel commented Oct 25, 2016

@scottgonzalez Added invalid min / max reset incl. tests

@jzaefferer
Copy link
Member

Correct, calendar and datepicker don't care about time at the moment, but we should keep in mind that timepicker addon is a widely used one.

Let's keep the timestamp then, but update the test to only look at the date, not the time.

@fnagel
Copy link
Member Author

fnagel commented Oct 28, 2016

Let's keep the timestamp then, but update the test to only look at the date, not the time.

So, using our dateequal helper everywhere should help, as we already check for year / month / date only:
https://github.com/fnagel/jquery-ui/blob/datepicker-working/tests/lib/qunit-assert-dateequal.js

@fnagel
Copy link
Member Author

fnagel commented Oct 29, 2016

@jzaefferer @scottgonzalez

Fixed tests by replacing a timestamp compare with a dateEqual assrert.

Ready to merge?

@fnagel
Copy link
Member Author

fnagel commented Oct 29, 2016

CLA error?

this.widget.find( "table button.ui-state-active" ).data( "ui-calendar-timestamp" ),
1463954400000,
assert.dateEqual(
new Date( this.widget.find( "table button.ui-state-active" ).data( "ui-calendar-timestamp" ) / 1000 ),
Copy link
Member

Choose a reason for hiding this comment

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

This is odd - why is the timestamp data attribute in a different format than the one accepted by new Date? Shouldn't those match?

Copy link
Member Author

Choose a reason for hiding this comment

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

You'r right, both should be milliseconds. Changed,

@jzaefferer jzaefferer dismissed scottgonzalez’s stale review October 30, 2016 16:46

Only a +1/kudos comment remains

@jzaefferer
Copy link
Member

The new Foundation is still working on a new CLA check. Until that is ready we can ignore the check at least for team members.

@fnagel fnagel force-pushed the datepicker-working branch from 34faf02 to fb3841f Compare November 4, 2016 09:46
Fix travis unit test issue due to timezone difference.
@fnagel fnagel force-pushed the datepicker-working branch from fb3841f to 68028e7 Compare November 4, 2016 10:26
@fnagel
Copy link
Member Author

fnagel commented Nov 4, 2016

@jzaefferer So, merge ready now, right? I would merge master in datepicker and push that right away, ok?

@jzaefferer
Copy link
Member

Yep

@fnagel fnagel merged commit 68028e7 into jquery:datepicker Nov 9, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

6 participants