-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Conversation
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. |
3054bc5
to
803f048
Compare
I think you mean lo and behold :) |
This unfortunately "affects" just about every test, so this exposes all our flakes. /me leans on retry button |
There was a problem hiding this 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"; |
There was a problem hiding this comment.
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('_', '-'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
pkg/lib/timeformat.ts
Outdated
const dateFnsLocale = (): any => (locales as any)[dateFormatLangDateFns()]; | ||
|
||
type DTFOptions = { | ||
[key: string]: string; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()]; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
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 😁 |
This comment was marked as resolved.
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.
There was a problem hiding this 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']) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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('_', '-'); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
This is a prerequisite for #20653 and also fixes an important bug.