Skip to content

Commit

Permalink
Merge pull request #25 from RightCapitalHQ/feature/ensure-valid-datel…
Browse files Browse the repository at this point in the history
…ike-input

Enhanced Type Safety and Error Handling in DateHelpers
  • Loading branch information
rainx authored Oct 19, 2023
2 parents f9f529a + 815c828 commit 2c0a533
Show file tree
Hide file tree
Showing 18 changed files with 1,197 additions and 215 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ jobs:
- name: Prettier code style check
run: pnpm prettier . --check

- name: Build
run: pnpm run build

- name: Test
run: pnpm run test

Expand Down
3 changes: 3 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-typescript'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat(date-helpers): add type assertion for `DateLike` input",
"packageName": "@rightcapital/date-helpers",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "chore: update TypeScript config",
"packageName": "@rightcapital/exceptions",
"email": "[email protected]",
"dependentChangeType": "patch"
}
7 changes: 3 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/**
* @type {import('jest').Config}
*/
module.exports = {
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
coverageDirectory: './coverage/',
collectCoverage: true,
preset: 'ts-jest',
testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'],
};
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"prepare": "husky install",
"build": "pnpm run clean && pnpm -r --filter=./packages/** run build",
"clean": "pnpm -r --parallel --filter=./packages/** exec tsc --build --clean",
"test": "jest",
"pretest": "pnpm run build",
"test": "pnpm run pretest && jest",
"lint": "concurrently pnpm:lint:*",
"lint:eslint": "eslint --max-warnings=0 .",
"lint:prettier": "prettier -c .",
Expand All @@ -36,6 +37,8 @@
}
},
"devDependencies": {
"@babel/preset-env": "^7.23.2",
"@babel/preset-typescript": "^7.23.2",
"@commitlint/cli": "17.8.0",
"@commitlint/config-conventional": "17.8.0",
"@commitlint/cz-commitlint": "17.8.0",
Expand All @@ -50,7 +53,6 @@
"inquirer": "9.2.11",
"jest": "29.7.0",
"prettier": "3.0.3",
"ts-jest": "29.1.1",
"ts-node": "10.9.1",
"typescript": "5.2.2"
}
Expand Down
70 changes: 68 additions & 2 deletions packages/date-helpers/__tests__/date-helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,65 @@
import { parseISO } from 'date-fns';
import { DateHelpers } from '../src/date-helpers';
import { InvalidArgumentException } from '@rightcapital/exceptions';

const output = parseISO('2019-10-21');

describe('DateHelpers', () => {
describe('DateHelpers input validation', () => {
it('parseDateString should throw for unrecognized date formats', () => {
const invalidDateString = 'ABCDE';
expect(() => DateHelpers.parseDateString(invalidDateString)).toThrowError(
InvalidArgumentException,
);
});

it('parseDateString should throw error for null or undefined input', () => {
// @ts-expect-error - intentionally passing invalid input to test error handling
expect(() => DateHelpers.parseDateString(null)).toThrow(
'Input cannot be null or undefined.',
);

// @ts-expect-error - intentionally passing invalid input to test error handling
expect(() => DateHelpers.parseDateString(undefined)).toThrow(
'Input cannot be null or undefined.',
);
});

it('formatDateLikeToString should throw InvalidArgumentException for invalid date input', () => {
expect(() =>
DateHelpers.formatDateLikeToString(
'invalid date',
DateHelpers.isoDateFormat,
),
).toThrowError(InvalidArgumentException);
});

it('formatDateLikeToString should throw InvalidArgumentException for invalid date format', () => {
expect(() =>
DateHelpers.formatDateLikeToString(new Date(), 'invalid format'),
).toThrowError(InvalidArgumentException);
});

it('formatDateLikeToString should throw error for null, undefined, or invalid input', () => {
expect(() =>
// @ts-expect-error - intentionally passing invalid input to test error handling
DateHelpers.formatDateLikeToString(null, DateHelpers.isoDateFormat),
).toThrowError(InvalidArgumentException);
expect(() =>
// @ts-expect-error - intentionally passing invalid input to test error handling
DateHelpers.formatDateLikeToString(undefined, DateHelpers.isoDateFormat),
).toThrowError(InvalidArgumentException);
expect(() =>
// @ts-expect-error - intentionally passing invalid input to test error handling
DateHelpers.formatDateLikeToString({}, DateHelpers.isoDateFormat),
).toThrowError(InvalidArgumentException);
expect(() =>
// @ts-expect-error - intentionally passing invalid input to test error handling
DateHelpers.formatDateLikeToString(12345, DateHelpers.isoDateFormat),
).toThrowError(InvalidArgumentException);
});
});

describe('DateHelpers output validation', () => {
it('date string to Date object conversion test', () => {
expect(DateHelpers.parseDateString('10/21/19')).toStrictEqual(output);
});
Expand Down Expand Up @@ -35,12 +91,22 @@ describe('DateHelpers', () => {
).toBe('10/21/2019 00:00:00');
});

it('convertDateToMonthDayYearString should format dates correctly', () => {
it('formatDateLikeToUsLocaleMediumDateString should format dates correctly', () => {
expect(DateHelpers.formatDateLikeToUsLocaleMediumDateString(output)).toBe(
'Oct 21, 2019',
);
expect(
DateHelpers.formatDateLikeToUsLocaleMediumDateString('2019-10-21'),
).toBe('Oct 21, 2019');
});

it('formatDateLikeToString should return an empty string if the input is an empty string', () => {
const emptyString = '';
expect(
DateHelpers.formatDateLikeToString(
emptyString,
DateHelpers.isoDateFormat,
),
).toBe(emptyString);
});
});
7 changes: 0 additions & 7 deletions packages/date-helpers/jest.config.js

This file was deleted.

8 changes: 2 additions & 6 deletions packages/date-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@
"main": "lib/date-helpers.js",
"typings": "lib/date-helpers.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
"lib": "lib"
},
"files": [
"lib"
],
"scripts": {
"test": "jest",
"build": "tsc",
"build": "tsc --build tsconfig.build.json",
"docs": "pnpm exec typedoc --plugin typedoc-plugin-markdown --out docs src/date-helpers.ts",
"eslint": "eslint --cache 'src/**/*.ts*'",
"lint": "pnpm prettier && pnpm eslint",
Expand All @@ -39,11 +37,9 @@
"date-fns": "2.30.0"
},
"peerDependencies": {
"@sentry/browser": "7.x",
"date-fns": "^2.29.3"
},
"devDependencies": {
"@sentry/browser": "7.74.0",
"typedoc": "0.25.2",
"typedoc-plugin-markdown": "3.16.0"
}
Expand Down
41 changes: 29 additions & 12 deletions packages/date-helpers/src/date-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { InvalidArgumentException } from '@rightcapital/exceptions';
import { captureException, withScope, Scope } from '@sentry/browser';
import { format, isValid, parse, parseISO } from 'date-fns';

export type DateLike = Date | string;
Expand All @@ -22,6 +21,28 @@ export class DateHelpers {
/** Format for date strings in ISO format, e.g., "2023-12-31" */
public static isoDateFormat = 'yyyy-MM-dd';

private static ensureValidDateInput(
input: unknown,
): asserts input is DateLike {
if (input === null || input === undefined) {
throw new InvalidArgumentException(
`Input cannot be null or undefined. Received: ${input}`,
);
}

if (input instanceof Date) {
return; // It's a valid Date object.
}

if (typeof input === 'string') {
return; // It's a valid string.
}

throw new InvalidArgumentException(
`Input must be a Date object or a string. Received: ${typeof input} - ${input}`,
);
}

/**
* Converts a date string to a Date object using various possible formats.
*
Expand All @@ -36,6 +57,8 @@ export class DateHelpers {
* @throws {InvalidArgumentException} Throws an exception if the date string cannot be parsed.
*/
public static parseDateString(input: string): Date {
this.ensureValidDateInput(input);

let output = parseISO(input);

if (isValid(output)) {
Expand All @@ -59,17 +82,9 @@ export class DateHelpers {
});

if (!isValid(output)) {
withScope((scope: Scope) => {
scope.setLevel('error');
scope.setExtras({
input,
});
captureException(
new InvalidArgumentException(
`Invalid Date: unable to parse date string - ${input}. The input might not match any expected formats or is not a valid date.`,
),
);
});
throw new InvalidArgumentException(
`Invalid Date: unable to parse date string - ${input}. The input might not match any expected formats or is not a valid date.`,
);
}

return output;
Expand Down Expand Up @@ -104,6 +119,8 @@ export class DateHelpers {
dateInput: DateLike,
dateFormat: string,
): string {
this.ensureValidDateInput(dateInput);

// Do nothing with empty strings
if (dateInput === '') {
return dateInput;
Expand Down
9 changes: 9 additions & 0 deletions packages/date-helpers/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
},
"references": [{ "path": "../exceptions" }],
"include": ["./src"]
}
10 changes: 3 additions & 7 deletions packages/date-helpers/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"rootDir": ".",
"outDir": "./lib"
},
"references": [
{
"path": "../exceptions"
}
],
"include": ["./src"]
"references": [{ "path": "../exceptions" }],
"include": ["src", "__tests__"]
}
5 changes: 0 additions & 5 deletions packages/exceptions/jest.config.js

This file was deleted.

6 changes: 2 additions & 4 deletions packages/exceptions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
"lib": "lib"
},
"files": [
"lib"
],
"scripts": {
"test": "jest",
"build": "tsc",
"build": "tsc --build tsconfig.build.json",
"docs": "pnpm exec typedoc --plugin typedoc-plugin-markdown --out docs src/index.ts",
"eslint": "eslint --cache 'src/**/*.ts*'",
"lint": "pnpm prettier && pnpm eslint",
Expand Down
8 changes: 8 additions & 0 deletions packages/exceptions/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
},
"include": ["./src"]
}
Loading

0 comments on commit 2c0a533

Please sign in to comment.