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

Type-annotate and unit-test timeformat, fix off-by-one day in parseShortDate() #20655

Merged
merged 4 commits into from
Jun 26, 2024

Conversation

martinpitt
Copy link
Member

@martinpitt martinpitt commented Jun 24, 2024

This is a prerequisite for #20653 and also fixes an important bug.

@martinpitt martinpitt added the no-test For doc/workflow changes, or experiments which don't need a full CI run, label Jun 24, 2024
@martinpitt
Copy link
Member Author

I completed the unit tests, and hold and below, they found a bug! Fix added.

Now I just need to sort out the type checker.

@martinpitt martinpitt removed the no-test For doc/workflow changes, or experiments which don't need a full CI run, label Jun 25, 2024
@martinpitt martinpitt changed the title Type-annotate and unit-test timeformat Type-annotate and unit-test timeformat, fix off-by-one day in parseShortDate( Jun 25, 2024
@martinpitt martinpitt changed the title Type-annotate and unit-test timeformat, fix off-by-one day in parseShortDate( Type-annotate and unit-test timeformat, fix off-by-one day in parseShortDate() Jun 25, 2024
@martinpitt martinpitt force-pushed the time-tests branch 2 times, most recently from 3054bc5 to 803f048 Compare June 25, 2024 06:34
@martinpitt martinpitt marked this pull request as ready for review June 25, 2024 06:35
@martinpitt martinpitt added the release-blocker Targetted for next release label Jun 25, 2024
@allisonkarlitskaya
Copy link
Member

I completed the unit tests, and hold and below, they found a bug! Fix added.

I think you mean lo and behold :)

@martinpitt
Copy link
Member Author

This unfortunately "affects" just about every test, so this exposes all our flakes. /me leans on retry button

Copy link
Member

@allisonkarlitskaya allisonkarlitskaya left a comment

Choose a reason for hiding this comment

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

I'm personally insulted that you didn't choose America/Toronto in your manual testing.

Is there a way we can whack the TZ of the browser from inside so that we can get those cases under regression tests? Nobody is ever going to run that command ever again.

Otherwise, looks good except for the egregious use of an indexed access type.

@@ -31,7 +31,7 @@ import { DatePicker } from "@patternfly/react-core/dist/esm/components/DatePicke
import { TimePicker } from "@patternfly/react-core/dist/esm/components/TimePicker/index.js";

import { ServerTime } from 'serverTime.js';
import * as timeformat from "timeformat.js";
import * as timeformat from "timeformat";
Copy link
Member

Choose a reason for hiding this comment

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

We added a rule to Cockpit Files to forbid file extension on imports.....

Topic for another PR :)

@@ -8,47 +8,60 @@ import { parse, formatDistanceToNow } from 'date-fns';
import * as locales from 'date-fns/locale';

// this needs to be dynamic, as some pages don't initialize cockpit.language right away
export const dateFormatLang = () => cockpit.language.replace('_', '-');
export const dateFormatLang = (): string => cockpit.language.replace('_', '-');
Copy link
Member

Choose a reason for hiding this comment

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

Was it necessary to annotate the return type?

In general, return types can be omitted, and I usually do that. On the other hand, I've seen noises from "smart people" (including the TypeScript team) which suggest that type inference in other parts of the program will be faster if you explicitly annotate...

This isn't a request for change — just random musings.

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 don't think it was necessary -- but if we do this, IMHO we should explicitly type all external API, to make it more self-documenting and avoid ambiguity. I like the python mypy approach to require types in declarations, even if inference could figure them out.

Copy link
Member

Choose a reason for hiding this comment

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

I'm going to start writing explicit return types from now on.

const dateFnsLocale = (): any => (locales as any)[dateFormatLangDateFns()];

type DTFOptions = {
[key: string]: string;
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 that's spelled Intl.DateTimeFormatOptions.

In general it's helpful to go looking inside of node_modules/typescript/lib/ or node_modules/@types to see what this thing is "officially" called, and use 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.

Thanks! Fixed.

if (cockpit.language == "en") return "enUS";
else return cockpit.language.replace('_', '');
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dateFnsLocale = (): any => (locales as any)[dateFormatLangDateFns()];
Copy link
Member

Choose a reason for hiding this comment

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

naja... I guess that part is going away soon...

// 07:41 AM
export const time = (t: Time): string => formatter({ timeStyle: "short" }).format(t);
// 7:41:26 AM
export const timeSeconds = (t: Time): string => formatter({ timeStyle: "medium" }).format(t);
Copy link
Member

Choose a reason for hiding this comment

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

I demand coverage!!

// 07:41 AM
export const time = (t: Time): string => formatter({ timeStyle: "short" }).format(t);
// 7:41:26 AM
export const timeSeconds = (t: Time): string => formatter({ timeStyle: "medium" }).format(t);
Copy link
Member

Choose a reason for hiding this comment

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

... or the 🪓

assert.equal(timeformat.distanceToNow(now - 62 * DAY), "2 Monate");
});

QUnit.test("firstDayOfWeek", assert => {
Copy link
Member

Choose a reason for hiding this comment

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

ah right. It would be nice if our coverage reporting also covered the QUnit runs...

const first_dow_sun = ['en', 'ja', 'ko', 'pt', 'pt_BR', 'sv', 'zh_CN', 'zh_TW'];

export function firstDayOfWeek(): number {
return first_dow_sun.indexOf(cockpit.language) >= 0 ? 0 : 1;
Copy link
Member Author

Choose a reason for hiding this comment

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

that line is covered, just not both cases -- we dont' test the shutdown time or timer create dialog in non-English. But again, qunit test covers this.

We are about to rename it to *.ts.
Turn the optional `addSuffix` parameter of `distanceToNow()` into a
proper boolean, to match `formatDistanceToNow()`'s signature.

We have to apply an "any" crowbar for the "magic" locale import. But
that's not exposed in the API, just an internal implementation detail.
Pay special attention to the relative date formatting, as we want to
replace it (cockpit-project#20653). We don't need to replicate the exact behaviour, but
should have some coverage.

This finds a bug in parseShortDate(). Add a comment and skip the test.
@martinpitt
Copy link
Member Author

Is there a way we can whack the TZ of the browser from inside so that we can get those cases under regression tests?

Not from within test-timeformat.ts, as they all run in the same browser instance. There's a CDP call, but we don't have access to that from inside a QUnit test.

A possible entry point would be in test/pytest/test_browser.py , it could run the timeformat test in separate test-server/browser runs. It's a bit ad-hoc, but I agree it'd be worth it. I did that now, and it reproduces the bug very nicely (and confirms the fix). And yes, it uses A/Toronto now 😁

@martinpitt

This comment was marked as resolved.

Do the "strip off time and set to midnight" step already for the
reference Date input of parse(). Substracting it afterwards previously
caused the returned date to be off by one day for time zones other than
UTC.

Add `test_timeformat_timezones()` pytest to cover various time zones.
Copy link
Member

@allisonkarlitskaya allisonkarlitskaya left a comment

Choose a reason for hiding this comment

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

Nice little piece of work. Thanks!



# run test-timeformat.ts in different time zones: west/UTC/east
@pytest.mark.parametrize('tz', ['America/Toronto', 'Europe/London', 'UTC', 'Europe/Berlin', 'Australia/Sydney'])
Copy link
Member

Choose a reason for hiding this comment

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

Cool.

test-timeformat is also going to run from the normal test_browser() path, but I guess that's good: then we get the version without explicit TZ set. It also means that pytest -k browser will still hit it (at least for the one case) but won't get bogged down with having to do the exhausting checking.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, I deliberately didn't ignore it from test_browser(), it's also nice to test in your current time zone.

@@ -8,47 +8,60 @@ import { parse, formatDistanceToNow } from 'date-fns';
import * as locales from 'date-fns/locale';

// this needs to be dynamic, as some pages don't initialize cockpit.language right away
export const dateFormatLang = () => cockpit.language.replace('_', '-');
export const dateFormatLang = (): string => cockpit.language.replace('_', '-');
Copy link
Member

Choose a reason for hiding this comment

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

I'm going to start writing explicit return types from now on.

// general Intl.DateTimeFormat formatter object
export const formatter = options => new Intl.DateTimeFormat(dateFormatLang(), options);
export const formatter = (options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat(dateFormatLang(), options);
Copy link
Member

Choose a reason for hiding this comment

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

Much better, thanks!

// 07:41 AM
export const time = (t: Time): string => formatter({ timeStyle: "short" }).format(t);
// 7:41:26 AM
export const timeSeconds = (t: Time): string => formatter({ timeStyle: "medium" }).format(t);
Copy link
Contributor

Choose a reason for hiding this comment

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

This added line is not executed by any test.

const first_dow_sun = ['en', 'ja', 'ko', 'pt', 'pt_BR', 'sv', 'zh_CN', 'zh_TW'];

export function firstDayOfWeek(): number {
return first_dow_sun.indexOf(cockpit.language) >= 0 ? 0 : 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

This added line is not executed by any test.

@martinpitt martinpitt merged commit 81bc367 into cockpit-project:main Jun 26, 2024
81 of 82 checks passed
@martinpitt martinpitt deleted the time-tests branch June 26, 2024 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release-blocker Targetted for next release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants