Skip to content

Commit 08d2430

Browse files
authored
fix(migrations): traverse nested template choices (#1152)
* fix(migrations): traverse nested template choices * fix(migrations): harden file exists backfill
1 parent 5ea1db0 commit 08d2430

File tree

2 files changed

+155
-38
lines changed

2 files changed

+155
-38
lines changed

src/migrations/consolidateFileExistsBehavior.test.ts

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CommandType } from "../types/macros/CommandType";
12
import { describe, expect, it } from "vitest";
23
import migration from "./consolidateFileExistsBehavior";
34

@@ -51,7 +52,52 @@ describe("consolidateFileExistsBehavior migration", () => {
5152
});
5253
});
5354

54-
it("normalizes nested macro command template choices", async () => {
55+
it("normalizes template choices nested inside Macro choice commands", async () => {
56+
const plugin = {
57+
settings: {
58+
choices: [
59+
{
60+
id: "macro-choice",
61+
name: "Macro Choice",
62+
type: "Macro",
63+
macro: {
64+
id: "macro-1",
65+
name: "Macro",
66+
commands: [
67+
{
68+
type: CommandType.NestedChoice,
69+
choice: {
70+
id: "template-choice",
71+
name: "Template",
72+
type: "Template",
73+
setFileExistsBehavior: true,
74+
fileExistsMode: "Append duplicate suffix",
75+
},
76+
},
77+
],
78+
},
79+
},
80+
],
81+
macros: [],
82+
},
83+
} as any;
84+
85+
await migration.migrate(plugin);
86+
87+
expect(
88+
plugin.settings.choices[0].macro.commands[0].choice,
89+
).toMatchObject({
90+
fileExistsBehavior: { kind: "apply", mode: "duplicateSuffix" },
91+
});
92+
expect(
93+
plugin.settings.choices[0].macro.commands[0].choice.fileExistsMode,
94+
).toBeUndefined();
95+
expect(
96+
plugin.settings.choices[0].macro.commands[0].choice.setFileExistsBehavior,
97+
).toBeUndefined();
98+
});
99+
100+
it("normalizes nested macro command template choices in legacy macros", async () => {
55101
const plugin = {
56102
settings: {
57103
choices: [],
@@ -82,5 +128,97 @@ describe("consolidateFileExistsBehavior migration", () => {
82128
expect(plugin.settings.macros[0].commands[0].choice).toMatchObject({
83129
fileExistsBehavior: { kind: "prompt" },
84130
});
131+
expect(
132+
plugin.settings.macros[0].commands[0].choice.fileExistsMode,
133+
).toBeUndefined();
134+
expect(
135+
plugin.settings.macros[0].commands[0].choice.setFileExistsBehavior,
136+
).toBeUndefined();
137+
});
138+
139+
it("normalizes template choices nested in conditional macro branches", async () => {
140+
const plugin = {
141+
settings: {
142+
choices: [],
143+
macros: [
144+
{
145+
id: "macro-1",
146+
name: "Macro",
147+
commands: [
148+
{
149+
type: CommandType.Conditional,
150+
thenCommands: [
151+
{
152+
type: CommandType.NestedChoice,
153+
choice: {
154+
id: "template-then",
155+
name: "Then Template",
156+
type: "Template",
157+
setFileExistsBehavior: true,
158+
fileExistsMode: "Append duplicate suffix",
159+
},
160+
},
161+
],
162+
elseCommands: [
163+
{
164+
type: CommandType.NestedChoice,
165+
choice: {
166+
id: "template-else",
167+
name: "Else Template",
168+
type: "Template",
169+
setFileExistsBehavior: false,
170+
fileExistsMode: "Overwrite the file",
171+
},
172+
},
173+
],
174+
},
175+
],
176+
},
177+
],
178+
},
179+
} as any;
180+
181+
await migration.migrate(plugin);
182+
183+
expect(
184+
plugin.settings.macros[0].commands[0].thenCommands[0].choice,
185+
).toMatchObject({
186+
fileExistsBehavior: { kind: "apply", mode: "duplicateSuffix" },
187+
});
188+
expect(
189+
plugin.settings.macros[0].commands[0].thenCommands[0].choice
190+
.fileExistsMode,
191+
).toBeUndefined();
192+
expect(
193+
plugin.settings.macros[0].commands[0].thenCommands[0].choice
194+
.setFileExistsBehavior,
195+
).toBeUndefined();
196+
expect(
197+
plugin.settings.macros[0].commands[0].elseCommands[0].choice,
198+
).toMatchObject({
199+
fileExistsBehavior: { kind: "prompt" },
200+
});
201+
expect(
202+
plugin.settings.macros[0].commands[0].elseCommands[0].choice
203+
.fileExistsMode,
204+
).toBeUndefined();
205+
expect(
206+
plugin.settings.macros[0].commands[0].elseCommands[0].choice
207+
.setFileExistsBehavior,
208+
).toBeUndefined();
209+
});
210+
211+
it("treats malformed persisted choice and macro collections as empty arrays", async () => {
212+
const plugin = {
213+
settings: {
214+
choices: { invalid: true },
215+
macros: "invalid",
216+
},
217+
} as any;
218+
219+
await migration.migrate(plugin);
220+
221+
expect(plugin.settings.choices).toEqual([]);
222+
expect(plugin.settings.macros).toEqual([]);
85223
});
86224
});

src/migrations/consolidateFileExistsBehavior.ts

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,32 @@
11
import type QuickAdd from "src/main";
2-
import type IChoice from "src/types/choices/IChoice";
3-
import type { IMacro } from "src/types/macros/IMacro";
42
import { deepClone } from "src/utils/deepClone";
53
import {
64
isTemplateChoice,
75
normalizeTemplateChoice,
86
} from "./helpers/normalizeTemplateFileExistsBehavior";
9-
import { isMultiChoice } from "./helpers/isMultiChoice";
10-
import { isNestedChoiceCommand } from "./helpers/isNestedChoiceCommand";
7+
import { walkAllChoices } from "./helpers/choice-traversal";
118
import type { Migration } from "./Migrations";
129

13-
function normalizeChoices(choices: IChoice[]): IChoice[] {
14-
for (const choice of choices) {
15-
if (isMultiChoice(choice)) {
16-
choice.choices = normalizeChoices(choice.choices);
17-
}
18-
19-
if (isTemplateChoice(choice)) {
20-
normalizeTemplateChoice(choice);
21-
}
22-
}
23-
24-
return choices;
25-
}
26-
27-
function normalizeMacros(macros: IMacro[]): IMacro[] {
28-
for (const macro of macros) {
29-
if (!Array.isArray(macro.commands)) continue;
30-
31-
for (const command of macro.commands) {
32-
if (isNestedChoiceCommand(command) && isTemplateChoice(command.choice)) {
33-
normalizeTemplateChoice(command.choice);
34-
}
35-
}
36-
}
37-
38-
return macros;
39-
}
40-
4110
const consolidateFileExistsBehavior: Migration = {
4211
description:
4312
"Re-run template file collision normalization for users with older migration state",
4413

4514
migrate: async (plugin: QuickAdd): Promise<void> => {
46-
const choicesCopy = deepClone(plugin.settings.choices);
47-
const macrosCopy = deepClone((plugin.settings as any).macros || []);
48-
49-
plugin.settings.choices = deepClone(normalizeChoices(choicesCopy));
50-
(plugin.settings as any).macros = normalizeMacros(macrosCopy);
15+
const choices = Array.isArray(plugin.settings.choices)
16+
? plugin.settings.choices
17+
: [];
18+
const macros = Array.isArray((plugin.settings as any).macros)
19+
? (plugin.settings as any).macros
20+
: [];
21+
22+
plugin.settings.choices = deepClone(choices);
23+
(plugin.settings as any).macros = deepClone(macros);
24+
25+
walkAllChoices(plugin, (choice) => {
26+
if (isTemplateChoice(choice)) {
27+
normalizeTemplateChoice(choice);
28+
}
29+
});
5130
},
5231
};
5332

0 commit comments

Comments
 (0)