diff --git a/package.json b/package.json index 9b664feaa..5dc6f865f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "lint": "npx semistandard --fix", "sass": "sass scss/custom.scss client/style.css --style compressed", "start": "node server.js", + "test": "node --test", "webpack": "webpack --config webpack.config.js" }, "dependencies": { diff --git a/test/get-bonus-part-label.test.js b/test/get-bonus-part-label.test.js new file mode 100644 index 000000000..aad6b2f5a --- /dev/null +++ b/test/get-bonus-part-label.test.js @@ -0,0 +1,33 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import getBonusPartLabel from '../client/scripts/utilities/get-bonus-part-label.js'; + +test('returns default value and empty difficulty when bonus is undefined', () => { + assert.equal(getBonusPartLabel(undefined, 0), '[10]'); +}); + +test('uses bonus value when present', () => { + const bonus = { values: [15, 10, 5] }; + assert.equal(getBonusPartLabel(bonus, 0), '[15]'); + assert.equal(getBonusPartLabel(bonus, 2), '[5]'); +}); + +test('uses difficultyModifier when present', () => { + const bonus = { values: [10, 10, 10], difficultyModifiers: ['e', 'm', 'h'] }; + assert.equal(getBonusPartLabel(bonus, 0), '[10e]'); + assert.equal(getBonusPartLabel(bonus, 2), '[10h]'); +}); + +test('falls back to defaultValue when index has no value', () => { + const bonus = { values: [10] }; + assert.equal(getBonusPartLabel(bonus, 5), '[10]'); +}); + +test('uses custom defaultValue and defaultDifficulty', () => { + assert.equal(getBonusPartLabel(undefined, 0, 20, 'e'), '[20e]'); +}); + +test('returns label with no difficulty when difficultyModifiers absent', () => { + const bonus = { values: [10, 10, 10] }; + assert.equal(getBonusPartLabel(bonus, 1), '[10]'); +}); diff --git a/test/merge-two-sorted-arrays.test.js b/test/merge-two-sorted-arrays.test.js new file mode 100644 index 000000000..511bd43d2 --- /dev/null +++ b/test/merge-two-sorted-arrays.test.js @@ -0,0 +1,43 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import mergeTwoSortedArrays from '../server/merge-two-sorted-arrays.js'; + +const byId = x => x.id; +const keepFirst = (a, _b) => a; +const sumValues = (a, b) => ({ id: a.id, v: a.v + b.v }); + +test('merges two non-overlapping sorted arrays', () => { + const a = [{ id: 1 }, { id: 3 }]; + const b = [{ id: 2 }, { id: 4 }]; + const result = mergeTwoSortedArrays(a, b, byId, keepFirst); + assert.deepEqual(result.map(x => x.id), [1, 2, 3, 4]); +}); + +test('combines elements with duplicate keys via combineFunction', () => { + const a = [{ id: 1, v: 10 }, { id: 2, v: 20 }]; + const b = [{ id: 2, v: 5 }, { id: 3, v: 30 }]; + const result = mergeTwoSortedArrays(a, b, byId, sumValues); + assert.deepEqual(result, [{ id: 1, v: 10 }, { id: 2, v: 25 }, { id: 3, v: 30 }]); +}); + +test('returns copy of array1 when array2 is empty', () => { + const a = [{ id: 1 }, { id: 2 }]; + const result = mergeTwoSortedArrays(a, [], byId, keepFirst); + assert.deepEqual(result.map(x => x.id), [1, 2]); +}); + +test('returns copy of array2 when array1 is empty', () => { + const b = [{ id: 5 }, { id: 7 }]; + const result = mergeTwoSortedArrays([], b, byId, keepFirst); + assert.deepEqual(result.map(x => x.id), [5, 7]); +}); + +test('returns empty array when both inputs are empty', () => { + const result = mergeTwoSortedArrays([], [], byId, keepFirst); + assert.deepEqual(result, []); +}); + +test('handles single-element arrays', () => { + const result = mergeTwoSortedArrays([{ id: 3 }], [{ id: 1 }], byId, keepFirst); + assert.deepEqual(result.map(x => x.id), [1, 3]); +}); diff --git a/test/strings.test.js b/test/strings.test.js new file mode 100644 index 000000000..c7b82453f --- /dev/null +++ b/test/strings.test.js @@ -0,0 +1,55 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { escapeHTML, kebabCase, removeParentheses, titleCase } from '../client/scripts/utilities/strings.js'; + +test('escapeHTML escapes &, <, >, ", and single quotes', () => { + assert.equal(escapeHTML('a & b'), 'a & b'); + assert.equal(escapeHTML('bold'), '<b>bold</b>'); + assert.equal(escapeHTML('"quoted"'), '"quoted"'); + assert.equal(escapeHTML("it's"), 'it's'); +}); + +test('escapeHTML returns undefined for undefined input', () => { + assert.equal(escapeHTML(undefined), undefined); +}); + +test('escapeHTML returns empty string unchanged', () => { + assert.equal(escapeHTML(''), ''); +}); + +test('kebabCase converts spaces to hyphens and lowercases', () => { + assert.equal(kebabCase('Hello World'), 'hello-world'); + assert.equal(kebabCase('multiple spaces'), 'multiple-spaces'); + assert.equal(kebabCase('ALLCAPS'), 'allcaps'); +}); + +test('kebabCase handles already-lowercase single word', () => { + assert.equal(kebabCase('hello'), 'hello'); +}); + +test('removeParentheses removes round parens content', () => { + // The function removes the parens and their content but does not collapse + // the surrounding spaces — a trailing trim is the only whitespace cleanup. + assert.equal(removeParentheses('foo (bar) baz'), 'foo baz'); +}); + +test('removeParentheses removes square bracket content', () => { + assert.equal(removeParentheses('foo [bar] baz'), 'foo baz'); +}); + +test('removeParentheses trims surrounding whitespace', () => { + assert.equal(removeParentheses(' hello '), 'hello'); +}); + +test('removeParentheses returns string unchanged when no parens', () => { + assert.equal(removeParentheses('plain string'), 'plain string'); +}); + +test('titleCase capitalises first letter of each word', () => { + assert.equal(titleCase('hello-world'), 'Hello World'); + assert.equal(titleCase('foo-bar-baz'), 'Foo Bar Baz'); +}); + +test('titleCase handles single word without hyphen', () => { + assert.equal(titleCase('hello'), 'Hello'); +});