@lizardbyte/shared-web 2024.901.15440

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ function levenshteinDistance(a, b) {
2
+ if (a.length === 0) return b.length;
3
+ if (b.length === 0) return a.length;
4
+
5
+ let matrix = [];
6
+
7
+ // increment along the first column of each row
8
+ let i;
9
+ for (i = 0; i <= b.length; i++) {
10
+ matrix[i] = [i];
11
+ }
12
+
13
+ // increment each column in the first row
14
+ let j;
15
+ for (j = 0; j <= a.length; j++) {
16
+ matrix[0][j] = j;
17
+ }
18
+
19
+ // Fill in the rest of the matrix
20
+ for (i = 1; i <= b.length; i++) {
21
+ for (j = 1; j <= a.length; j++) {
22
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
23
+ matrix[i][j] = matrix[i - 1][j - 1];
24
+ } else {
25
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
26
+ Math.min(matrix[i][j - 1] + 1, // insertion
27
+ matrix[i - 1][j] + 1)); // deletion
28
+ }
29
+ }
30
+ }
31
+
32
+ // return the percentage of the levenshtein distance
33
+ return (1 - (matrix[b.length][a.length] / Math.max(a.length, b.length))) * 100
34
+ }
35
+
36
+ module.exports = levenshteinDistance;
@@ -0,0 +1 @@
1
+ import "../css/lizardbyte.scss";
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Load a script asynchronously, and add it to the DOM. Optionally, call a callback when the script has loaded.
3
+ * @param url The URL of the script to load.
4
+ * @param callback An optional callback to call when the script has loaded.
5
+ */
6
+ function loadScript(url, callback) {
7
+ const script = document.createElement('script');
8
+ script.src = url;
9
+ script.async = true;
10
+
11
+ script.onload = () => {
12
+ if (callback) callback(null, script);
13
+ };
14
+
15
+ script.onerror = () => {
16
+ if (callback) callback(new Error(`Failed to load script: ${url}`));
17
+ };
18
+
19
+ document.head.appendChild(script);
20
+ }
21
+
22
+ module.exports = loadScript;
@@ -0,0 +1,25 @@
1
+ let quoteCache = null;
2
+
3
+ /**
4
+ * Fetch a random quote from our API.
5
+ * @returns {Promise<*>} A promise that resolves with a random quote
6
+ */
7
+ async function fetchRandomQuote() {
8
+ if (!quoteCache) {
9
+ const response = await fetch('https://app.lizardbyte.dev/uno/random-quotes/games.json');
10
+ quoteCache = await response.json();
11
+ }
12
+ return quoteCache[Math.floor(Math.random() * quoteCache.length)];
13
+ }
14
+
15
+ /**
16
+ * Reset the quote cache.
17
+ */
18
+ function resetQuoteCache() {
19
+ quoteCache = null;
20
+ }
21
+
22
+ module.exports = {
23
+ fetchRandomQuote,
24
+ resetQuoteCache,
25
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Sorts an array of objects by two keys in descending order.
3
+ * @param firstKey The primary key to sort by.
4
+ * @param secondKey The secondary key to sort by, in case the first key is equal.
5
+ * @returns {(function(*, *): (number))|*} The sorting function.
6
+ */
7
+ let rankingSorter = function (firstKey, secondKey) {
8
+ return function(a, b) {
9
+ if (a[firstKey] > b[firstKey]) {
10
+ return -1;
11
+ } else if (a[firstKey] < b[firstKey]) {
12
+ return 1;
13
+ }
14
+ else {
15
+ if (a[secondKey] > b[secondKey]) {
16
+ return 1;
17
+ } else if (a[secondKey] < b[secondKey]) {
18
+ return -1;
19
+ } else {
20
+ return 0;
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ module.exports = rankingSorter;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Sleep for a given amount of time.
3
+ * @param ms The time to sleep in milliseconds
4
+ * @returns {Promise<unknown>} A promise that resolves after the given time
5
+ */
6
+ function sleep(ms) {
7
+ return new Promise(resolve => setTimeout(resolve, ms));
8
+ }
9
+
10
+ module.exports = sleep;
@@ -0,0 +1,26 @@
1
+ import {
2
+ describe,
3
+ expect,
4
+ test,
5
+ } from '@jest/globals';
6
+
7
+ const levenshteinDistance = require('../src/js/levenshtein-distance');
8
+
9
+ describe('levenshteinDistance', () => {
10
+ const testCases = [
11
+ { a: 'test', b: 'test', expected: 100 },
12
+ { a: 'abc', b: 'xyz', expected: 0 },
13
+ { a: 'kitten', b: 'sitting', expected: 57.14 },
14
+ { a: 'flaw', b: 'lawn', expected: 50 },
15
+ { a: '', b: 'nonempty', expected: 8 },
16
+ { a: 'nonempty', b: '', expected: 8 }
17
+ ];
18
+
19
+ test.each(testCases)(
20
+ 'should return $expected when comparing "$a" and "$b"',
21
+ ({ a, b, expected }) => {
22
+ const result = levenshteinDistance(a, b);
23
+ expect(result).toBeCloseTo(expected, 2);
24
+ }
25
+ );
26
+ });
@@ -0,0 +1,48 @@
1
+ import {
2
+ beforeEach,
3
+ describe,
4
+ expect,
5
+ it,
6
+ } from '@jest/globals';
7
+
8
+ const loadScript = require('../src/js/load-script');
9
+
10
+ describe('loadScript', () => {
11
+ beforeEach(() => {
12
+ document.head.innerHTML = '';
13
+ });
14
+
15
+ it('should load a script and call the callback on success', (done) => {
16
+ const url = 'https://example.com/test-script.js';
17
+ loadScript(url, (err, script) => {
18
+ expect(err).toBeNull();
19
+ expect(script).toBeInstanceOf(HTMLScriptElement);
20
+ expect(script.src).toBe(url);
21
+ done();
22
+ });
23
+
24
+ const script = document.head.querySelector('script');
25
+ script.onload();
26
+ });
27
+
28
+ it('should call the callback with an error on failure', (done) => {
29
+ const url = 'https://example.com/test-script.js';
30
+ loadScript(url, (err) => {
31
+ expect(err).toBeInstanceOf(Error);
32
+ expect(err.message).toBe(`Failed to load script: ${url}`);
33
+ done();
34
+ });
35
+
36
+ const script = document.head.querySelector('script');
37
+ script.onerror();
38
+ });
39
+
40
+ it('should load a script without a callback', () => {
41
+ const url = 'https://example.com/test-script.js';
42
+ loadScript(url);
43
+
44
+ const script = document.head.querySelector('script');
45
+ expect(script).toBeInstanceOf(HTMLScriptElement);
46
+ expect(script.src).toBe(url);
47
+ });
48
+ });
@@ -0,0 +1,77 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ jest,
8
+ } from '@jest/globals';
9
+
10
+ const { fetchRandomQuote, resetQuoteCache } = require('../src/js/random-quote');
11
+
12
+ describe('fetchRandomQuote', () => {
13
+ let originalFetch;
14
+
15
+ beforeEach(() => {
16
+ // Save the original fetch function
17
+ originalFetch = globalThis.fetch;
18
+ // Clear the quote cache before each test
19
+ jest.resetModules();
20
+ // Reset the quotesCache
21
+ resetQuoteCache();
22
+ });
23
+
24
+ afterEach(() => {
25
+ // Restore the original fetch function
26
+ globalThis.fetch = originalFetch;
27
+ });
28
+
29
+ it('should fetch and return a random quote', async () => {
30
+ const mockQuotes = [
31
+ { quote: 'Quote 1' },
32
+ { quote: 'Quote 2' },
33
+ { quote: 'Quote 3' }
34
+ ];
35
+
36
+ globalThis.fetch = jest.fn(() =>
37
+ Promise.resolve({
38
+ json: () => Promise.resolve(mockQuotes)
39
+ })
40
+ );
41
+
42
+ const quote = await fetchRandomQuote();
43
+ expect(mockQuotes).toContainEqual(quote);
44
+ expect(globalThis.fetch).toHaveBeenCalledTimes(1);
45
+ });
46
+
47
+ it('should return a cached quote if already fetched', async () => {
48
+ const mockQuotes = [
49
+ { quote: 'Quote 1' },
50
+ { quote: 'Quote 2' },
51
+ { quote: 'Quote 3' }
52
+ ];
53
+
54
+ globalThis.fetch = jest.fn(() =>
55
+ Promise.resolve({
56
+ json: () => Promise.resolve(mockQuotes)
57
+ })
58
+ );
59
+
60
+ // First call to populate the cache
61
+ await fetchRandomQuote();
62
+ // Second call should use the cache
63
+ const quote = await fetchRandomQuote();
64
+
65
+ expect(mockQuotes).toContainEqual(quote);
66
+ expect(globalThis.fetch).toHaveBeenCalledTimes(1);
67
+ });
68
+
69
+ it('should handle fetch errors gracefully', async () => {
70
+ globalThis.fetch = jest.fn(() =>
71
+ Promise.reject(new Error('Failed to fetch'))
72
+ );
73
+
74
+ await expect(fetchRandomQuote()).rejects.toThrow('Failed to fetch');
75
+ expect(globalThis.fetch).toHaveBeenCalledTimes(1);
76
+ });
77
+ });
@@ -0,0 +1,55 @@
1
+ import {
2
+ describe,
3
+ expect,
4
+ it,
5
+ } from '@jest/globals';
6
+
7
+ const rankingSorter = require('../src/js/ranking-sorter');
8
+
9
+ describe('rankingSorter', () => {
10
+ const testCases = [
11
+ {
12
+ description: 'should sort by firstKey descending and secondKey ascending',
13
+ data: [
14
+ { key1: 1, key2: 2 },
15
+ { key1: 2, key2: 1 },
16
+ { key1: 1, key2: 1 }
17
+ ],
18
+ expected: [
19
+ { key1: 2, key2: 1 },
20
+ { key1: 1, key2: 1 },
21
+ { key1: 1, key2: 2 }
22
+ ]
23
+ },
24
+ {
25
+ description: 'should return 1 when firstKey values are equal and secondKey of first object is greater',
26
+ data: [
27
+ { key1: 1, key2: 2 },
28
+ { key1: 1, key2: 1 }
29
+ ],
30
+ expected: 1,
31
+ compare: true
32
+ },
33
+ {
34
+ description: 'should return 0 when both firstKey and secondKey values are equal',
35
+ data: [
36
+ { key1: 1, key2: 1 },
37
+ { key1: 1, key2: 1 }
38
+ ],
39
+ expected: 0,
40
+ compare: true
41
+ }
42
+ ];
43
+
44
+ testCases.forEach(({ description, data, expected, compare }) => {
45
+ it(description, () => {
46
+ if (compare) {
47
+ const result = rankingSorter('key1', 'key2')(data[0], data[1]);
48
+ expect(result).toBe(expected);
49
+ } else {
50
+ const sorted = data.sort(rankingSorter('key1', 'key2'));
51
+ expect(sorted).toEqual(expected);
52
+ }
53
+ });
54
+ });
55
+ });
@@ -0,0 +1,41 @@
1
+ import {
2
+ afterAll,
3
+ beforeAll,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ jest,
8
+ test,
9
+ } from '@jest/globals';
10
+
11
+ const sleep = require('../src/js/sleep');
12
+
13
+ describe('sleep function', () => {
14
+ beforeAll(() => {
15
+ jest.useFakeTimers();
16
+ })
17
+
18
+ beforeEach(() => {
19
+ jest.spyOn(global, 'setTimeout');
20
+ });
21
+
22
+ test.each([
23
+ [500], // 0.5 second
24
+ [1000], // 1 second
25
+ [1500], // 1.5 seconds
26
+ [2000], // 2 seconds
27
+ [60000], // 60 seconds
28
+ ])('resolves after %i milliseconds', async (delay) => {
29
+ let delay_value = delay[0];
30
+ const sleepPromise = sleep(delay_value);
31
+ jest.advanceTimersByTime(delay_value);
32
+ await sleepPromise;
33
+
34
+ // Allow a small margin of error for the timer
35
+ expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), delay_value);
36
+ });
37
+
38
+ afterAll(() => {
39
+ jest.useRealTimers();
40
+ });
41
+ });