Skip to content

Commit 568012a

Browse files
fehmerMiodec
andauthored
fix(presets): preset with minWpmCustom should not activate minWpm (@fehmer, @Miodec) (monkeytypegame#6857)
Co-authored-by: Miodec <[email protected]>
1 parent a387d82 commit 568012a

File tree

6 files changed

+223
-49
lines changed

6 files changed

+223
-49
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import * as PresetController from "../../src/ts/controllers/preset-controller";
2+
import { Preset } from "@monkeytype/schemas/presets";
3+
import * as DB from "../../src/ts/db";
4+
import * as UpdateConfig from "../../src/ts/config";
5+
import * as Notifications from "../../src/ts/elements/notifications";
6+
import * as TestLogic from "../../src/ts/test/test-logic";
7+
import * as TagController from "../../src/ts/controllers/tag-controller";
8+
import { deepClone } from "../../src/ts/utils/misc";
9+
10+
describe("PresetController", () => {
11+
describe("apply", () => {
12+
vi.mock("../../src/ts/test/test-logic", () => ({
13+
restart: vi.fn(),
14+
}));
15+
const dbGetSnapshotMock = vi.spyOn(DB, "getSnapshot");
16+
const configApplyMock = vi.spyOn(UpdateConfig, "apply");
17+
const configSaveFullConfigMock = vi.spyOn(
18+
UpdateConfig,
19+
"saveFullConfigToLocalStorage"
20+
);
21+
const configGetConfigChangesMock = vi.spyOn(
22+
UpdateConfig,
23+
"getConfigChanges"
24+
);
25+
const notificationAddMock = vi.spyOn(Notifications, "add");
26+
const testRestartMock = vi.spyOn(TestLogic, "restart");
27+
const tagControllerClearMock = vi.spyOn(TagController, "clear");
28+
const tagControllerSetMock = vi.spyOn(TagController, "set");
29+
const tagControllerSaveActiveMock = vi.spyOn(
30+
TagController,
31+
"saveActiveToLocalStorage"
32+
);
33+
34+
beforeEach(() => {
35+
[
36+
dbGetSnapshotMock,
37+
configApplyMock,
38+
configSaveFullConfigMock,
39+
configGetConfigChangesMock,
40+
notificationAddMock,
41+
testRestartMock,
42+
tagControllerClearMock,
43+
tagControllerSetMock,
44+
tagControllerSaveActiveMock,
45+
].forEach((it) => it.mockClear());
46+
47+
configApplyMock.mockResolvedValue();
48+
});
49+
50+
it("should apply for full preset", async () => {
51+
//GIVEN
52+
const preset = givenPreset({ config: { punctuation: true } });
53+
54+
//WHEN
55+
await PresetController.apply(preset._id);
56+
57+
//THEN
58+
expect(configApplyMock).toHaveBeenCalledWith(preset.config);
59+
expect(tagControllerClearMock).toHaveBeenCalled();
60+
expect(testRestartMock).toHaveBeenCalled();
61+
expect(notificationAddMock).toHaveBeenCalledWith("Preset applied", 1, {
62+
duration: 2,
63+
});
64+
expect(configSaveFullConfigMock).toHaveBeenCalled();
65+
});
66+
67+
it("should apply for full preset with tags", async () => {
68+
//GIVEN
69+
const preset = givenPreset({
70+
config: { tags: ["tagOne", "tagTwo"] },
71+
});
72+
73+
//WHEN
74+
await PresetController.apply(preset._id);
75+
76+
//THEN
77+
expect(tagControllerClearMock).toHaveBeenCalled();
78+
expect(tagControllerSetMock).toHaveBeenNthCalledWith(
79+
1,
80+
"tagOne",
81+
true,
82+
false
83+
);
84+
expect(tagControllerSetMock).toHaveBeenNthCalledWith(
85+
2,
86+
"tagTwo",
87+
true,
88+
false
89+
);
90+
expect(tagControllerSaveActiveMock).toHaveBeenCalled();
91+
});
92+
93+
it("should ignore unknown preset", async () => {
94+
//WHEN
95+
await PresetController.apply("unknown");
96+
//THEN
97+
expect(notificationAddMock).not.toHaveBeenCalled();
98+
expect(configApplyMock).not.toHaveBeenCalled();
99+
});
100+
101+
it("should apply for partial preset", async () => {
102+
//GIVEN
103+
const preset = givenPreset({
104+
config: { punctuation: true },
105+
settingGroups: ["test"],
106+
});
107+
108+
UpdateConfig.setNumbers(true);
109+
const oldConfig = deepClone(UpdateConfig.default);
110+
111+
//WHEN
112+
await PresetController.apply(preset._id);
113+
114+
//THEN
115+
expect(configApplyMock).toHaveBeenCalledWith({
116+
...oldConfig,
117+
numbers: true,
118+
punctuation: true,
119+
});
120+
expect(testRestartMock).toHaveBeenCalled();
121+
expect(notificationAddMock).toHaveBeenCalledWith("Preset applied", 1, {
122+
duration: 2,
123+
});
124+
expect(configSaveFullConfigMock).toHaveBeenCalled();
125+
});
126+
127+
it("should apply for partial preset with tags", async () => {
128+
//GIVEN
129+
const preset = givenPreset({
130+
config: { tags: ["tagOne", "tagTwo"] },
131+
settingGroups: ["test", "behavior"],
132+
});
133+
134+
//WHEN
135+
await PresetController.apply(preset._id);
136+
137+
//THEN
138+
expect(tagControllerClearMock).toHaveBeenCalled();
139+
expect(tagControllerSetMock).toHaveBeenNthCalledWith(
140+
1,
141+
"tagOne",
142+
true,
143+
false
144+
);
145+
expect(tagControllerSetMock).toHaveBeenNthCalledWith(
146+
2,
147+
"tagTwo",
148+
true,
149+
false
150+
);
151+
expect(tagControllerSaveActiveMock).toHaveBeenCalled();
152+
});
153+
154+
const givenPreset = (partialPreset: Partial<Preset>): Preset => {
155+
const preset: Preset = {
156+
_id: "1",
157+
...partialPreset,
158+
} as any;
159+
dbGetSnapshotMock.mockReturnValue({ presets: [preset] } as any);
160+
return preset;
161+
};
162+
});
163+
});

frontend/__tests__/root/config.spec.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,9 @@ describe("Config", () => {
11001100
describe("apply", () => {
11011101
it("should fill missing values with defaults", async () => {
11021102
//GIVEN
1103+
replaceConfig({
1104+
mode: "words",
1105+
});
11031106
await Config.apply({
11041107
numbers: true,
11051108
punctuation: true,
@@ -1198,26 +1201,46 @@ describe("Config", () => {
11981201
numbers: true,
11991202
});
12001203
await Config.apply({
1204+
...Config.getConfigChanges(),
12011205
punctuation: true,
12021206
});
12031207
const config = getConfig();
12041208
expect(config.numbers).toBe(true);
12051209
});
12061210

1207-
it("should reset all values to default if fullReset is true", async () => {
1211+
it("should not enable minWpm if not provided", async () => {
12081212
replaceConfig({
1209-
numbers: true,
1210-
theme: "serika",
1213+
minWpm: "off",
1214+
});
1215+
await Config.apply({
1216+
minWpmCustomSpeed: 100,
1217+
});
1218+
const config = getConfig();
1219+
expect(config.minWpm).toBe("off");
1220+
expect(config.minWpmCustomSpeed).toEqual(100);
1221+
});
1222+
1223+
it("should apply minWpm if part of the full config", async () => {
1224+
replaceConfig({
1225+
minWpm: "off",
1226+
});
1227+
await Config.apply({
1228+
minWpm: "custom",
1229+
minWpmCustomSpeed: 100,
1230+
});
1231+
const config = getConfig();
1232+
expect(config.minWpm).toBe("custom");
1233+
expect(config.minWpmCustomSpeed).toEqual(100);
1234+
});
1235+
1236+
it("should keep the keymap off when applying keymapLayout", async () => {
1237+
replaceConfig({});
1238+
await Config.apply({
1239+
keymapLayout: "qwerty",
12111240
});
1212-
await Config.apply(
1213-
{
1214-
punctuation: true,
1215-
},
1216-
true
1217-
);
12181241
const config = getConfig();
1219-
expect(config.numbers).toBe(false);
1220-
expect(config.theme).toEqual("serika_dark");
1242+
expect(config.keymapLayout).toEqual("qwerty");
1243+
expect(config.keymapMode).toEqual("off");
12211244
});
12221245
});
12231246
});

frontend/src/ts/config.ts

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@ import { Config, FunboxName } from "@monkeytype/schemas/configs";
2020
import { Mode } from "@monkeytype/schemas/shared";
2121
import { Language } from "@monkeytype/schemas/languages";
2222
import { LocalStorageWithSchema } from "./utils/local-storage-with-schema";
23-
import {
24-
migrateConfig,
25-
replaceLegacyValues,
26-
sanitizeConfig,
27-
} from "./utils/config";
23+
import { migrateConfig } from "./utils/config";
2824
import { getDefaultConfig } from "./constants/default-config";
2925
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
3026
import { ZodSchema } from "zod";
@@ -819,41 +815,28 @@ const lastConfigsToApply: Set<keyof Config> = new Set([
819815
"funbox",
820816
]);
821817

822-
export async function apply(
823-
configToApply: Config | Partial<Config>,
824-
fullReset = false
825-
): Promise<void> {
826-
if (configToApply === undefined || configToApply === null) return;
818+
export async function apply(partialConfig: Partial<Config>): Promise<void> {
819+
if (partialConfig === undefined || partialConfig === null) return;
827820

828-
//remove additional keys, migrate old values if needed
829-
configToApply = sanitizeConfig(replaceLegacyValues(configToApply));
821+
//migrate old values if needed, remove additional keys and merge with default config
822+
const fullConfig: Config = migrateConfig(partialConfig);
830823

831824
ConfigEvent.dispatch("fullConfigChange");
832825

833826
const defaultConfig = getDefaultConfig();
834-
for (const key of typedKeys(fullReset ? defaultConfig : configToApply)) {
827+
for (const key of typedKeys(fullConfig)) {
835828
//@ts-expect-error this is fine, both are of type config
836829
config[key] = defaultConfig[key];
837830
}
838831

839-
const partialKeys = typedKeys(configToApply);
840-
const partialKeysToApplyFirst = partialKeys.filter(
832+
const configKeysToReset: (keyof Config)[] = [];
833+
834+
const firstKeys = typedKeys(fullConfig).filter(
841835
(key) => !lastConfigsToApply.has(key)
842836
);
843-
const partialKeysToApplyLast = Array.from(lastConfigsToApply.values()).filter(
844-
(key) => partialKeys.includes(key)
845-
);
846-
const partialKeysToApply = [
847-
...partialKeysToApplyFirst,
848-
...partialKeysToApplyLast,
849-
];
850-
851-
const configKeysToReset: (keyof Config)[] = [];
852837

853-
for (const configKey of partialKeysToApply) {
854-
const configValue = configToApply[
855-
configKey
856-
] as ConfigSchemas.Config[keyof Config];
838+
for (const configKey of [...firstKeys, ...lastConfigsToApply]) {
839+
const configValue = fullConfig[configKey];
857840

858841
const set = genericSet(configKey, configValue, true);
859842

@@ -888,7 +871,7 @@ export async function loadFromLocalStorage(): Promise<void> {
888871
if (newConfig === undefined) {
889872
await reset();
890873
} else {
891-
await apply(newConfig, true);
874+
await apply(newConfig);
892875
saveFullConfigToLocalStorage(true);
893876
}
894877
loadDone();
@@ -921,7 +904,7 @@ export async function applyFromJson(json: string): Promise<void> {
921904
},
922905
}
923906
);
924-
await apply(parsedConfig, true);
907+
await apply(parsedConfig);
925908
saveFullConfigToLocalStorage();
926909
Notifications.add("Done", 1);
927910
} catch (e) {

frontend/src/ts/controllers/account-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ async function getDataAndInit(): Promise<boolean> {
154154
console.log(
155155
"no local config or local and db configs are different - applying db"
156156
);
157-
await UpdateConfig.apply(snapshot.config, true);
157+
await UpdateConfig.apply(snapshot.config);
158158
UpdateConfig.saveFullConfigToLocalStorage(true);
159159

160160
//funboxes might be different and they wont activate on the account page

frontend/src/ts/controllers/preset-controller.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Preset } from "@monkeytype/schemas/presets";
2-
import * as UpdateConfig from "../config";
2+
import Config, * as UpdateConfig from "../config";
33
import * as DB from "../db";
44
import * as Notifications from "../elements/notifications";
55
import * as TestLogic from "../test/test-logic";
66
import * as TagController from "./tag-controller";
77
import { SnapshotPreset } from "../constants/default-snapshot";
8+
import { deepClone } from "../utils/misc";
89

910
export async function apply(_id: string): Promise<void> {
1011
const snapshot = DB.getSnapshot();
@@ -15,10 +16,14 @@ export async function apply(_id: string): Promise<void> {
1516
return;
1617
}
1718

18-
await UpdateConfig.apply(
19-
presetToApply.config,
20-
!isPartialPreset(presetToApply)
21-
);
19+
if (isPartialPreset(presetToApply)) {
20+
await UpdateConfig.apply({
21+
...deepClone(Config),
22+
...presetToApply.config,
23+
});
24+
} else {
25+
await UpdateConfig.apply(presetToApply.config);
26+
}
2227

2328
if (
2429
!isPartialPreset(presetToApply) ||

frontend/src/ts/utils/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ function mergeWithDefaultConfig(config: PartialConfig): Config {
3030
/**
3131
* remove all values from the config which are not valid
3232
*/
33-
export function sanitizeConfig(
33+
function sanitizeConfig(
3434
config: ConfigSchemas.PartialConfig
3535
): ConfigSchemas.PartialConfig {
3636
//make sure to use strip()
3737
return sanitize(ConfigSchemas.PartialConfigSchema.strip(), config);
3838
}
3939

40-
export function replaceLegacyValues(
40+
function replaceLegacyValues(
4141
configObj: ConfigSchemas.PartialConfig
4242
): ConfigSchemas.PartialConfig {
4343
//@ts-expect-error legacy configs

0 commit comments

Comments
 (0)