Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 13 additions & 14 deletions package/src/lib/DateAssertion.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Assertion } from "./Assertion";
import { DateMethod, DateOptions, DayOfWeek } from "./DateAssertion.types";
import { dateOptionsToDate, dayOfWeekAsNumber } from "./helpers/dates";
import { optionsToDate, dayOfWeekAsNumber, dateToOptions } from "./helpers/dates";

import { AssertionError } from "assert";

const DATE_METHOD_MAP: Record<keyof DateOptions, DateMethod> = {
day: "getDay",
hours: "getHours",
miliseconds: "getMilliseconds",
milliseconds: "getMilliseconds",
minutes: "getMinutes",
month: "getMonth",
seconds: "getSeconds",
Expand Down Expand Up @@ -64,7 +64,7 @@ export class DateAssertion extends Assertion<Date> {
* Check if two dates are equal or partially equal
* by using a configuration object that can contain
* optional specifications for: year, month, day, hour,
* minutes, seconds and miliseconds, equals the actual date.
* minutes, seconds and milliseconds, equals the actual date.
* The test fails when the value of one of the specifications
* doesn't match the actual date.
*
Expand All @@ -73,7 +73,7 @@ export class DateAssertion extends Assertion<Date> {
* const septemberTenth2022 = new Date(2022, 8, 10);
*
* expect(octoberTenth2022).toMatchDateParts({
* month: 8,
* month: "august", // or just `8`
* year:2022,
* });
* ```
Expand All @@ -82,23 +82,22 @@ export class DateAssertion extends Assertion<Date> {
* @returns the assertion instance
*/
public toMatchDateParts(options: DateOptions): this {
const optionsAsDate = dateOptionsToDate(options);
const assertWhen = Object.keys(options).every(key => {
const dateMethod = DATE_METHOD_MAP[key];
return optionsAsDate[dateMethod]() === this.actual[dateMethod]();
});
const optionsAsDate = optionsToDate(options);
const error = new AssertionError({
actual: this.actual,
actual: dateToOptions(this.actual, options),
expected: options,
message: `Expected <${this.actual.toISOString()}> to be equal to <${optionsAsDate.toISOString()}>`,
message: `Expected <${this.actual.toISOString()}> to have parts <${JSON.stringify(options)}>`,
});
const invertedError = new AssertionError({
actual: this.actual,
message: `Expected <${this.actual.toISOString()}> NOT to be equal to <${optionsAsDate.toISOString()}>`,
actual: dateToOptions(this.actual, options),
message: `Expected <${this.actual.toISOString()}> NOT to have parts <${JSON.stringify(options)}>`,
});

return this.execute({
assertWhen,
assertWhen: Object.keys(options).every(key => {
const dateMethod = DATE_METHOD_MAP[key];
return optionsAsDate[dateMethod]() === this.actual[dateMethod]();
}),
error,
invertedError,
});
Expand Down
2 changes: 1 addition & 1 deletion package/src/lib/DateAssertion.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type DateMethod = {
export interface DateOptions {
day?: DayOfWeek | number;
hours?: number;
miliseconds?: number;
milliseconds?: number;
minutes?: number;
month?: Month | number;
seconds?: number;
Expand Down
146 changes: 102 additions & 44 deletions package/src/lib/helpers/dates.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,108 @@
import { DateOptions, DayOfWeek, Month } from "../DateAssertion.types";

const DAYS_OF_WEEK: DayOfWeek[] = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];

const MONTHS: Month[] = [
"january",
"february",
"march",
"april",
"may",
"june",
"july",
"august",
"september",
"october",
"november",
"december",
];

/**
* Provides a numeric representation of a day of the week string. The number is
* consistent with JavaScript's {@link Date}, so it's zero-based and starts
* from Sunday = `0` to Saturday = `6`.
*
* @param day a day of the week string
* @returns a number representing the day of the week
*/
export function dayOfWeekAsNumber (day: DayOfWeek): number {
switch (day) {
case "sunday": return 0;
case "monday": return 1;
case "tuesday": return 2;
case "wednesday": return 3;
case "thursday": return 4;
case "friday": return 5;
case "saturday": return 6;
}
}
return DAYS_OF_WEEK.indexOf(day);
}

export function monthOfYear (month: Month): number {
switch (month) {
case "january": return 0;
case "february": return 1;
case "march": return 2;
case "april": return 3;
case "may": return 4;
case "june": return 5;
case "july": return 6;
case "august": return 7;
case "september": return 8;
case "october": return 9;
case "november": return 10;
case "december": return 11;
}
}
/**
* Provides a numeric representation of a month string. The number is consistent
* with JavaScript's {@link Date}, so it's zero-based and starts from
* January = `0` to December = `11`.
*
* @param month a month string
* @returns a number representing the month
*/
export function monthOfYear (month: Month): number {
return MONTHS.indexOf(month);
}

export function optionsToDate(options: DateOptions): Date {
const {
year = 0,
month = 0,
day = 0,
hours = 0,
minutes = 0,
seconds = 0,
milliseconds = 0,
} = options;
const monthAsNum = typeof month === "string"
? monthOfYear(month) + 1
: month;
const dayAsNum = typeof day === "string"
? dayOfWeekAsNumber(day)
: day;

export function dateOptionsToDate(options: DateOptions): Date {
const { year, month, day, hours, minutes, seconds, miliseconds } = options;
const monthAsNum = typeof month === "string"
? monthOfYear(month)
: month;
const dayAsNum = typeof day === "string"
? dayOfWeekAsNumber(day)
: day;
const today = new Date();
return new Date(
year ?? today.getFullYear(),
monthAsNum ?? today.getMonth(),
dayAsNum ?? today.getDate(),
hours ?? today.getHours(),
minutes ?? today.getMinutes(),
seconds ?? today.getSeconds(),
miliseconds ?? today.getMilliseconds(),
);
return new Date(
year,
monthAsNum,
dayAsNum,
hours,
minutes,
seconds,
milliseconds,
);
}

export function dateToOptions(date: Date, sample?: DateOptions): DateOptions {
const options = {
day: date.getDate(),
hours: date.getHours(),
milliseconds: date.getMilliseconds(),
minutes: date.getMinutes(),
month: date.getMonth(),
seconds: date.getSeconds(),
year: date.getFullYear(),
};

if (sample !== undefined) {
return Object.keys(sample).reduce((acc, key) => {
const dayOrMonth = key === "day"
? DAYS_OF_WEEK[date.getDay()]
: MONTHS[date.getMonth()];
const value = typeof sample[key] === "string"
? dayOrMonth
: options[key];

return {
...acc,
[key]: value,
};
}, { } as DateOptions);
}

return options;
}
21 changes: 8 additions & 13 deletions package/test/lib/DateAssertion.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import dedent from "@cometlib/dedent";

import { DateAssertion } from "../../src/lib/DateAssertion";
import { dateOptionsToDate, dayOfWeekAsNumber } from "../../src/lib/helpers/dates";
import { DateOptions } from "../../src/lib/DateAssertion.types";
import { dayOfWeekAsNumber } from "../../src/lib/helpers/dates";

import assert, { AssertionError } from "assert";

Expand Down Expand Up @@ -47,10 +48,10 @@ describe("[Unit] DateAssertion.test.ts", () => {
context("when the actual date matches the passed date", () => {
it("returns the assertion instance", () => {
const actualDate = new Date(2021, 1, 1, 12, 10, 15, 25);
const options = {
const options: DateOptions = {
day: 1,
hours: 12,
miliseconds: 25,
milliseconds: 25,
minutes: 10,
month: 1,
seconds: 15,
Expand All @@ -59,10 +60,7 @@ describe("[Unit] DateAssertion.test.ts", () => {
const test = new DateAssertion(actualDate);
assert.deepStrictEqual(test.toMatchDateParts(options), test);
assert.throws(() => test.not.toMatchDateParts(options), {
message: dedent`
Expected <${actualDate.toISOString()}> NOT to be equal to \
<${dateOptionsToDate(options).toISOString()}>
`,
message: `Expected <${actualDate.toISOString()}> NOT to have parts <${JSON.stringify(options)}>`,
name: AssertionError.name,
});
});
Expand All @@ -81,21 +79,18 @@ describe("[Unit] DateAssertion.test.ts", () => {
context("when the actual date is NOT equal to the passed date", () => {
it("throws an assertion error", () => {
const actualDate = new Date(2021, 1, 1, 12, 10, 15, 25);
const options = {
const options: DateOptions = {
day: 1,
hours: 12,
miliseconds: 24,
milliseconds: 24,
minutes: 10,
month: 1,
seconds: 15,
year: 2021,
};
const test = new DateAssertion(actualDate);
assert.throws(() => test.toMatchDateParts(options), {
message: dedent`
Expected <${actualDate.toISOString()}> to be equal to \
<${dateOptionsToDate(options).toISOString()}>
`,
message: `Expected <${actualDate.toISOString()}> to have parts <${JSON.stringify(options)}>`,
name: AssertionError.name,
});
assert.deepStrictEqual(test.not.toMatchDateParts(options), test);
Expand Down
Loading