adding monkeytype
Some checks failed
Mark Stale PRs / stale (push) Has been cancelled

This commit is contained in:
Benjamin Falch
2026-04-23 13:53:44 +02:00
parent e214a2fd35
commit 2bc741fb78
1930 changed files with 7590652 additions and 0 deletions

View File

@@ -0,0 +1,498 @@
import { describe, it, expect, afterAll, vi } from "vitest";
import * as Misc from "../../src/utils/misc";
import { ObjectId } from "mongodb";
describe("Misc Utils", () => {
afterAll(() => {
vi.useRealTimers();
});
describe("matchesAPattern", () => {
const testCases = [
{
pattern: "eng.*",
cases: ["english", "aenglish", "en", "eng"],
expected: [true, false, false, true],
},
{
pattern: "\\d+",
cases: ["b", "2", "331", "1a"],
expected: [false, true, true, false],
},
{
pattern: "(hi|hello)",
cases: ["hello", "hi", "hillo", "hi hello"],
expected: [true, true, false, false],
},
{
pattern: ".+",
cases: ["a2", "b2", "c1", ""],
expected: [true, true, true, false],
},
];
it.each(testCases)(
"matchesAPattern with $pattern",
({ pattern, cases, expected }) => {
cases.forEach((caseValue, index) => {
expect(Misc.matchesAPattern(caseValue, pattern)).toBe(
expected[index],
);
});
},
);
});
describe("kogascore", () => {
const testCases = [
{
wpm: 214.8,
acc: 93.04,
timestamp: 1653586489000,
expectedScore: 1214800930423111,
},
{
wpm: 214.8,
acc: 93.04,
timestamp: 1653601763000,
expectedScore: 1214800930407837,
},
{
wpm: 199.37,
acc: 97.69,
timestamp: 1653588809000,
expectedScore: 1199370976920791,
},
{
wpm: 196.2,
acc: 96.07,
timestamp: 1653591901000,
expectedScore: 1196200960717699,
},
{
wpm: 196.205,
acc: 96.075,
timestamp: 1653591901000,
expectedScore: 1196210960817699,
},
{
// this one is particularly important - in JS 154.39 * 100 is equal to 15438.999999999998
// thanks floating point errors!
wpm: 154.39,
acc: 96.14,
timestamp: 1740333827000,
expectedScore: 1154390961421373,
},
];
it.each(testCases)(
"kogascore with wpm:$wpm, acc:$acc, timestamp:$timestamp = $expectedScore",
({ wpm, acc, timestamp, expectedScore }) => {
expect(Misc.kogascore(wpm, acc, timestamp)).toBe(expectedScore);
},
);
});
describe("identity", () => {
const testCases = [
{
input: "",
expected: "string",
},
{
input: {},
expected: "object",
},
{
input: 0,
expected: "number",
},
{
input: null,
expected: "null",
},
{
input: undefined,
expected: "undefined",
},
];
it.each(testCases)(
"identity with $input = $expected",
({ input, expected }) => {
expect(Misc.identity(input)).toBe(expected);
},
);
});
describe("flattenObjectDeep", () => {
const testCases = [
{
obj: {
a: {
b: {
c: 1,
},
},
d: 2,
e: [],
},
expected: {
"a.b.c": 1,
d: 2,
e: [],
},
},
{
obj: {
a: {
b: {
c: 1,
},
},
d: {
e: {
f: 2,
g: 3,
},
},
},
expected: {
"a.b.c": 1,
"d.e.f": 2,
"d.e.g": 3,
},
},
{
obj: {
a: {
b: {
c: 1,
d: {
e: 2,
f: 3,
g: {},
},
},
},
},
expected: {
"a.b.c": 1,
"a.b.d.e": 2,
"a.b.d.f": 3,
"a.b.d.g": {},
},
},
{
obj: {},
expected: {},
},
];
it.each(testCases)(
"flattenObjectDeep with $obj = $expected",
({ obj, expected }) => {
expect(Misc.flattenObjectDeep(obj)).toEqual(expected);
},
);
});
it("sanitizeString", () => {
const testCases = [
{
input: "h̶̼͔̭͈̏́̀́͋͜ͅe̵̺̞̦̫̫͔̋́̅̅̃̀͝͝ļ̶̬̯͚͇̺͍̞̫̟͖͋̓͛̆̒̓͜ĺ̴̗̘͇̬̆͂͌̈͊͝͝ỡ̴̡̦̩̠̞̐̃͆̚͠͝",
expected: "hello",
},
{
input: "hello",
expected: "hello",
},
{
input: "hel lo",
expected: "hel lo",
},
{
input: " hel lo ",
expected: "hel lo",
},
{
input: "",
expected: "",
},
{
input: " \n\n\n",
expected: "",
},
{
input: undefined,
expected: undefined,
},
];
testCases.forEach(({ input, expected }) => {
expect(Misc.sanitizeString(input)).toEqual(expected);
});
});
it("getOrdinalNumberString", () => {
const testCases = [
{
input: 0,
output: "0th",
},
{
input: 1,
output: "1st",
},
{
input: 2,
output: "2nd",
},
{
input: 3,
output: "3rd",
},
{
input: 4,
output: "4th",
},
{
input: 10,
output: "10th",
},
{
input: 11,
output: "11th",
},
{
input: 12,
output: "12th",
},
{
input: 13,
output: "13th",
},
{
input: 100,
output: "100th",
},
{
input: 101,
output: "101st",
},
{
input: 102,
output: "102nd",
},
{
input: 103,
output: "103rd",
},
{
input: 104,
output: "104th",
},
{
input: 93589423,
output: "93589423rd",
},
];
testCases.forEach(({ input, output }) => {
expect(Misc.getOrdinalNumberString(input)).toEqual(output);
});
});
it("formatSeconds", () => {
const testCases = [
{
seconds: 5,
expected: "5 seconds",
},
{
seconds: 65,
expected: "1.08 minutes",
},
{
seconds: Misc.HOUR_IN_SECONDS,
expected: "1 hour",
},
{
seconds: Misc.DAY_IN_SECONDS,
expected: "1 day",
},
{
seconds: Misc.WEEK_IN_SECONDS,
expected: "1 week",
},
{
seconds: Misc.YEAR_IN_SECONDS,
expected: "1 year",
},
{
seconds: 2 * Misc.YEAR_IN_SECONDS,
expected: "2 years",
},
{
seconds: 4 * Misc.YEAR_IN_SECONDS,
expected: "4 years",
},
{
seconds: 3 * Misc.WEEK_IN_SECONDS,
expected: "3 weeks",
},
{
seconds: Misc.MONTH_IN_SECONDS * 4,
expected: "4 months",
},
{
seconds: Misc.MONTH_IN_SECONDS * 11,
expected: "11 months",
},
];
testCases.forEach(({ seconds, expected }) => {
expect(Misc.formatSeconds(seconds)).toBe(expected);
});
});
describe("replaceObjectId", () => {
it("replaces objecId with string", () => {
const fromDatabase = {
_id: new ObjectId(),
test: "test",
number: 1,
};
expect(Misc.replaceObjectId(fromDatabase)).toStrictEqual({
_id: fromDatabase._id.toHexString(),
test: "test",
number: 1,
});
});
it("ignores null values", () => {
expect(Misc.replaceObjectId(null)).toBeNull();
});
});
describe("replaceObjectIds", () => {
it("replaces objecIds with string", () => {
const fromDatabase = {
_id: new ObjectId(),
test: "test",
number: 1,
};
const fromDatabase2 = {
_id: new ObjectId(),
test: "bob",
number: 2,
};
expect(
Misc.replaceObjectIds([fromDatabase, fromDatabase2]),
).toStrictEqual([
{
_id: fromDatabase._id.toHexString(),
test: "test",
number: 1,
},
{
_id: fromDatabase2._id.toHexString(),
test: "bob",
number: 2,
},
]);
});
it("handles undefined", () => {
expect(Misc.replaceObjectIds(undefined as any)).toBeUndefined();
});
});
describe("omit()", () => {
it("should omit a single key", () => {
const input = { a: 1, b: 2, c: 3 };
const result = Misc.omit(input, ["b"]);
expect(result).toEqual({ a: 1, c: 3 });
});
it("should omit multiple keys", () => {
const input = { a: 1, b: 2, c: 3, d: 4 };
const result = Misc.omit(input, ["a", "d"]);
expect(result).toEqual({ b: 2, c: 3 });
});
it("should return the same object if no keys are omitted", () => {
const input = { x: 1, y: 2 };
const result = Misc.omit(input, []);
expect(result).toEqual({ x: 1, y: 2 });
});
it("should not mutate the original object", () => {
const input = { foo: "bar", baz: "qux" };
const copy = { ...input };
Misc.omit(input, ["baz"]);
expect(input).toEqual(copy);
});
it("should ignore keys that do not exist", () => {
const input = { a: 1, b: 2 };
const result = Misc.omit(input, "c" as any); // allow a non-existing key
expect(result).toEqual({ a: 1, b: 2 });
});
it("should work with different value types", () => {
const input = {
str: "hello",
num: 123,
bool: true,
obj: { x: 1 },
arr: [1, 2, 3],
};
const result = Misc.omit(input, ["bool", "arr"]);
expect(result).toEqual({
str: "hello",
num: 123,
obj: { x: 1 },
});
});
});
describe("isPlainObject", () => {
it("should return true for plain objects", () => {
expect(Misc.isPlainObject({})).toBe(true);
expect(Misc.isPlainObject({ a: 1, b: 2 })).toBe(true);
expect(Misc.isPlainObject(Object.create(Object.prototype))).toBe(true);
});
it("should return false for arrays", () => {
expect(Misc.isPlainObject([])).toBe(false);
expect(Misc.isPlainObject([1, 2, 3])).toBe(false);
});
it("should return false for null", () => {
expect(Misc.isPlainObject(null)).toBe(false);
});
it("should return false for primitives", () => {
expect(Misc.isPlainObject(123)).toBe(false);
expect(Misc.isPlainObject("string")).toBe(false);
expect(Misc.isPlainObject(true)).toBe(false);
expect(Misc.isPlainObject(undefined)).toBe(false);
expect(Misc.isPlainObject(Symbol("sym"))).toBe(false);
});
it("should return false for objects with different prototypes", () => {
// oxlint-disable-next-line no-extraneous-class
class MyClass {}
expect(Misc.isPlainObject(new MyClass())).toBe(false);
expect(Misc.isPlainObject(Object.create(null))).toBe(false);
expect(Misc.isPlainObject(new Date())).toBe(false);
expect(Misc.isPlainObject(new Map())).toBe(false);
expect(Misc.isPlainObject(new Set())).toBe(false);
});
it("should return false for functions", () => {
// oxlint-disable-next-line no-empty-function
expect(Misc.isPlainObject(function () {})).toBe(false);
// oxlint-disable-next-line no-empty-function
expect(Misc.isPlainObject(() => {})).toBe(false);
});
});
});

View File

@@ -0,0 +1,21 @@
import { describe, it, expect } from "vitest";
import { buildMonkeyMail } from "../../src/utils/monkey-mail";
describe("Monkey Mail", () => {
it("should properly create a mail object", () => {
const mailConfig = {
subject: "",
body: "",
timestamp: Date.now(),
};
const mail = buildMonkeyMail(mailConfig) as any;
expect(mail.id).toBeDefined();
expect(mail.subject).toBe("");
expect(mail.body).toBe("");
expect(mail.timestamp).toBeDefined();
expect(mail.read).toBe(false);
expect(mail.rewards).toEqual([]);
});
});

View File

@@ -0,0 +1,213 @@
import { describe, it, expect } from "vitest";
import * as pb from "../../src/utils/pb";
import { Mode, PersonalBests } from "@monkeytype/schemas/shared";
import { Result } from "@monkeytype/schemas/results";
import { FunboxName } from "@monkeytype/schemas/configs";
describe("Pb Utils", () => {
describe("funboxCatGetPb", () => {
const testCases: { funbox: FunboxName[] | undefined; expected: boolean }[] =
[
{
funbox: ["plus_one"],
expected: true,
},
{
funbox: [],
expected: true,
},
{
funbox: undefined,
expected: true,
},
{
funbox: ["nausea", "plus_one"],
expected: true,
},
{
funbox: ["arrows"],
expected: false,
},
];
it.each(testCases)(
"canFunboxGetPb with $funbox = $expected",
({ funbox, expected }) => {
const result = pb.canFunboxGetPb({ funbox } as any);
expect(result).toBe(expected);
},
);
});
describe("checkAndUpdatePb", () => {
it("should update personal best", () => {
const userPbs: PersonalBests = {
time: {},
words: {},
custom: {},
quote: {},
zen: {},
};
const result = {
difficulty: "normal",
language: "english",
punctuation: false,
lazyMode: false,
acc: 100,
consistency: 100,
rawWpm: 100,
wpm: 110,
numbers: false,
mode: "time",
mode2: "15",
} as unknown as Result<Mode>;
const run = pb.checkAndUpdatePb(
userPbs,
{} as pb.LbPersonalBests,
result,
);
expect(run.isPb).toBe(true);
expect(run.personalBests.time?.["15"]?.[0]).not.toBe(undefined);
expect(run.lbPersonalBests).not.toBe({});
});
it("should not override default pb when saving numbers test", () => {
const userPbs: PersonalBests = {
time: {
"15": [
{
acc: 100,
consistency: 100,
difficulty: "normal",
lazyMode: false,
language: "english",
numbers: false,
punctuation: false,
raw: 100,
timestamp: 0,
wpm: 100,
},
],
},
words: {},
custom: {},
quote: {},
zen: {},
};
const result = {
difficulty: "normal",
language: "english",
punctuation: false,
lazyMode: false,
acc: 100,
consistency: 100,
rawWpm: 100,
wpm: 110,
numbers: true,
mode: "time",
mode2: "15",
} as unknown as Result<Mode>;
const run = pb.checkAndUpdatePb(userPbs, undefined, result);
expect(run.isPb).toBe(true);
expect(run.personalBests.time?.["15"]).toEqual(
expect.arrayContaining([
expect.objectContaining({ numbers: false, wpm: 100 }),
expect.objectContaining({ numbers: true, wpm: 110 }),
]),
);
});
});
describe("updateLeaderboardPersonalBests", () => {
const userPbs: PersonalBests = {
time: {
"15": [
{
acc: 100,
consistency: 100,
difficulty: "normal",
lazyMode: false,
language: "english",
numbers: false,
punctuation: false,
raw: 100,
timestamp: 0,
wpm: 100,
},
{
acc: 100,
consistency: 100,
difficulty: "normal",
lazyMode: false,
language: "spanish",
numbers: false,
punctuation: false,
raw: 100,
timestamp: 0,
wpm: 100,
},
],
},
words: {},
custom: {},
quote: {},
zen: {},
};
it("should update leaderboard personal bests if they dont exist or the structure is incomplete", () => {
const lbpbstartingvalues = [
undefined,
{},
{ time: {} },
{ time: { "15": {} } },
{ time: { "15": { english: {} } } },
];
const result15 = {
mode: "time",
mode2: "15",
} as unknown as Result<Mode>;
for (const lbPb of lbpbstartingvalues) {
const lbPbPb = pb.updateLeaderboardPersonalBests(
userPbs,
structuredClone(lbPb) as pb.LbPersonalBests,
result15,
);
expect(lbPbPb).toEqual({
time: {
"15": {
english: {
acc: 100,
consistency: 100,
difficulty: "normal",
lazyMode: false,
language: "english",
numbers: false,
punctuation: false,
raw: 100,
timestamp: 0,
wpm: 100,
},
spanish: {
acc: 100,
consistency: 100,
difficulty: "normal",
lazyMode: false,
language: "spanish",
numbers: false,
punctuation: false,
raw: 100,
timestamp: 0,
wpm: 100,
},
},
},
});
}
});
});
});

View File

@@ -0,0 +1,220 @@
import { describe, it, expect } from "vitest";
import { replaceLegacyValues, DBResult } from "../../src/utils/result";
describe("Result Utils", () => {
describe("replaceLegacyValues", () => {
describe("legacy charStats conversion", () => {
it.each([
{
description:
"should convert correctChars and incorrectChars to charStats",
correctChars: 95,
incorrectChars: 5,
expectedCharStats: [95, 5, 0, 0],
},
{
description: "should handle zero values for legacy chars",
correctChars: 0,
incorrectChars: 0,
expectedCharStats: [0, 0, 0, 0],
},
{
description: "should handle large values for legacy chars",
correctChars: 9999,
incorrectChars: 1234,
expectedCharStats: [9999, 1234, 0, 0],
},
])(
"$description",
({ correctChars, incorrectChars, expectedCharStats }) => {
const resultWithLegacyChars: DBResult = {
correctChars,
incorrectChars,
} as any;
const result = replaceLegacyValues(resultWithLegacyChars);
expect(result.charStats).toEqual(expectedCharStats);
expect(result.correctChars).toBeUndefined();
expect(result.incorrectChars).toBeUndefined();
},
);
it("should prioritise charStats when legacy data exists", () => {
const resultWithBothFormats: DBResult = {
charStats: [80, 4, 2, 1],
correctChars: 95,
incorrectChars: 5,
} as any;
const result = replaceLegacyValues(resultWithBothFormats);
// Should convert legacy values and overwrite existing charStats
expect(result.charStats).toEqual([80, 4, 2, 1]);
// Legacy values should be removed after conversion
expect(result.correctChars).toBeUndefined();
expect(result.incorrectChars).toBeUndefined();
});
it.each([
{
description:
"should not convert when only one legacy property is present",
input: { correctChars: 95 },
expectedCharStats: undefined,
expectedCorrectChars: 95,
expectedIncorrectChars: undefined,
},
{
description: "should not convert when only incorrectChars is present",
input: { incorrectChars: 5 },
expectedCharStats: undefined,
expectedCorrectChars: undefined,
expectedIncorrectChars: 5,
},
])(
"$description",
({
input,
expectedCharStats,
expectedCorrectChars,
expectedIncorrectChars,
}) => {
const result = replaceLegacyValues(input as any);
// Should not convert since both properties are required
expect(result.charStats).toBe(expectedCharStats);
expect(result.correctChars).toBe(expectedCorrectChars);
expect(result.incorrectChars).toBe(expectedIncorrectChars);
},
);
});
describe("legacy funbox conversion", () => {
it.each([
{
description: "should convert string funbox to array",
input: "memory#mirror",
expected: ["memory", "mirror"],
},
{
description: "should convert single funbox string to array",
input: "memory",
expected: ["memory"],
},
{
description: "should convert 'none' funbox to empty array",
input: "none",
expected: [],
},
{
description: "should handle complex funbox combinations",
input: "memory#mirror#arrows#58008",
expected: ["memory", "mirror", "arrows", "58008"],
},
])("$description", ({ input, expected }) => {
const resultWithStringFunbox: DBResult = {
funbox: input as any,
} as any;
const result = replaceLegacyValues(resultWithStringFunbox);
expect(result.funbox).toEqual(expected);
});
});
describe("legacy chartData conversion", () => {
it("should convert chartData with 'raw' property to 'burst'", () => {
const resultWithLegacyChartData: DBResult = {
chartData: {
wpm: [50, 55, 60],
raw: [52, 57, 62],
err: [1, 0, 2],
} as any,
} as any;
const result = replaceLegacyValues(resultWithLegacyChartData);
expect(result.chartData).toEqual({
wpm: [50, 55, 60],
burst: [52, 57, 62],
err: [1, 0, 2],
});
});
it("should not convert chartData when it's 'toolong'", () => {
const resultWithToolongChartData: DBResult = {
chartData: "toolong",
} as any;
const result = replaceLegacyValues(resultWithToolongChartData);
expect(result.chartData).toBe("toolong");
});
it("should not convert chartData when it doesn't have 'raw' property", () => {
const resultWithModernChartData: DBResult = {
chartData: {
wpm: [50, 55, 60],
burst: [52, 57, 62],
err: [1, 0, 2],
},
} as any;
const result = replaceLegacyValues(resultWithModernChartData);
expect(result.chartData).toEqual({
wpm: [50, 55, 60],
burst: [52, 57, 62],
err: [1, 0, 2],
});
});
it("should not convert chartData when it's undefined", () => {
const resultWithoutChartData: DBResult = {} as any;
const result = replaceLegacyValues(resultWithoutChartData);
expect(result.chartData).toBeUndefined();
});
});
it("should convert all legacy data at once", () => {
const resultWithAllLegacy: DBResult = {
correctChars: 100,
incorrectChars: 8,
funbox: "memory#mirror" as any,
chartData: {
wpm: [50, 55, 60],
raw: [52, 57, 62],
err: [1, 0, 2],
} as any,
} as any;
const result = replaceLegacyValues(resultWithAllLegacy);
expect(result.charStats).toEqual([100, 8, 0, 0]);
expect(result.correctChars).toBeUndefined();
expect(result.incorrectChars).toBeUndefined();
expect(result.funbox).toEqual(["memory", "mirror"]);
expect(result.chartData).toEqual({
wpm: [50, 55, 60],
burst: [52, 57, 62],
err: [1, 0, 2],
});
});
describe("no legacy values", () => {
it("should return result unchanged when no legacy values present", () => {
const modernResult: DBResult = {
charStats: [95, 5, 2, 1],
funbox: ["memory"],
} as any;
const result = replaceLegacyValues(modernResult);
expect(result).toEqual(modernResult);
});
});
});
});

View File

@@ -0,0 +1,55 @@
import { describe, it, expect } from "vitest";
import * as Validation from "../../src/utils/validation";
describe("Validation", () => {
it("isTestTooShort", () => {
const testCases = [
{
result: {
mode: "time",
mode2: 10,
customText: undefined,
testDuration: 10,
bailedOut: false,
},
expected: true,
},
{
result: {
mode: "time",
mode2: 15,
customText: undefined,
testDuration: 15,
bailedOut: false,
},
expected: false,
},
{
result: {
mode: "time",
mode2: 0,
customText: undefined,
testDuration: 20,
bailedOut: false,
},
expected: false,
},
{
result: {
mode: "time",
mode2: 0,
customText: undefined,
testDuration: 2,
bailedOut: false,
},
expected: true,
},
];
testCases.forEach((testCase) => {
expect(Validation.isTestTooShort(testCase.result as any)).toBe(
testCase.expected,
);
});
});
});