Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 139 additions & 1 deletion src/migrations/consolidateFileExistsBehavior.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CommandType } from "../types/macros/CommandType";
import { describe, expect, it } from "vitest";
import migration from "./consolidateFileExistsBehavior";

Expand Down Expand Up @@ -51,7 +52,52 @@ describe("consolidateFileExistsBehavior migration", () => {
});
});

it("normalizes nested macro command template choices", async () => {
it("normalizes template choices nested inside Macro choice commands", async () => {
const plugin = {
settings: {
choices: [
{
id: "macro-choice",
name: "Macro Choice",
type: "Macro",
macro: {
id: "macro-1",
name: "Macro",
commands: [
{
type: CommandType.NestedChoice,
choice: {
id: "template-choice",
name: "Template",
type: "Template",
setFileExistsBehavior: true,
fileExistsMode: "Append duplicate suffix",
},
},
],
},
},
],
macros: [],
},
} as any;

await migration.migrate(plugin);

expect(
plugin.settings.choices[0].macro.commands[0].choice,
).toMatchObject({
fileExistsBehavior: { kind: "apply", mode: "duplicateSuffix" },
});
expect(
plugin.settings.choices[0].macro.commands[0].choice.fileExistsMode,
).toBeUndefined();
expect(
plugin.settings.choices[0].macro.commands[0].choice.setFileExistsBehavior,
).toBeUndefined();
});

it("normalizes nested macro command template choices in legacy macros", async () => {
const plugin = {
settings: {
choices: [],
Expand Down Expand Up @@ -82,5 +128,97 @@ describe("consolidateFileExistsBehavior migration", () => {
expect(plugin.settings.macros[0].commands[0].choice).toMatchObject({
fileExistsBehavior: { kind: "prompt" },
});
expect(
plugin.settings.macros[0].commands[0].choice.fileExistsMode,
).toBeUndefined();
expect(
plugin.settings.macros[0].commands[0].choice.setFileExistsBehavior,
).toBeUndefined();
});

it("normalizes template choices nested in conditional macro branches", async () => {
const plugin = {
settings: {
choices: [],
macros: [
{
id: "macro-1",
name: "Macro",
commands: [
{
type: CommandType.Conditional,
thenCommands: [
{
type: CommandType.NestedChoice,
choice: {
id: "template-then",
name: "Then Template",
type: "Template",
setFileExistsBehavior: true,
fileExistsMode: "Append duplicate suffix",
},
},
],
elseCommands: [
{
type: CommandType.NestedChoice,
choice: {
id: "template-else",
name: "Else Template",
type: "Template",
setFileExistsBehavior: false,
fileExistsMode: "Overwrite the file",
},
},
],
},
],
},
],
},
} as any;

await migration.migrate(plugin);

expect(
plugin.settings.macros[0].commands[0].thenCommands[0].choice,
).toMatchObject({
fileExistsBehavior: { kind: "apply", mode: "duplicateSuffix" },
});
expect(
plugin.settings.macros[0].commands[0].thenCommands[0].choice
.fileExistsMode,
).toBeUndefined();
expect(
plugin.settings.macros[0].commands[0].thenCommands[0].choice
.setFileExistsBehavior,
).toBeUndefined();
expect(
plugin.settings.macros[0].commands[0].elseCommands[0].choice,
).toMatchObject({
fileExistsBehavior: { kind: "prompt" },
});
expect(
plugin.settings.macros[0].commands[0].elseCommands[0].choice
.fileExistsMode,
).toBeUndefined();
expect(
plugin.settings.macros[0].commands[0].elseCommands[0].choice
.setFileExistsBehavior,
).toBeUndefined();
});

it("treats malformed persisted choice and macro collections as empty arrays", async () => {
const plugin = {
settings: {
choices: { invalid: true },
macros: "invalid",
},
} as any;

await migration.migrate(plugin);

expect(plugin.settings.choices).toEqual([]);
expect(plugin.settings.macros).toEqual([]);
});
});
53 changes: 16 additions & 37 deletions src/migrations/consolidateFileExistsBehavior.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,32 @@
import type QuickAdd from "src/main";
import type IChoice from "src/types/choices/IChoice";
import type { IMacro } from "src/types/macros/IMacro";
import { deepClone } from "src/utils/deepClone";
import {
isTemplateChoice,
normalizeTemplateChoice,
} from "./helpers/normalizeTemplateFileExistsBehavior";
import { isMultiChoice } from "./helpers/isMultiChoice";
import { isNestedChoiceCommand } from "./helpers/isNestedChoiceCommand";
import { walkAllChoices } from "./helpers/choice-traversal";
import type { Migration } from "./Migrations";

function normalizeChoices(choices: IChoice[]): IChoice[] {
for (const choice of choices) {
if (isMultiChoice(choice)) {
choice.choices = normalizeChoices(choice.choices);
}

if (isTemplateChoice(choice)) {
normalizeTemplateChoice(choice);
}
}

return choices;
}

function normalizeMacros(macros: IMacro[]): IMacro[] {
for (const macro of macros) {
if (!Array.isArray(macro.commands)) continue;

for (const command of macro.commands) {
if (isNestedChoiceCommand(command) && isTemplateChoice(command.choice)) {
normalizeTemplateChoice(command.choice);
}
}
}

return macros;
}

const consolidateFileExistsBehavior: Migration = {
description:
"Re-run template file collision normalization for users with older migration state",

migrate: async (plugin: QuickAdd): Promise<void> => {
const choicesCopy = deepClone(plugin.settings.choices);
const macrosCopy = deepClone((plugin.settings as any).macros || []);

plugin.settings.choices = deepClone(normalizeChoices(choicesCopy));
(plugin.settings as any).macros = normalizeMacros(macrosCopy);
const choices = Array.isArray(plugin.settings.choices)
? plugin.settings.choices
: [];
const macros = Array.isArray((plugin.settings as any).macros)
? (plugin.settings as any).macros
: [];

plugin.settings.choices = deepClone(choices);
(plugin.settings as any).macros = deepClone(macros);

walkAllChoices(plugin, (choice) => {
if (isTemplateChoice(choice)) {
normalizeTemplateChoice(choice);
}
});
},
};

Expand Down
Loading