Skip to content

Commit

Permalink
dates + tests (#3)
Browse files Browse the repository at this point in the history
* add isDate()
* refine
* more tests
* more config
  • Loading branch information
mrgeorgegray authored Feb 22, 2021
1 parent 4232796 commit a0aeb8e
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 45 deletions.
10 changes: 9 additions & 1 deletion jest.config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
{
"rootDir": "./",
"collectCoverageFrom": [
"<rootDir>/src/**/*.ts",
"!<rootDir>/src/index.ts",
"!**/*.d.ts",
"!**/node_modules/**",
"!**/vendor/**"
],
"testMatch": [
"<rootDir>/tests/**/*(*.)@(spec|test).{ts,tsx}"
"<rootDir>/tests/**/*(*.)@(spec|test).ts"
],
"transform": {
"^.+\\.(ts|tsx)$": "ts-jest"
Expand Down
46 changes: 28 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
{
"name": "@mrgeorgegray/dawn-till-dusk",
"name": "dawn-till-dusk",
"version": "0.0.1",
"description": "See sunrise & sunset times 'from dawn till dusk' on your command line",
"keywords": [
"sunrise"
],
"main": "lib/index.js",
"engines": {
"node": "^12 || ^14 || ^15"
},
"main": "lib/index.js",
"preferGlobal": true,
"bin": {
"dtd": "lib/index.js"
},
"scripts": {
"build": "cross-env tsc -p tsconfig.build.json && chmod +x lib/index.js",
"prepare": "cross-env npm run build",
"prepublishOnly": "cross-env npm test && npm run lint",
"dtd": "cross-env ./lib/index.js",
"format": "cross-env prettier --write \"{src,test}/**/*.{js,jsx,ts,tsx}\"",
"lint": "cross-env eslint .",
"build": "tsc -p tsconfig.build.json && chmod +x lib/index.js",
"prepare": "npm run build",
"prepublishOnly": "npm test && npm run lint",
"preversion": "npm run lint",
"version": "npm run format && git add -A src",
"postversion": "git push && git push --tags",
"dtd": "./lib/index.js",
"format": "prettier --write \"{src,test}/**/*.{js,jsx,ts,tsx}\"",
"lint": "eslint .",
"test": "cross-env TZ=utc jest",
"test:coverage": "cross-env TZ=utc jest --coverage"
},
"repository": {
"type": "git",
"url": "git://github.com/mrgeorgegray/dawn-till-dusk"
},
"keywords": [
"sunrise"
],
"author": "George Gray",
"license": "MIT",
"preferGlobal": true,
"bin": {
"dtd": "lib/index.js"
"bugs": {
"url": "https://github.com/mrgeorgegray/dawn-till-dusk/issues"
},
"homepage": "https://github.com/mrgeorgegray/dawn-till-dusk#readme",
"dependencies": {
"axios": "^0.21.1",
"cache-manager": "^3.4.0",
Expand All @@ -48,8 +59,7 @@
"ts-jest": "^26.5.1",
"typescript": "^4.1.5"
},
"repository": {
"type": "git",
"url": "git://github.com/mrgeorgegray/dawn-till-dusk"
}
"files": [
"lib/**/*"
]
}
23 changes: 16 additions & 7 deletions src/drawTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import formatTime from "./formatTime";
import formatDayLength from "./formatDayLength";
import { SunData } from "./getSunData";

export default (data: SunData): string => {
const drawTable = ({
date,
civil_twilight_begin,
sunrise,
sunset,
civil_twilight_end,
day_length,
}: SunData): string => {
const table = new Table({
style: { border: ["gray"] },
});
Expand All @@ -14,15 +21,17 @@ export default (data: SunData): string => {
{
colSpan: 3,
hAlign: "center",
content: `dawn-till-dusk for '${data.date}'`,
content: `dawn-till-dusk for '${date}'`,
},
],
{ "🌅": ["Dawn", formatTime(data.civil_twilight_begin)] },
{ "😎": ["Sunrise", formatTime(data.sunrise)] },
{ "🌇": ["Sunset", formatTime(data.sunset)] },
{ "🧛": ["Dusk", formatTime(data.civil_twilight_end)] },
{ "⏱": ["Length", formatDayLength(data.day_length)] }
{ "🌅": ["Dawn", formatTime(civil_twilight_begin)] },
{ "😎": ["Sunrise", formatTime(sunrise)] },
{ "🌇": ["Sunset", formatTime(sunset)] },
{ "🧛": ["Dusk", formatTime(civil_twilight_end)] },
{ "⏱": ["Length", formatDayLength(day_length)] }
);

return table.toString();
};

export default drawTable;
22 changes: 13 additions & 9 deletions src/formatDayLength.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
export default (value: number): string => {
const hours = Math.floor(value / 3600);
const minutes = Math.floor((value - hours * 3600) / 60);
const seconds = value - hours * 3600 - minutes * 60;
/**
* Formats a value in seconds to HHh:MMm:SSs
* @param {number} seconds - Duration in seconds
* @return {string} A formatted string
*/
const formatDayLength = (seconds: number): string => {
const hrs = Math.floor(seconds / 3600);
const min = Math.floor((seconds - hrs * 3600) / 60);
const sec = seconds - hrs * 3600 - min * 60;
const formatValue = (val: number) => (val < 10 ? `0${val}` : `${val}`);

const formattedHours = hours < 10 ? `0${hours}` : hours.toString();
const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes.toString();
const formattedSeconds = seconds < 10 ? `0${seconds}` : seconds.toString();

return `${formattedHours}h:${formattedMinutes}m:${formattedSeconds}s`;
return `${formatValue(hrs)}h:${formatValue(min)}m:${formatValue(sec)}s`;
};

export default formatDayLength;
10 changes: 9 additions & 1 deletion src/formatTime.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
export default (dateString: string): string =>
/**
* Formats dateString toLocaleTimeString()
* for consistent styling
* @param {string} dateString - A valid date string
* @return {string} A formatted string
*/
const formatTime = (dateString: string): string =>
new Date(dateString).toLocaleTimeString();

export default formatTime;
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const args = yargs
.option("date", {
alias: "d",
type: "string",
description: "Search by date",
default: "today",
description: "Search by date, format 'YYYY-MM-DD'",
default: "Today",
})
.option("logging", {
alias: "l",
Expand All @@ -41,7 +41,7 @@ void (async () => {
try {
const output = await main({
clean: args.clean,
date: args.date,
date: args.date || new Date().toISOString().split("T")[0],
debug: args.logging,
});
console.log(output);
Expand Down
15 changes: 15 additions & 0 deletions src/isDate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Basic validation for dateString
* @param {string} dateString
*/
const isDate = (dateString: string): boolean => {
const pattern = /^\d{4}-\d{2}-\d{2}$/;

if (!pattern.test(dateString)) {
return false;
}

return isNaN(Date.parse(dateString)) ? false : true;
};

export default isDate;
7 changes: 6 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { diskCache } from "./cache";
import drawTable from "./drawTable";
import { getIPData } from "./getIPData";
import { getSunData } from "./getSunData";
import isDate from "./isDate";

export interface Options {
clean: boolean;
Expand All @@ -23,9 +24,13 @@ const main: ({ clean, date, debug }: Options) => Promise<string> = async ({
}

log("[Options]:");
log({ debug, date });
log({ clean, date, debug });
log("");

if (!isDate(date)) {
return "Date must be a valid date with format YYYY-MM-DD";
}

if (clean) {
try {
await diskCache.reset();
Expand Down
File renamed without changes.
File renamed without changes.
19 changes: 17 additions & 2 deletions tests/src/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,29 @@ describe("cacheResponse()", () => {
});

describe("when cache enabled", () => {
it("makes a request, returns the value and stores result", async () => {
it("makes a request, returns the value and stores value", async () => {
await diskCache.reset();
const key = "cached";
const value = { data: "data" };
const response = await cacheResponse(key, () => Promise.resolve(value));
const request = jest.fn(() => Promise.resolve(value));
const response = await cacheResponse(key, request);
const cachedValue = await diskCache.get(key);

expect(response).toEqual(value);
expect(cachedValue).toEqual(value);
expect(request).toHaveBeenCalled();
});

it("doesn't make a request with a stored value", async () => {
await diskCache.reset();
const key = "cached";
const value = { data: "data" };
const request = jest.fn(() => Promise.resolve(value));
await diskCache.set(key, value);
const response = await cacheResponse(key, request);

expect(response).toEqual(value);
expect(request).not.toHaveBeenCalled();
});
});
});
6 changes: 3 additions & 3 deletions tests/src/drawTable.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import drawTable from "../../src/drawTable";
import sunData from "../mock/sunData";
import storedSunData from "../mock/storedSunData";

describe("drawTable()", () => {
const table = drawTable(sunData);
const table = drawTable(storedSunData);

it("contains the title", () => {
expect(table).toContain(`dawn-till-dusk for '${sunData.date}'`);
expect(table).toContain(`dawn-till-dusk for '${storedSunData.date}'`);
});

it("contains the headings", () => {
Expand Down
15 changes: 15 additions & 0 deletions tests/src/isDate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import isDate from "../../src/isDate";

describe("isDate()", () => {
it("is returns false with a badly formatted date", () => {
expect(isDate("21-21-21")).toEqual(false);
});

it("is returns false with an impossible date", () => {
expect(isDate("2021-90-90")).toEqual(false);
});

it("is returns true with a valid date", () => {
expect(isDate("2021-01-01")).toEqual(true);
});
});
108 changes: 108 additions & 0 deletions tests/src/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import main from "../../src/main";
import { diskCache } from "../../src/cache";
import drawTable from "../../src/drawTable";
import * as getIPData from "../../src/getIPData";
import * as getSunData from "../../src/getSunData";

import storedIPData from "../mock/storedIPData";
import storedSunData from "../mock/storedSunData";

jest.mock("../../src/drawTable", () => jest.fn());

describe("main()", () => {
describe("logging", () => {
it("displays options", async () => {
const consoleSpy = jest.spyOn(console, "info").mockImplementation();
const options = {
clean: false,
date: "bad-date",
debug: true,
};
await main(options);

expect(consoleSpy).toHaveBeenNthCalledWith(1, "[Options]:");
expect(consoleSpy).toHaveBeenNthCalledWith(2, options);
});
});

describe("validation", () => {
it("returns an error with a bad date", async () => {
const response = await main({
clean: false,
date: "bad-date",
debug: false,
});

expect(response).toEqual(
"Date must be a valid date with format YYYY-MM-DD"
);
});
});

describe("clean", () => {
it("clears the cache successfully", async () => {
const cacheSpy = jest
.spyOn(diskCache, "reset")
.mockImplementationOnce(() => "mocked clear");

const response = await main({
clean: true,
date: "2021-01-01",
debug: false,
});

expect(cacheSpy).toHaveBeenCalled();
expect(response).toEqual("Cache is cleared");
});

it("reports a error when reset fails", async () => {
const cacheSpy = jest
.spyOn(diskCache, "reset")
.mockImplementationOnce(() => {
throw new Error("uh oh");
});

const response = await main({
clean: true,
date: "2021-01-01",
debug: false,
});

expect(cacheSpy).toHaveBeenCalled();
expect(response).toEqual("Error clearing cache");
});
});

describe("main", () => {
it("creates a table", async () => {
jest
.spyOn(getIPData, "getIPData")
.mockImplementationOnce(() => Promise.resolve(storedIPData));
jest
.spyOn(getSunData, "getSunData")
.mockImplementationOnce(() => Promise.resolve(storedSunData));

await main({
clean: false,
date: storedSunData.date,
debug: false,
});

expect(drawTable).toHaveBeenCalledWith(storedSunData);
});

it("throws an error when data fetching fails", async () => {
jest
.spyOn(getIPData, "getIPData")
.mockImplementationOnce(() => Promise.reject("Failed"));

await expect(
main({
clean: false,
date: storedSunData.date,
debug: false,
})
).rejects.toThrow("Failed");
});
});
});

0 comments on commit a0aeb8e

Please sign in to comment.