Skip to content

Commit 2d17786

Browse files
committed
Refactor TypeScript for readability, drop ramda, bump to 1.5.0
Replace Ramda calls with native Array/Object methods, swap hand-rolled callback promisification for fs/promises, inline trivial wrappers, fold the fake Promise.all([single]) ceremony, and let errors propagate naturally instead of going through the awkward statusMessage helper. Make percentageToColor table-driven and delete the unused zip helper. All 29 tests still pass; tsc and eslint are clean.
1 parent a3ae75b commit 2d17786

File tree

10 files changed

+116
-418
lines changed

10 files changed

+116
-418
lines changed

__tests__/util.test.ts

Lines changed: 10 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,16 @@
1-
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
2-
import { urlEscaper, zip, statusMessage } from '../src/util';
1+
import { describe, it, expect } from 'bun:test';
2+
import { urlEscaper } from '../src/util';
33

4-
describe('test util', () => {
5-
describe('test pure functions in util', () => {
6-
it('should return escaped https url', () => {
7-
const actualUrl = 'https://abcöd%f&/?get=hi';
8-
const expectedUrl = 'abc_d_f___get_hi';
9-
expect(urlEscaper(actualUrl)).toBe(expectedUrl);
10-
});
11-
12-
it('should return escaped http url', () => {
13-
const actualUrl = 'https://abcöd%f&/?get=hi';
14-
const expectedUrl = 'abc_d_f___get_hi';
15-
expect(urlEscaper(actualUrl)).toBe(expectedUrl);
16-
});
17-
18-
it('should zip together two arrays', () => {
19-
const input = [[0, 1, 2, 3], [4, 5, 6, 7]];
20-
const expected = [[0, 4], [1, 5], [2, 6], [3, 7]];
21-
expect(zip(input)).toStrictEqual(expected);
22-
});
4+
describe('urlEscaper', () => {
5+
it('strips the https scheme and replaces non-alphanumerics', () => {
6+
expect(urlEscaper('https://abcöd%f&/?get=hi')).toBe('abc_d_f___get_hi');
237
});
248

25-
describe('test functions with side-effects in util', () => {
26-
let stdoutOutput = '';
27-
const stdoutWrite = process.stdout.write;
28-
29-
beforeEach(() => {
30-
process.stdout.write = (x: string) => {
31-
stdoutOutput += x;
32-
return true;
33-
};
34-
stdoutOutput = '';
35-
});
36-
37-
afterEach(() => {
38-
process.stdout.write = stdoutWrite;
39-
});
40-
41-
it('should write success message if no error', () => {
42-
const expectedSuccessMessage = 'success';
43-
statusMessage(expectedSuccessMessage, 'error', undefined);
44-
expect(stdoutOutput).toBe(expectedSuccessMessage);
45-
});
9+
it('strips the http scheme and replaces non-alphanumerics', () => {
10+
expect(urlEscaper('http://abcöd%f&/?get=hi')).toBe('abc_d_f___get_hi');
11+
});
4612

47-
it('should not write a success metric if error is thrown', () => {
48-
const expectedErrorMessage = 'error';
49-
expect(() => {
50-
statusMessage('success', expectedErrorMessage, new Error('error'));
51-
}).toThrow(new Error(expectedErrorMessage));
52-
expect(stdoutOutput).toBe('');
53-
});
13+
it('lowercases the result', () => {
14+
expect(urlEscaper('HTTPS://Example.COM/Path')).toBe('example_com_path');
5415
});
5516
});
56-

bun.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@emazzotta/lighthouse-badges",
3-
"version": "1.4.5",
3+
"version": "1.5.0",
44
"description": "🚦Generate gh-badges (shields.io) based on Lighthouse performance.",
55
"main": "dist/src/index.js",
66
"bin": {
@@ -52,8 +52,7 @@
5252
"badge-maker": "^5.0.2",
5353
"chrome-launcher": "1.2.1",
5454
"clui": "^0.3.6",
55-
"lighthouse": "^13.1.0",
56-
"ramda": "^0.32.0"
55+
"lighthouse": "^13.1.0"
5756
},
5857
"devDependencies": {
5958
"@eslint/js": "^10.0.1",

src/calculations.ts

Lines changed: 27 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,42 @@
1-
import * as R from 'ramda';
21
import type { LighthouseMetrics } from './types.js';
32

4-
const PERCENTAGE_THRESHOLDS = {
5-
EXCELLENT: 95,
6-
GOOD: 90,
7-
ABOVE_AVERAGE: 75,
8-
AVERAGE: 60,
9-
BELOW_AVERAGE: 40,
10-
} as const;
3+
const COLOR_THRESHOLDS: ReadonlyArray<readonly [number, string]> = [
4+
[95, 'brightgreen'],
5+
[90, 'green'],
6+
[75, 'yellowgreen'],
7+
[60, 'yellow'],
8+
[40, 'orange'],
9+
];
1110

12-
const COLOR_MAP = {
13-
EXCELLENT: 'brightgreen',
14-
GOOD: 'green',
15-
ABOVE_AVERAGE: 'yellowgreen',
16-
AVERAGE: 'yellow',
17-
BELOW_AVERAGE: 'orange',
18-
POOR: 'red',
19-
} as const;
11+
const POOR_COLOR = 'red';
2012

21-
export const percentageToColor = (percentage: number): string => {
22-
if (percentage >= PERCENTAGE_THRESHOLDS.EXCELLENT) return COLOR_MAP.EXCELLENT;
23-
if (percentage >= PERCENTAGE_THRESHOLDS.GOOD) return COLOR_MAP.GOOD;
24-
if (percentage >= PERCENTAGE_THRESHOLDS.ABOVE_AVERAGE) return COLOR_MAP.ABOVE_AVERAGE;
25-
if (percentage >= PERCENTAGE_THRESHOLDS.AVERAGE) return COLOR_MAP.AVERAGE;
26-
if (percentage >= PERCENTAGE_THRESHOLDS.BELOW_AVERAGE) return COLOR_MAP.BELOW_AVERAGE;
27-
return COLOR_MAP.POOR;
28-
};
13+
export const percentageToColor = (percentage: number): string =>
14+
COLOR_THRESHOLDS.find(([threshold]) => percentage >= threshold)?.[1] ?? POOR_COLOR;
2915

30-
const calculateCategoryAverage = (
31-
category: string,
32-
metrics: LighthouseMetrics[],
33-
): number => {
34-
const categoryScores = R.pluck(category, metrics);
35-
const sum = R.sum(categoryScores);
36-
const count = R.length(metrics);
37-
return Math.round(sum / count);
16+
const averageByCategory = (category: string, metrics: LighthouseMetrics[]): number => {
17+
const sum = metrics.reduce((total, metric) => total + (metric[category] ?? 0), 0);
18+
return Math.round(sum / metrics.length);
3819
};
3920

4021
export const getAverageScore = (metrics: LighthouseMetrics[]): LighthouseMetrics => {
41-
if (metrics.length === 0) {
42-
return {};
43-
}
44-
45-
const headMetric = R.head(metrics);
46-
if (!headMetric) {
47-
return {};
48-
}
49-
50-
const categories = R.keys(headMetric);
51-
const scoreObjects = categories.map((category: string) => ({
52-
[category]: calculateCategoryAverage(category, metrics),
53-
}));
54-
55-
return R.mergeAll(scoreObjects) as LighthouseMetrics;
56-
};
57-
58-
const calculateTotalScore = (metrics: LighthouseMetrics[]): number => {
59-
const sumOfAllScores = R.pipe(
60-
R.map((metric: LighthouseMetrics) => R.sum(R.values(metric))),
61-
R.sum,
62-
)(metrics);
63-
return sumOfAllScores;
64-
};
22+
const [head] = metrics;
23+
if (!head) return {};
6524

66-
const calculateTotalCategories = (headMetric: LighthouseMetrics): number => {
67-
return R.length(R.keys(headMetric));
25+
return Object.fromEntries(
26+
Object.keys(head).map((category) => [category, averageByCategory(category, metrics)]),
27+
);
6828
};
6929

7030
export const getSquashedScore = (metrics: LighthouseMetrics[]): LighthouseMetrics => {
71-
if (metrics.length === 0) {
72-
return { lighthouse: 0 };
73-
}
74-
75-
const headMetric = R.head(metrics);
76-
if (!headMetric) {
77-
return { lighthouse: 0 };
78-
}
31+
const [head] = metrics;
32+
if (!head) return { lighthouse: 0 };
7933

80-
const totalScore = calculateTotalScore(metrics);
81-
const totalCategories = calculateTotalCategories(headMetric);
82-
const totalMetrics = R.length(metrics);
83-
const averageScore = totalScore / (totalMetrics * totalCategories);
34+
const totalScore = metrics.reduce(
35+
(sum, metric) => sum + Object.values(metric).reduce((a, b) => a + b, 0),
36+
0,
37+
);
38+
const totalCategories = Object.keys(head).length;
39+
const averageScore = totalScore / (metrics.length * totalCategories);
8440

85-
return {
86-
lighthouse: Math.round(averageScore),
87-
};
41+
return { lighthouse: Math.round(averageScore) };
8842
};

src/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@
33
import handleUserInput from './main.js';
44
import CLI from 'clui';
55

6-
const CLI_SPINNER = new CLI.Spinner('Running Lighthouse, please wait...', ['◜', '◠', '◝', '◞', '◡', '◟']);
7-
handleUserInput(CLI_SPINNER).then(() => {});
8-
6+
const spinner = new CLI.Spinner('Running Lighthouse, please wait...', ['◜', '◠', '◝', '◞', '◡', '◟']);
7+
await handleUserInput(spinner);

0 commit comments

Comments
 (0)