diff options
| author | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2022-05-01 00:56:06 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2022-05-01 00:56:06 +0200 |
| commit | a7cf03c192470cbab13edeb1aec99e0c66dede10 (patch) | |
| tree | 581b4430d1de958dcb666bae80a7678332134602 /src/lib/utils | |
| parent | 010f307346e525ac2e4239a0549d2c1a4d6d102b (diff) | |
Update / use typescript
Diffstat (limited to 'src/lib/utils')
| -rw-r--r-- | src/lib/utils/glyph_hash.test.ts | 68 | ||||
| -rw-r--r-- | src/lib/utils/glyph_hash.ts | 55 | ||||
| -rw-r--r-- | src/lib/utils/readable_time.test.ts | 83 | ||||
| -rw-r--r-- | src/lib/utils/readable_time.ts | 46 | ||||
| -rw-r--r-- | src/lib/utils/resolve_after.test.ts | 37 | ||||
| -rw-r--r-- | src/lib/utils/resolve_after.ts | 29 |
6 files changed, 318 insertions, 0 deletions
diff --git a/src/lib/utils/glyph_hash.test.ts b/src/lib/utils/glyph_hash.test.ts new file mode 100644 index 0000000..5f57a2a --- /dev/null +++ b/src/lib/utils/glyph_hash.test.ts @@ -0,0 +1,68 @@ +import { getGlyphHash } from './glyph_hash'; +import type { GlyphHash } from './glyph_hash'; + +type TestState = { + glyphHash?: GlyphHash +}; + + +describe('Glyph Hash utility', () => { + + test('it throws an exception if the string is too short', () => { + + expect(() => { + + getGlyphHash('short'); + }).toThrow(); + + expect(() => { + + getGlyphHash('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); + }).toThrow(); + + expect(() => { + + getGlyphHash('abcdefghijklmnopqrstuvwxyzABCDEF'); + }).not.toThrow(); + }); + + test('it treats UUIDs with hyphens the same as those without', () => { + + const uuidWithHyphens = 'f7722355-2285-46c0-a55f-3483a826f3a6'; + const uuidWithoutHyphens = 'f7722355228546c0a55f3483a826f3a6'; + + expect(getGlyphHash(uuidWithHyphens)).toEqual(getGlyphHash(uuidWithoutHyphens)); + }); + + describe('it generates four sets of glyphs and colors', () => { + + const state: TestState = {}; + + beforeEach(() => { + + state.glyphHash = getGlyphHash('f7722355-2285-46c0-a55f-3483a826f3a6'); + }); + + test('there should be four glyph fragments', () => { + + expect(state.glyphHash.length).toBe(4); + }); + + test('each fragment should have a single character glyph', () => { + + for (const glyphHashFragment of state.glyphHash) { + expect(typeof glyphHashFragment.glyph).toBe('string'); + expect(glyphHashFragment.glyph.length).toBe(1); + } + }); + + test('each fragment should have a hexadecimal color', () => { + + for (const glyphHashFragment of state.glyphHash) { + expect(typeof glyphHashFragment.color).toBe('string'); + expect(glyphHashFragment.color.length).toBe(7); + expect(glyphHashFragment.color).toEqual(expect.stringMatching(/#[0-9a-f]{6}/)); + } + }); + }); +}); diff --git a/src/lib/utils/glyph_hash.ts b/src/lib/utils/glyph_hash.ts new file mode 100644 index 0000000..b704569 --- /dev/null +++ b/src/lib/utils/glyph_hash.ts @@ -0,0 +1,55 @@ +export type GlyphHash = GlyphHashFragment[]; +type GlyphHashFragment = { + glyph: string, + color: string +}; + +const internals = { + kDehyphenRegex: /[-]/g, + kSplitterRegex: /.{1,8}/g, + kGlyphs: [ + '☽', + '☆', + '♢', + '♡', + '╱', + '╲', + '╳', + '〰', + '▷', + '⏊', + '〒', + '▢', + '◯', + '⏃', + '⏀', + '⏆' + ], + unexpectedUUIDLength: class UnexpectedUUIDLength extends Error { + name = 'UnexpectedUUIDLength'; + message = 'The provided string was not a valid UUIDv4, please provide a 32 character long string' + } +}; + +// Return a glyph with color based on a 4 byte fragment of a UUIDv4 +const getGlyphHashFragment = function (uuidFragment: string): GlyphHashFragment { + + const glyphIndex = parseInt(uuidFragment.substring(0, 2), 16) % 16; + return { + glyph: internals.kGlyphs[glyphIndex], + color: `#${uuidFragment.substring(2, 8)}` + }; +}; + +// Return an array of glyphs based on a UUIDv4 +export const getGlyphHash = function (uuid: string): GlyphHash { + + const dehyphenedUuid = uuid.replace(/[-]/g, ''); + + if (dehyphenedUuid.length !== 32) { + throw new internals.unexpectedUUIDLength(); + } + + const hashFragments = dehyphenedUuid.match(internals.kSplitterRegex); + return hashFragments.map(getGlyphHashFragment); +}; diff --git a/src/lib/utils/readable_time.test.ts b/src/lib/utils/readable_time.test.ts new file mode 100644 index 0000000..5d8ba27 --- /dev/null +++ b/src/lib/utils/readable_time.test.ts @@ -0,0 +1,83 @@ +import { readableTime } from './readable_time'; + +describe('readableTime', () => { + + test('it shows negative time as 0', () => { + + const response = readableTime(-1000); + + expect(response.count).toBe(0); + expect(response.label).toContain('seconds'); + }); + + test('uses seconds as the smallest unit', () => { + + const response = readableTime(10); + + expect(response.count).toBe(0); + expect(response.label).toContain('seconds'); + }); + + test('correctly divides miliseconds into seconds', () => { + + const response = readableTime(4000); + + expect(response.count).toBe(4); + }); + + test('uses seconds if the time is < 1 minute', () => { + + const response = readableTime(59 * 1000); + + expect(response.label).toContain('seconds'); + }); + + test('correctly divides miliseconds into minutes', () => { + + const response = readableTime(2 * 60 * 1000); + + expect(response.count).toBe(2); + }); + + test('uses minutes if the time is < 1 hour', () => { + + const response = readableTime(59 * 60 * 1000); + + expect(response.label).toContain('minutes'); + }); + + test('correctly divides miliseconds into hours', () => { + + const response = readableTime(2 * 60 * 60 * 1000); + + expect(response.count).toBe(2); + }); + + test('uses hours if the time is < 1 days', () => { + + const response = readableTime(23 * 60 * 60 * 1000); + + expect(response.label).toContain('hours'); + }); + + test('correctly divides miliseconds into days', () => { + + const response = readableTime(2 * 24 * 60 * 60 * 1000); + + expect(response.count).toBe(2); + }); + + test('uses days if the time is >= 1 day', () => { + + const response = readableTime(364 * 24 * 60 * 60 * 1000); + + expect(response.label).toContain('days'); + }); + + test('uses days as the maximum unit', () => { + + const response = readableTime(Number.MAX_VALUE); + + expect(response.label).toContain('days'); + }); +}); diff --git a/src/lib/utils/readable_time.ts b/src/lib/utils/readable_time.ts new file mode 100644 index 0000000..86ba044 --- /dev/null +++ b/src/lib/utils/readable_time.ts @@ -0,0 +1,46 @@ +type DateMagnitude = 'day' | 'hour' | 'minute' | 'second'; + +type ReadableTime = { + count: number, + label: string +}; + + +const internals = { + magnitudes: { + day: 86400000, + hour: 3600000, + minute: 60000, + second: 1000 + }, + labels: { + day: 'time.days', + hour: 'time.hours', + minute: 'time.minutes', + second: 'time.seconds' + }, + + makeTimeReadable(time: number, magnitude: DateMagnitude): ReadableTime { + + return { + count: Math.floor(time / internals.magnitudes[magnitude]), + label: internals.labels[magnitude] + }; + } +}; + +export const readableTime = function readableTime(time: number): ReadableTime { + + switch (true) { + case time >= internals.magnitudes.day: + return internals.makeTimeReadable(time, 'day'); + case time >= internals.magnitudes.hour: + return internals.makeTimeReadable(time, 'hour'); + case time >= internals.magnitudes.minute: + return internals.makeTimeReadable(time, 'minute'); + case time < 0: + return internals.makeTimeReadable(0, 'second'); + default: + return internals.makeTimeReadable(time, 'second'); + } +}; diff --git a/src/lib/utils/resolve_after.test.ts b/src/lib/utils/resolve_after.test.ts new file mode 100644 index 0000000..7e0cc3c --- /dev/null +++ b/src/lib/utils/resolve_after.test.ts @@ -0,0 +1,37 @@ +import { resolveAfter } from './resolve_after'; + +describe('Resolve After', () => { + + test('it should throw if given 0', () => { + + expect(() => { + + resolveAfter(0); + }).toThrow(); + }); + + test('it should throw if given a negative number', () => { + + expect(() => { + + resolveAfter(-1); + }).toThrow(); + }); + + test('it should resolve after the specified number of times', () => { + + expect(() => { + + const { counter, promise: resolveAfterThree } = resolveAfter(3); + let resolved = false; + + resolveAfterThree.then(() => (resolved = true)); + counter(); + expect(resolved).toBe(false); + counter(); + expect(resolved).toBe(false); + counter(); + expect(resolved).toBe(true); + }).toThrow(); + }); +}); diff --git a/src/lib/utils/resolve_after.ts b/src/lib/utils/resolve_after.ts new file mode 100644 index 0000000..95a477e --- /dev/null +++ b/src/lib/utils/resolve_after.ts @@ -0,0 +1,29 @@ +export type ResolveAfterPromise = { + counter: () => void, + promise: Promise<void> +}; + +export const resolveAfter = function (timesUntilResolve: number): ResolveAfterPromise { + + let counter = null; + let currentValue = 0; + + if (timesUntilResolve <= 0) { + throw new Error('Resolve after requires a positive integer'); + } + + const promise: Promise<void> = new Promise((resolvePromise) => { + + counter = () => { + + if (++currentValue === timesUntilResolve) { + resolvePromise(); + } + }; + }); + + return { + counter, + promise + }; +}; |