Skip to content

Commit

Permalink
Polishing changes
Browse files Browse the repository at this point in the history
  • Loading branch information
hudson-newey committed Dec 19, 2023
1 parent e48ba51 commit f1532c8
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { OnChanges, Component } from "@angular/core";
import { NgbTooltipModule } from "@ng-bootstrap/ng-bootstrap";

// use use a standalone component here so that it implements all its
@Component({
selector: "baw-abstract-datetime",
templateUrl: "./abstract-datetime.component.html",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const testCases: TestCase[] = [
name: "should display the full date and time in the users local timezone by default",
// create a Luxon DateTime object in UTC+00:00
// we should see this date be localized to UTC+08:00 (the test runners timezone)
// we do not call setZone() here like the other tests because we want to create the DateTime object in UTC+00:00
// so that we can see it localized to UTC+08:00
value: DateTime.fromISO("2020-01-01T12:10:11.000Z"),
expectedText: "2020-01-01 20:10:11",
expectedTooltip: "2020-01-01 20:10:11.000 Australia/Perth +08:00",
Expand All @@ -59,14 +61,30 @@ const testCases: TestCase[] = [
// most of the tests create the DateTime objects in UTC+00:00
// for this test, we create th DateTime object in UTC+02:00
// we should therefore see the hours increase by 6
name: "should display the correct dateTime if the Luxon dateTime object is a different timezone to the users local timezone",
name: "should display the correct dateTime if the Luxon dateTime object is a different utc offset to the users local timezone",
// by using +02:00, we know the offset, but not the timezone
// however, because this value will be localized to the users local timezone, we should see the timezone emitted
// in the users local timezone
value: DateTime.fromISO("2020-01-01T12:10:11.000+02:00"),
expectedText: "2020-01-01 18:10:11",
// notice that the tooltips are still in the test runners mock timezone
// this is because the emitted date/time has been localized to the test runners timezone
expectedTooltip: "2020-01-01 18:10:11.000 Australia/Perth +08:00",
expectedDateTimeAttribute: "2020-01-01T18:10:11.000+08:00",
},
{
// this test is the same as the previous offset test, except that we know both the timezone and offset
// in this test. (as opposed to the previous test where we only knew the offset)
name: "should display the correct DateTime if the Luxon dateTime object is a different timezone to the users local timezone",
// by using Australia/Sydney, we know the timezone and offset
value: DateTime.fromISO("2020-01-01T12:10:11.000Z").setZone(
"Australia/Sydney",
{ keepLocalTime: true }
),
expectedText: "2020-01-01 09:10:11",
expectedTooltip: "2020-01-01 09:10:11.000 Australia/Perth +08:00",
expectedDateTimeAttribute: "2020-01-01T09:10:11.000+08:00",
},
{
name: "should increment date if timezone localization causes the date to increment",
value: DateTime.fromISO("2020-01-01T23:10:11.000Z"),
Expand Down Expand Up @@ -99,13 +117,6 @@ const testCases: TestCase[] = [
timeOnly: true,
expectedDateTimeAttribute: "2020-01-01T20:10:11.000+08:00",
},
{
name: "should have a tooltip that displays the full un-localized utc date, utc time, and utc offset for a Luxon DateTime object",
value: DateTime.fromISO("2020-01-01T12:10:11.000Z"),
expectedText: "2020-01-01 20:10:11",
expectedTooltip: "2020-01-01 20:10:11.000 Australia/Perth +08:00",
expectedDateTimeAttribute: "2020-01-01T20:10:11.000+08:00",
},
{
name: "should localize a Luxon DateTime object with a timezone offset",
value: DateTime.fromISO("2020-01-01T12:10:11.000+01:00"),
Expand Down Expand Up @@ -134,17 +145,11 @@ const testCases: TestCase[] = [
expectedTooltip: "2020-01-01 19:10:11.000 Australia/Perth +08:00",
expectedDateTimeAttribute: "2020-01-01T19:10:11.000+08:00",
},
{
name: "should have the correct tooltip for a Luxon DateTime object with an offset, but no timezone",
value: new Date("2020-01-01T12:10:11.000+01:00"),
expectedText: "2020-01-01 19:10:11",
expectedTooltip: "2020-01-01 19:10:11.000 Australia/Perth +08:00",
expectedDateTimeAttribute: "2020-01-01T19:10:11.000+08:00",
},
];

describe("DatetimeComponent", () => {
let spectator: Spectator<DatetimeComponent>;
let testRunnersDefaultTimezone;

const createComponent = createComponentFactory({
component: DatetimeComponent,
Expand All @@ -171,6 +176,9 @@ describe("DatetimeComponent", () => {

beforeEach(() => setup());

beforeAll(() => testRunnersDefaultTimezone = Settings.defaultZone);
afterAll(() => Settings.defaultZone = testRunnersDefaultTimezone);

it("should create", () => {
const fakeDateTime = DateTime.fromISO("2020-01-01T12:10:11.000Z");
spectator.component.value = fakeDateTime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export class DatetimeComponent extends AbstractDatetimeComponent {
}

public update(): void {
const fullDateTime = this.luxonDateTime.toFormat("yyyy-MM-dd HH:mm:ss");
// the fullDateTime is used regardless of format as it is used in the tooltip
// it emits as much information as possible
const fullDateTime = this.luxonDateTime.toFormat("yyyy-MM-dd HH:mm:ss.SSS");
const timezoneName = this.longTimezone();

// we do not place the timezoneName in brackets as the ngx-bootstrap tooltip
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ const testCases: TestCase[] = [
expectedTooltip: "00:10:01.500 (PT10M1.5S)",
expectedDateTimeAttribute: "PT10M1.5S",
humanized: true,
}
},
];

describe("DurationComponent", () => {
let spectator: Spectator<DurationComponent>;
let originalDefaultZone;

const createComponent = createComponentFactory({
component: DurationComponent,
Expand All @@ -93,13 +94,17 @@ describe("DurationComponent", () => {

beforeEach(() => setup());

beforeAll(() => (originalDefaultZone = Settings.defaultZone));
afterAll(() => (Settings.defaultZone = originalDefaultZone));

it("should create", () => {
spectator.component.value = modelData.time();
expect(spectator.component).toBeInstanceOf(DurationComponent);
});

it("should throw a console error if both iso8601 and humanized props flags are set", () => {
const expectedErrorMessage = "baw-duration: cannot use both iso8601 and humanized";
const expectedErrorMessage =
"baw-duration: cannot use both iso8601 and humanized";
console.error = jasmine.createSpy("error");

spectator.component.value = modelData.time();
Expand Down Expand Up @@ -128,15 +133,19 @@ describe("DurationComponent", () => {
});

it("should have the correct text", () => {
expect(componentElement()).toHaveExactTrimmedText(testCase.expectedText);
expect(componentElement()).toHaveExactTrimmedText(
testCase.expectedText
);
});

it("should have the correct tooltip", () => {
assertTooltip(componentElement(), testCase.expectedTooltip);
});

it("should have the correct dateTime attribute", () => {
expect(componentElement().dateTime).toBe(testCase.expectedDateTimeAttribute);
expect(componentElement().dateTime).toBe(
testCase.expectedDateTimeAttribute
);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ interface TestCase {
expectedDateTimeAttribute: string;
}

// TODO: I recently changed the tooltips for this component and therefore I haven't created the
// test functionality to assert the tooltips
const testCases: TestCase[] = [
{
name: "should format a duration correctly into a relative time",
Expand All @@ -26,7 +24,7 @@ const testCases: TestCase[] = [
// the test runners fake date/time is midnight at 2020-01-01
// therefore, by subtracting 2 hours and 10 minutes from that date/time
// we get 2019-12-31 21:50:00 (the expected tooltip)
expectedTooltip: "2019-12-31 21:50:00 +00:00",
expectedTooltip: "2019-12-31 21:50:00.000 +00:00",
expectedDateTimeAttribute: "PT2H10M",
},
{
Expand All @@ -39,51 +37,59 @@ const testCases: TestCase[] = [
}),
expectedText: "4 hours 42 minutes ago",
// both the tooltip and the machine readable dateTime attribute should include seconds and milliseconds
expectedTooltip: "2019-12-31 19:17:49 +00:00",
expectedTooltip: "2019-12-31 19:17:49.500 +00:00",
expectedDateTimeAttribute: "PT4H42M10.5S",
},
{
name: "should handle negative durations correctly",
value: Duration.fromObject({ hours: -2, minutes: -10 }),
expectedText: "2 hours 10 minutes from now",
expectedTooltip: "2020-01-01 02:10:00 +00:00",
expectedTooltip: "2020-01-01 02:10:00.000 +00:00",
expectedDateTimeAttribute: "PT-2H-10M",
},
{
name: "should be take a JavaScript Date object and emit how long ago it was in a human readable format",
value: new Date("2019-11-13T14:17:58.000Z"),
expectedText: "1 month 3 weeks ago",
// because all JavaScript dates only support utc and local timezones, we create this test JavaScript Date object in utc
expectedTooltip: "2019-11-13 14:17:58 +00:00",
expectedDateTimeAttribute: "P1M2W6DT9H42M2S",
},
{
name: "should be take a Luxon DateTime object and emit how long ago it was in a human readable format",
value: DateTime.fromISO("2019-11-13T14:17:58.000Z", { zone: "utc" }),
expectedText: "1 month 3 weeks ago",
expectedTooltip: "2019-11-13 14:17:58 +00:00",
expectedTooltip: "2019-11-13 14:17:58.000 +00:00",
expectedDateTimeAttribute: "P1M2W6DT9H42M2S",
},
{
name: "should localize a Luxon dateTime object to the users timezone",
value: DateTime.fromISO("2019-12-31T04:17:58.000Z", { zone: "Australia/Darwin" }),
expectedText: "19 hours 42 minutes ago",
expectedTooltip: "2019-12-31 13:47:58 Australia/Darwin +09:30",
expectedTooltip: "2019-12-31 13:47:58.000 Australia/Darwin +09:30",
expectedDateTimeAttribute: "PT19H42M2S",
},
{
name: "should be take a JavaScript Date object and emit how long ago it was in a human readable format",
value: new Date("2019-11-13T14:17:58.000Z"),
expectedText: "1 month 3 weeks ago",
// because all JavaScript dates only support utc and local timezones, we create this test JavaScript Date object in utc
expectedTooltip: "2019-11-13 14:17:58.000 +00:00",
expectedDateTimeAttribute: "P1M2W6DT9H42M2S",
},
{
name: "should take a JavaScript Date object with miliseconds and emit how long ago it was in a human readable format",
value: new Date("2019-11-13T14:17:58.500Z"),
expectedText: "1 month 3 weeks ago",
expectedTooltip: "2019-11-13 14:17:58.500 +00:00",
expectedDateTimeAttribute: "P1M2W6DT9H42M1.5S",
}
];

describe("RelativeTimeComponent", () => {
let spectator: Spectator<RelativeTimeComponent>;
// because we monkey patch this function for testing, we need to revert the
// changes after the test is run so that it doesn't affect other future tests
let originalDateTimeNow: () => DateTime;

const createComponent = createComponentFactory({
component: RelativeTimeComponent,
imports: [SharedModule, MockBawApiModule],
});

function setup(): void {
// because some tests assert the difference between a date and "now", we need to mock "now"
// so that the tests are consistent and don't fail because of a difference of a few milliseconds due to execution time
Settings.defaultZone = "utc";
DateTime.now = jasmine
.createSpy("now")
Expand All @@ -103,6 +109,11 @@ describe("RelativeTimeComponent", () => {

beforeEach(() => setup());

// because some tests assert the difference between a date and "now", we need to mock "now"
// so that the tests are consistent and don't fail because of a difference of a few milliseconds due to execution time
beforeAll(() => originalDateTimeNow = DateTime.now);
afterAll(() => DateTime.now = originalDateTimeNow);

it("should create", () => {
spectator.component.value = modelData.time();
expect(spectator.component).toBeInstanceOf(RelativeTimeComponent);
Expand All @@ -112,7 +123,7 @@ describe("RelativeTimeComponent", () => {
Settings.defaultZone = "Australia/Perth";

const fakeDuration = Duration.fromObject({ hours: 2, minutes: 10 });
const expectedTooltip = "2019-12-31 21:50:00 Australia/Perth +08:00";
const expectedTooltip = "2019-12-31 21:50:00.000 Australia/Perth +08:00";

spectator.component.value = fakeDuration;
update();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,23 @@ export class RelativeTimeComponent extends AbstractDatetimeComponent {
return durationDifference.rescale();
}

// because this component can accept a Luxon DateTime, JavaScript Date, or Luxon Duration object
// we want to standardize the shape that we use throughout this component
// therefore, we convert all the formats to a Luxon DateTime object
private get luxonStartDate(): DateTime {
if (this.value instanceof DateTime) {
return this.value;
} else if (this.value instanceof Date) {
return DateTime.fromJSDate(this.value);
}

return this.value instanceof Date
? DateTime.fromJSDate(this.value)
: DateTime.now().minus(this.durationValue);
return DateTime.now().minus(this.durationValue);
}

public update(): void {
const formattedStartDate = this.luxonStartDate.toFormat("yyyy-MM-dd HH:mm:ss");
// we include milliseconds in the tooltip, so that we can display as much
// information as possible in the tooltip
const formattedStartDate = this.luxonStartDate.toFormat("yyyy-MM-dd HH:mm:ss.SSS");
const timezoneInformation = this.longTimezone();

this.tooltipValue = `${formattedStartDate} ${timezoneInformation}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const testCases: TestCase[] = [
{
// in this test, the explicit and implicit timezones are the same
// therefore, we should never see any localization occur
name: "should not localize the date/time",
name: "should not localize the date/time that is in the test runners timezone",
value: DateTime.fromISO("2020-01-01T12:10:11.000Z").setZone(
"Australia/Darwin", // UTC+09:30
{ keepLocalTime: true }
Expand Down Expand Up @@ -101,7 +101,7 @@ const testCases: TestCase[] = [
timeOnly: true,
},
{
name: "should use the correct tooltip when we know the offset, but not timezone",
name: "should use the correct information when we know the explicit offset, but not timezone",
// we should see that the implicit tooltip has timezone information as we can
// derive it from the implicit zone information.
// however, when we provide an offset to the explicit timezone, we should see that the tooltip
Expand Down Expand Up @@ -161,6 +161,21 @@ const testCases: TestCase[] = [
expectedExplicitTooltip: "2020-01-01 12:10:11.123 Australia/Darwin +09:30",
expectedExplicitDateTimeAttribute: "2020-01-01T12:10:11.123+09:30",
},
{
name: "should handle fractional JavaScript datetime's correctly",
value: new Date("2020-01-01T12:10:11.123Z"),
explicitTimezone: "Australia/Darwin",

// the dom text should not include the fractional seconds
// however, the tooltip should include the fractional seconds
expectedImplicitText: "2020-01-01 12:10:11",
expectedImplicitTooltip: "2020-01-01 12:10:11.123 +00:00",
expectedImplicitDateTimeAttribute: "2020-01-01T12:10:11.123Z",

expectedExplicitText: "2020-01-01 21:40:11",
expectedExplicitTooltip: "2020-01-01 21:40:11.123 Australia/Darwin +09:30",
expectedExplicitDateTimeAttribute: "2020-01-01T21:40:11.123+09:30",
},
// the JavaScript date is created in utc time
// therefore, we don't expect any timezone information on the tooltip
// but when the timezone is overridden with an explicit timezone
Expand Down Expand Up @@ -208,6 +223,21 @@ const testCases: TestCase[] = [

timeOnly: true,
},
{
name: "should localize an implicit JavaScript timezone to the explicit timezone",
// because we create the JavaScript date in utc time, we expect the explicit time to
// increase by 9:30 hours (the difference between utc and Australia/Darwin)
value: new Date("2020-01-01T12:10:11.000Z"),
explicitTimezone: "Australia/Darwin",

expectedImplicitText: "2020-01-01 12:10:11",
expectedImplicitTooltip: "2020-01-01 12:10:11.000 +00:00",
expectedImplicitDateTimeAttribute: "2020-01-01T12:10:11.000Z",

expectedExplicitText: "2020-01-01 21:40:11",
expectedExplicitTooltip: "2020-01-01 21:40:11.000 Australia/Darwin +09:30",
expectedExplicitDateTimeAttribute: "2020-01-01T21:40:11.000+09:30",
},
];

/*
Expand All @@ -225,6 +255,7 @@ const testCases: TestCase[] = [
*/
describe("ZonedDateTimeComponent", () => {
let spectator: Spectator<ZonedDateTimeComponent>;
let originalDefaultZone;

const createComponent = createComponentFactory({
component: ZonedDateTimeComponent,
Expand All @@ -246,6 +277,9 @@ describe("ZonedDateTimeComponent", () => {

beforeEach(() => setup());

beforeAll(() => originalDefaultZone = Settings.defaultZone);
afterAll(() => Settings.defaultZone = originalDefaultZone);

it("should create", () => {
spectator.component.value = modelData.dateTime();
update();
Expand Down

0 comments on commit f1532c8

Please sign in to comment.