Some code cleanup, add jest coverage and begin using it for utility functions

This commit is contained in:
DaneEveritt 2022-06-26 14:34:09 -04:00
parent ca39830333
commit 1eb3ea2ee4
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
29 changed files with 2044 additions and 134 deletions

View file

@ -0,0 +1,56 @@
import { bytesToString, ip, mbToBytes } from '@/lib/formatters';
describe('@/lib/formatters.ts', function () {
describe('mbToBytes()', function () {
it('should convert from MB to Bytes', function () {
expect(mbToBytes(1)).toBe(1_000_000);
expect(mbToBytes(0)).toBe(0);
expect(mbToBytes(0.1)).toBe(100_000);
expect(mbToBytes(0.001)).toBe(1000);
expect(mbToBytes(1024)).toBe(1_024_000_000);
});
});
describe('bytesToString()', function () {
it.each([
[ 0, '0 Bytes' ],
[ 0.5, '0 Bytes' ],
[ 0.9, '0 Bytes' ],
[ 100, '100 Bytes' ],
[ 100.25, '100.25 Bytes' ],
[ 100.998, '101 Bytes' ],
[ 512, '512 Bytes' ],
[ 1000, '1 KB' ],
[ 1024, '1.02 KB' ],
[ 5068, '5.07 KB' ],
[ 10_000, '10 KB' ],
[ 11_864, '11.86 KB' ],
[ 1_000_000, '1 MB' ],
[ 1_356_000, '1.36 MB' ],
[ 1_024_000, '1.02 MB' ],
[ 1_000_000_000, '1 GB' ],
[ 1_024_000_000, '1.02 GB' ],
[ 1_678_342_000, '1.68 GB' ],
[ 1_000_000_000_000, '1 TB' ],
])('should format %d bytes as "%s"', function (input, output) {
expect(bytesToString(input)).toBe(output);
});
});
describe('ip()', function () {
it('should format an IPv4 address', function () {
expect(ip('127.0.0.1')).toBe('127.0.0.1');
});
it('should format an IPv6 address', function () {
expect(ip(':::1')).toBe('[:::1]');
expect(ip('2001:db8::')).toBe('[2001:db8::]');
});
it('should handle random inputs', function () {
expect(ip('1')).toBe('1');
expect(ip('foobar')).toBe('foobar');
expect(ip('127.0.0.1:25565')).toBe('[127.0.0.1:25565]');
});
});
});

View file

@ -0,0 +1,35 @@
const _CONVERSION_UNIT = 1000;
/**
* Given a value in megabytes converts it back down into bytes.
*/
function mbToBytes (megabytes: number): number {
return Math.floor(megabytes * _CONVERSION_UNIT * _CONVERSION_UNIT);
}
/**
* Given an amount of bytes, converts them into a human readable string format
* using "1000" as the divisor.
*/
function bytesToString (bytes: number): string {
if (bytes < 1) return '0 Bytes';
const i = Math.floor(Math.log(bytes) / Math.log(_CONVERSION_UNIT));
const value = Number((bytes / Math.pow(_CONVERSION_UNIT, i)).toFixed(2));
return `${value} ${[ 'Bytes', 'KB', 'MB', 'GB', 'TB' ][i]}`;
}
/**
* Formats an IPv4 or IPv6 address.
*/
function ip (value: string): string {
// noinspection RegExpSimplifiable
return /([a-f0-9:]+:+)+[a-f0-9]+/.test(value) ? `[${value}]` : value;
}
export {
ip,
mbToBytes,
bytesToString,
};

View file

@ -0,0 +1,29 @@
import { hexToRgba } from '@/lib/helpers';
describe('@/lib/helpers.ts', function () {
describe('hexToRgba()', function () {
it('should return the expected rgba', function () {
expect(hexToRgba('#ffffff')).toBe('rgba(255, 255, 255, 1)');
expect(hexToRgba('#00aabb')).toBe('rgba(0, 170, 187, 1)');
expect(hexToRgba('#efefef')).toBe('rgba(239, 239, 239, 1)');
});
it('should ignore case', function () {
expect(hexToRgba('#FF00A3')).toBe('rgba(255, 0, 163, 1)');
});
it('should allow alpha channel changes', function () {
expect(hexToRgba('#ece5a8', 0.5)).toBe('rgba(236, 229, 168, 0.5)');
expect(hexToRgba('#ece5a8', 0.1)).toBe('rgba(236, 229, 168, 0.1)');
expect(hexToRgba('#000000', 0)).toBe('rgba(0, 0, 0, 0)');
});
it('should handle invalid strings', function () {
expect(hexToRgba('')).toBe('');
expect(hexToRgba('foobar')).toBe('foobar');
expect(hexToRgba('#fff')).toBe('#fff');
expect(hexToRgba('#')).toBe('#');
expect(hexToRgba('#fffffy')).toBe('#fffffy');
});
});
});

View file

@ -0,0 +1,17 @@
/**
* Given a valid six character HEX color code, converts it into its associated
* RGBA value with a user controllable alpha channel.
*/
function hexToRgba (hex: string, alpha = 1): string {
// noinspection RegExpSimplifiable
if (!/#?([a-fA-F0-9]{2}){3}/.test(hex)) {
return hex;
}
// noinspection RegExpSimplifiable
const [ r, g, b ] = hex.match(/[a-fA-F0-9]{2}/g)!.map(v => parseInt(v, 16));
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
export { hexToRgba };

View file

@ -0,0 +1,30 @@
import { isObject } from '@/lib/objects';
describe('@/lib/objects.ts', function () {
describe('isObject()', function () {
it('should return true for objects', function () {
expect(isObject({})).toBe(true);
expect(isObject({ foo: 123 })).toBe(true);
expect(isObject(Object.freeze({}))).toBe(true);
});
it('should return false for null', function () {
expect(isObject(null)).toBe(false);
});
it.each([
undefined,
123,
'foobar',
() => ({}),
Function,
String(123),
isObject,
() => null,
[],
[ 1, 2, 3 ],
])('should return false for %p', function (value) {
expect(isObject(value)).toBe(false);
});
});
});

View file

@ -0,0 +1,29 @@
/**
* Determines if the value provided to the function is an object type that
* is not null.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
function isObject (val: unknown): val is {} {
return typeof val === 'object' && val !== null && !Array.isArray(val);
}
/**
* Determines if an object is truly empty by looking at the keys present
* and the prototype value.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
function isEmptyObject (val: {}): boolean {
return Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype;
}
/**
* A helper function for use in TypeScript that returns all of the keys
* for an object, but in a typed manner to make working with them a little
* easier.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
function getObjectKeys<T extends {}> (o: T): (keyof T)[] {
return Object.keys(o) as (keyof typeof o)[];
}
export { isObject, isEmptyObject, getObjectKeys };

View file

@ -0,0 +1,14 @@
import { capitalize } from '@/lib/strings';
describe('@/lib/strings.ts', function () {
describe('capitalize()', function () {
it('should capitalize a string', function () {
expect(capitalize('foo bar')).toBe('Foo bar');
expect(capitalize('FOOBAR')).toBe('Foobar');
});
it('should handle empty strings', function () {
expect(capitalize('')).toBe('');
});
});
});

View file

@ -0,0 +1,5 @@
function capitalize (value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
}
export { capitalize };