Skip to content

Commit 841dbe3

Browse files
committed
refactor: reorganize funbox validation logic and update imports
1 parent f3f0e9e commit 841dbe3

9 files changed

Lines changed: 209 additions & 236 deletions

File tree

frontend/__tests__/root/config.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
Config as ConfigType,
1111
CaretStyleSchema,
1212
} from "@monkeytype/schemas/configs";
13-
import * as FunboxValidation from "../../src/ts/test/funbox/funbox-validation";
13+
import * as FunboxValidation from "../../src/ts/config/funbox-validation";
1414
import * as ConfigValidation from "../../src/ts/config/validation";
1515
import * as ConfigEvent from "../../src/ts/observables/config-event";
1616
import * as ApeConfig from "../../src/ts/ape/config";

frontend/__tests__/test/funbox/funbox-validation.spec.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
1-
import { describe, it, expect, afterEach, vi } from "vitest";
2-
import { canSetConfigWithCurrentFunboxes } from "../../../src/ts/test/funbox/funbox-validation";
1+
import { describe, it, expect } from "vitest";
2+
import { canSetConfigWithCurrentFunboxes } from "../../../src/ts/config/funbox-validation";
33

4-
import * as Notifications from "../../../src/ts/states/notifications";
54
import { FunboxName } from "@monkeytype/schemas/configs";
65
describe("funbox-validation", () => {
76
describe("canSetConfigWithCurrentFunboxes", () => {
8-
const addNotificationMock = vi.spyOn(
9-
Notifications,
10-
"showNoticeNotification",
11-
);
12-
afterEach(() => {
13-
addNotificationMock.mockClear();
14-
});
15-
167
const testCases = [
178
//checks for frontendForcedConfig
189
{
1910
key: "mode",
2011
value: "zen",
2112
funbox: ["memory"],
22-
error: "You can't set mode to zen with currently active funboxes.",
13+
expected: false,
2314
},
24-
{ key: "mode", value: "words", funbox: ["memory"] }, //ok
15+
{ key: "mode", value: "words", funbox: ["memory"], expected: true },
2516

2617
//checks for zen mode
2718
...[
@@ -40,10 +31,15 @@ describe("funbox-validation", () => {
4031
key: "mode",
4132
value: "zen",
4233
funbox: [funbox],
43-
error: "You can't set mode to zen with currently active funboxes.",
34+
expected: false,
4435
})),
45-
{ key: "mode", value: "zen", funbox: ["mirror"] }, //ok
46-
{ key: "mode", value: "zen", funbox: ["space_balls"] }, //no frontendFunctions
36+
{ key: "mode", value: "zen", funbox: ["mirror"], expected: true },
37+
{
38+
key: "mode",
39+
value: "zen",
40+
funbox: ["space_balls"],
41+
expected: true,
42+
},
4743

4844
//checks for words and custom
4945
...["quote", "custom"].flatMap((value) =>
@@ -56,23 +52,22 @@ describe("funbox-validation", () => {
5652
key: "mode",
5753
value,
5854
funbox: [funbox],
59-
error: `You can't set mode to ${value} with currently active funboxes.`,
55+
expected: false,
6056
})),
6157
),
62-
{ key: "mode", value: "quote", funbox: ["space_balls"] }, //no frontendFunctions
58+
{
59+
key: "mode",
60+
value: "quote",
61+
funbox: ["space_balls"],
62+
expected: true,
63+
},
6364
];
6465
it.for(testCases)(
6566
`check $funbox with $key=$value`,
66-
({ key, value, funbox, error }) => {
67+
({ key, value, funbox, expected }) => {
6768
expect(
6869
canSetConfigWithCurrentFunboxes(key, value, funbox as FunboxName[]),
69-
).toBe(error === undefined);
70-
71-
if (error !== undefined) {
72-
expect(addNotificationMock).toHaveBeenCalledWith(error, {
73-
durationMs: 5000,
74-
});
75-
}
70+
).toBe(expected);
7671
},
7772
);
7873
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { checkForcedConfig, getFunbox } from "@monkeytype/funbox";
2+
import { Config, ConfigValue, FunboxName } from "@monkeytype/schemas/configs";
3+
4+
export function canSetConfigWithCurrentFunboxes(
5+
key: string,
6+
value: ConfigValue,
7+
funbox: FunboxName[] = [],
8+
): boolean {
9+
const funboxes = getFunbox(funbox);
10+
if (key === "mode") {
11+
let fb = getFunbox(funbox).filter(
12+
(f) =>
13+
f.frontendForcedConfig?.["mode"] !== undefined &&
14+
!(f.frontendForcedConfig["mode"] as ConfigValue[]).includes(value),
15+
);
16+
if (value === "zen") {
17+
fb = fb.concat(
18+
funboxes.filter((f) => {
19+
const funcs = f.frontendFunctions ?? [];
20+
const props = f.properties ?? [];
21+
return (
22+
funcs.includes("getWord") ||
23+
funcs.includes("pullSection") ||
24+
funcs.includes("alterText") ||
25+
funcs.includes("withWords") ||
26+
props.includes("changesCapitalisation") ||
27+
props.includes("nospace") ||
28+
props.some((fp) => fp.startsWith("toPush:")) ||
29+
props.includes("changesWordsVisibility") ||
30+
props.includes("speaks") ||
31+
props.includes("changesLayout") ||
32+
props.includes("changesWordsFrequency")
33+
);
34+
}),
35+
);
36+
}
37+
if (value === "quote" || value === "custom") {
38+
fb = fb.concat(
39+
funboxes.filter((f) => {
40+
const funcs = f.frontendFunctions ?? [];
41+
const props = f.properties ?? [];
42+
return (
43+
funcs.includes("getWord") ||
44+
funcs.includes("pullSection") ||
45+
funcs.includes("withWords") ||
46+
props.includes("changesWordsFrequency")
47+
);
48+
}),
49+
);
50+
}
51+
52+
if (fb.length > 0) {
53+
return false;
54+
}
55+
}
56+
if (!checkForcedConfig(key, value, funboxes).result) {
57+
return false;
58+
}
59+
60+
return true;
61+
}
62+
63+
export type FunboxConfigError = {
64+
key: string;
65+
value: ConfigValue;
66+
};
67+
68+
export function canSetFunboxWithConfig(
69+
funbox: FunboxName,
70+
config: Config,
71+
): { ok: true } | { ok: false; errors: FunboxConfigError[] } {
72+
const funboxToCheck = [...config.funbox, funbox];
73+
74+
const errors: FunboxConfigError[] = [];
75+
for (const [configKey, configValue] of Object.entries(config)) {
76+
if (
77+
!canSetConfigWithCurrentFunboxes(configKey, configValue, funboxToCheck)
78+
) {
79+
errors.push({
80+
key: configKey,
81+
value: configValue,
82+
});
83+
}
84+
}
85+
if (errors.length > 0) {
86+
return { ok: false, errors };
87+
}
88+
return { ok: true };
89+
}

frontend/src/ts/config/metadata.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { checkCompatibility } from "@monkeytype/funbox";
22
import * as DB from "../db";
33
import { showNoticeNotification } from "../states/notifications";
44
import { isAuthenticated } from "../firebase";
5-
import { canSetFunboxWithConfig } from "../test/funbox/funbox-validation";
5+
import { canSetFunboxWithConfig } from "./funbox-validation";
66
import { reloadAfter } from "../utils/misc";
77
import { isDevEnvironment } from "../utils/env";
88
import * as ConfigSchemas from "@monkeytype/schemas/configs";
@@ -307,9 +307,10 @@ export const configMetadata: ConfigMetadataObject = {
307307
}
308308

309309
for (const funbox of value) {
310-
if (!canSetFunboxWithConfig(funbox, currentConfig)) {
310+
const check = canSetFunboxWithConfig(funbox, currentConfig);
311+
if (!check.ok) {
311312
showNoticeNotification(
312-
`${value}" cannot be enabled with the current config`,
313+
`"${funbox}" cannot be enabled with the current config`,
313314
);
314315
return true;
315316
}

frontend/src/ts/config/setters.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import { showNoticeNotification } from "../states/notifications";
88
import {
99
canSetConfigWithCurrentFunboxes,
1010
canSetFunboxWithConfig,
11-
} from "../test/funbox/funbox-validation";
11+
} from "./funbox-validation";
1212
import * as TestState from "../test/test-state";
13-
import { typedKeys, triggerResize } from "../utils/misc";
13+
import { typedKeys, triggerResize, escapeHTML } from "../utils/misc";
14+
import { camelCaseToWords, capitalizeFirstLetter } from "../utils/strings";
1415
import { Config, setConfigStore } from "./store";
1516
import { FunboxName } from "@monkeytype/schemas/configs";
1617

@@ -77,6 +78,16 @@ export function setConfig<T extends keyof ConfigSchemas.Config>(
7778
}
7879

7980
if (!canSetConfigWithCurrentFunboxes(key, value, Config.funbox)) {
81+
if (key === "words" || key === "time") {
82+
showNoticeNotification("Active funboxes do not support infinite tests");
83+
} else {
84+
showNoticeNotification(
85+
`You can't set ${camelCaseToWords(
86+
key,
87+
)} to ${String(value)} with currently active funboxes.`,
88+
{ durationMs: 5000 },
89+
);
90+
}
8091
console.warn(
8192
`Could not set config key "${key}" with value "${JSON.stringify(
8293
value,
@@ -152,7 +163,18 @@ export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean {
152163
return false;
153164
}
154165

155-
if (!canSetFunboxWithConfig(funbox, Config)) {
166+
const funboxCheck = canSetFunboxWithConfig(funbox, Config);
167+
if (!funboxCheck.ok) {
168+
const errorStrings = funboxCheck.errors.map(
169+
(e) =>
170+
`${capitalizeFirstLetter(
171+
camelCaseToWords(e.key),
172+
)} cannot be set to ${String(e.value)}.`,
173+
);
174+
showNoticeNotification(
175+
`You can't enable ${escapeHTML(funbox.replace(/_/g, " "))}:<br />${errorStrings.map((s) => escapeHTML(s)).join("<br />")}`,
176+
{ durationMs: 5000, useInnerHtml: true },
177+
);
156178
return false;
157179
}
158180

0 commit comments

Comments
 (0)