diff --git a/tests/e2e-vault-config.test.ts b/tests/e2e-vault-config.test.ts new file mode 100644 index 000000000..b50349952 --- /dev/null +++ b/tests/e2e-vault-config.test.ts @@ -0,0 +1,20 @@ +import { readFileSync, readdirSync } from "node:fs"; +import { join, relative, resolve } from "node:path"; +import { expect, it } from "vitest"; + +const ROOT = resolve(__dirname, ".."); +const E2E = join(ROOT, "tests/e2e"); + +it("sets vault options in e2e specs without writing app.json", () => { + const hits = readdirSync(E2E, { recursive: true, encoding: "utf8" }) + .filter((file) => file.endsWith(".ts")) + .flatMap((file) => + readFileSync(join(E2E, file), "utf8") + .split("\n") + .flatMap((line, i) => (/\b(setConfig|saveConfig|requestSaveConfig)\s*\(/.test(line) ? [`${relative(ROOT, join(E2E, file))}:${i + 1}`] : [])), + ); + expect( + hits, + "Use setVaultConfig from tests/e2e/uiHelpers.ts: setConfig and saveConfig write app.json, which Obsidian can read back under load after the spec restores the option, leaking it into every later spec (#2207).", + ).toEqual([]); +}); diff --git a/tests/e2e/conditional-branch-persistence.test.ts b/tests/e2e/conditional-branch-persistence.test.ts index 32e8681c9..ecd6bf298 100644 --- a/tests/e2e/conditional-branch-persistence.test.ts +++ b/tests/e2e/conditional-branch-persistence.test.ts @@ -7,6 +7,7 @@ import { acquireQuickAddVaultRunLock, createQuickAddObsidianClient, } from "./e2eVault"; +import { setVaultConfig } from "./uiHelpers"; const PLUGIN_ID = "quickadd"; const CHOICE_ID = "qa-e2e-cond-branch"; @@ -77,7 +78,7 @@ beforeAll(async () => { // the helpers above stay window-agnostic, so if a runner-side fix ever // makes popouts reliable, deleting this line is the whole migration. await obsidian.dev.eval( - `app.vault.setConfig('settingsPopoutWindow', false)`, + setVaultConfig("settingsPopoutWindow", false), ); qa = obsidian.plugin(PLUGIN_ID); await qa.reload({ waitUntilReady: true }); diff --git a/tests/e2e/daily-note-token.test.ts b/tests/e2e/daily-note-token.test.ts index f006ec53a..452f68520 100644 --- a/tests/e2e/daily-note-token.test.ts +++ b/tests/e2e/daily-note-token.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { CaptureChoice } from "../../src/types/choices/CaptureChoice"; import type IChoice from "../../src/types/choices/IChoice"; import { createQuickAddE2EHarness, seedVaultFile } from "./e2eVault"; +import { setVaultConfig } from "./uiHelpers"; const getContext = createQuickAddE2EHarness("daily-note-token"); @@ -233,7 +234,7 @@ describe("{{DAILY}} in native Obsidian", () => { choice.format = { enabled: true, format: "{{DAILY|link}}" }; await saveChoice(choice); const useMarkdownLinks = await obsidian.dev.evalJson("app.vault.getConfig('useMarkdownLinks')"); - await obsidian.dev.evalJson("(() => { app.vault.setConfig('useMarkdownLinks', true); return true; })()"); + await obsidian.dev.evalJson(`${setVaultConfig("useMarkdownLinks", true)}, true`); try { await obsidian.execJson("quickadd:run", { id: choice.id, verify: true, date: "2031-02-18" }); await expect.poll(() => read(inbox), { timeout: 10000, interval: 100 }) @@ -249,8 +250,7 @@ describe("{{DAILY}} in native Obsidian", () => { })()`); expect(opened).toBe(`${folder}/2031-02-18.md`); } finally { - // Saved now: the debounced save of `true` would otherwise reach disk after the restore. - await obsidian.dev.evalJsonAsync(`(async () => { app.vault.setConfig('useMarkdownLinks', ${useMarkdownLinks === true}); await app.vault.saveConfig(); return true; })()`); + await obsidian.dev.evalJson(`${setVaultConfig("useMarkdownLinks", useMarkdownLinks === true)}, true`); } }); diff --git a/tests/e2e/drag-pill-bounds.test.ts b/tests/e2e/drag-pill-bounds.test.ts index 8a7173a29..fbbeab40a 100644 --- a/tests/e2e/drag-pill-bounds.test.ts +++ b/tests/e2e/drag-pill-bounds.test.ts @@ -2,7 +2,7 @@ import { expect, it } from "vitest"; import { CaptureChoice } from "../../src/types/choices/CaptureChoice"; import type IChoice from "../../src/types/choices/IChoice"; import { createQuickAddE2EHarness } from "./e2eVault"; -import { waitForElement } from "./uiHelpers"; +import { setVaultConfig, waitForElement } from "./uiHelpers"; const getContext = createQuickAddE2EHarness("drag-pill-bounds"); @@ -26,7 +26,7 @@ it("keeps the drag pill inside a narrow window with the grip under the cursor", // Narrow enough that a pill growing right from the handle runs off the window. await obsidian.dev.evalJson(`require("@electron/remote").getCurrentWindow().setSize(900, 700), true`); await expect.poll(() => obsidian.dev.evalJson("innerWidth")).toBe(900); - await obsidian.dev.evalJson("app.vault.setConfig('settingsPopoutWindow', false), app.setting.open(), app.setting.openTabById('quickadd'), true"); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", false)}, app.setting.open(), app.setting.openTabById('quickadd'), true`); const handleSelector = `[aria-label=${JSON.stringify(`Reorder ${LONG_NAME}`)}]`; await waitForElement(obsidian, handleSelector); const [x, y] = await obsidian.dev.evalJson(`(() => { @@ -53,7 +53,7 @@ it("keeps the drag pill inside a narrow window with the grip under the cursor", if (pressedAt) await mouse("mouseReleased", pressedAt[0], pressedAt[1], 1); await obsidian.dev.evalJson(`(() => { app.setting.close(); - app.vault.setConfig('settingsPopoutWindow', ${JSON.stringify(popout)}); + ${setVaultConfig("settingsPopoutWindow", popout)}; require("@electron/remote").getCurrentWindow().setSize(${originalSize[0]}, ${originalSize[1]}); return true; })()`); diff --git a/tests/e2e/package-import-destination.test.ts b/tests/e2e/package-import-destination.test.ts index 9d2024c11..391f1c761 100644 --- a/tests/e2e/package-import-destination.test.ts +++ b/tests/e2e/package-import-destination.test.ts @@ -2,7 +2,7 @@ import { expect, it } from "vitest"; import type { QuickAddPackage } from "../../src/types/packages/QuickAddPackage"; import { encodeToBase64 } from "../../src/utils/base64"; import { createQuickAddE2EHarness, seedVaultFile } from "./e2eVault"; -import { POLL_OPTS, jsLiteral, pressKey, typeInto, waitForElement } from "./uiHelpers"; +import { POLL_OPTS, jsLiteral, pressKey, setVaultConfig, typeInto, waitForElement } from "./uiHelpers"; // #1865: typing a file's Destination past a path that names a folder regrouped // the row, which re-created it and dropped focus, and a folder destination @@ -32,7 +32,7 @@ it("keeps focus while you type a destination, and blocks a folder destination", }; const popout = await obsidian.dev.evalJson("app.vault.getConfig('settingsPopoutWindow') ?? true"); - await obsidian.dev.evalJson("app.vault.setConfig('settingsPopoutWindow', false), true"); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", false)}, true`); const state = () => obsidian.dev.evalJson<{ focused: boolean; value: string; folder: boolean; importDisabled: boolean }>(`(() => { const input = document.querySelector(${jsLiteral(DESTINATION)}); return { @@ -67,6 +67,6 @@ it("keeps focus while you type a destination, and blocks a folder destination", } finally { await pressKey(obsidian, "Escape"); await obsidian.dev.evalJson("app.setting.close(); true"); - await obsidian.dev.evalJson(`app.vault.setConfig('settingsPopoutWindow', ${jsLiteral(popout)}), true`); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", popout)}, true`); } }); diff --git a/tests/e2e/package-import-existing.test.ts b/tests/e2e/package-import-existing.test.ts index abf3728a1..dee08fd15 100644 --- a/tests/e2e/package-import-existing.test.ts +++ b/tests/e2e/package-import-existing.test.ts @@ -1,7 +1,7 @@ import { expect, it } from "vitest"; import type IChoice from "../../src/types/choices/IChoice"; import { createQuickAddE2EHarness, seedVaultFile } from "./e2eVault"; -import { jsLiteral, POLL_OPTS, pressKey, typeInto } from "./uiHelpers"; +import { jsLiteral, POLL_OPTS, pressKey, setVaultConfig, typeInto } from "./uiHelpers"; // "Import" adds a choice and never replaces one: the CLI refuses it for a // choice already in the vault, and the modal only offers it for new choices. @@ -47,7 +47,7 @@ it("offers Overwrite, not Import, for a choice already in the vault", async () = await obsidian.execJson("quickadd:package-import", { path: mine }); const popout = await obsidian.dev.evalJson("app.vault.getConfig('settingsPopoutWindow') ?? true"); - await obsidian.dev.evalJson("app.vault.setConfig('settingsPopoutWindow', false), true"); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", false)}, true`); try { await obsidian.dev.evalJson(`(() => { app.setting.open(); app.setting.openTabById("quickadd"); return true; })()`); await expect.poll(() => obsidian.dev.evalJson(`(() => { @@ -65,6 +65,6 @@ it("offers Overwrite, not Import, for a choice already in the vault", async () = } finally { await pressKey(obsidian, "Escape"); await obsidian.dev.evalJson("app.setting.close(); true"); - await obsidian.dev.evalJson(`app.vault.setConfig('settingsPopoutWindow', ${jsLiteral(popout)}), true`); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", popout)}, true`); } }); diff --git a/tests/e2e/package-import-inline-code.test.ts b/tests/e2e/package-import-inline-code.test.ts index e9b1c33e1..9e39686be 100644 --- a/tests/e2e/package-import-inline-code.test.ts +++ b/tests/e2e/package-import-inline-code.test.ts @@ -1,7 +1,7 @@ import { expect, it } from "vitest"; import type IChoice from "../../src/types/choices/IChoice"; import { createQuickAddE2EHarness } from "./e2eVault"; -import { clickAt, POLL_OPTS, pressKey, typeInto, waitForElement } from "./uiHelpers"; +import { clickAt, POLL_OPTS, pressKey, setVaultConfig, typeInto, waitForElement } from "./uiHelpers"; // #1912: the import review shows JavaScript written into a choice setting, counts // viewing it like opening a bundled script, and says beside Import what is left. @@ -67,7 +67,7 @@ it("shows a choice's inline code and says beside Import what is left", async () }; const popout = await obsidian.dev.evalJson("app.vault.getConfig('settingsPopoutWindow') ?? true"); - await obsidian.dev.evalJson("app.vault.setConfig('settingsPopoutWindow', false), true"); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", false)}, true`); try { await obsidian.dev.evalJson(`(() => { app.setting.open(); app.setting.openTabById("quickadd"); return true; })()`); await expect.poll(() => obsidian.dev.evalJson(`(() => { @@ -116,6 +116,6 @@ it("shows a choice's inline code and says beside Import what is left", async () } finally { await pressKey(obsidian, "Escape"); await obsidian.dev.evalJson("app.setting.close(); true"); - await obsidian.dev.evalJson(`app.vault.setConfig('settingsPopoutWindow', ${JSON.stringify(popout)}), true`); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", popout)}, true`); } }); diff --git a/tests/e2e/package-import-result.test.ts b/tests/e2e/package-import-result.test.ts index af43e9f82..e009e4105 100644 --- a/tests/e2e/package-import-result.test.ts +++ b/tests/e2e/package-import-result.test.ts @@ -3,7 +3,7 @@ import type IChoice from "../../src/types/choices/IChoice"; import type { QuickAddPackage } from "../../src/types/packages/QuickAddPackage"; import { encodeToBase64 } from "../../src/utils/base64"; import { createQuickAddE2EHarness } from "./e2eVault"; -import { POLL_OPTS, pressKey, typeInto, waitForElement } from "./uiHelpers"; +import { POLL_OPTS, pressKey, setVaultConfig, typeInto, waitForElement } from "./uiHelpers"; // #1880: after Import package, the result callout sat half hidden behind the // footer, and Cancel stayed next to Close. @@ -31,7 +31,7 @@ it("shows the import result in view, with a single Close", async () => { }; const popout = await obsidian.dev.evalJson("app.vault.getConfig('settingsPopoutWindow') ?? true"); - await obsidian.dev.evalJson("app.vault.setConfig('settingsPopoutWindow', false), true"); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", false)}, true`); try { await obsidian.dev.evalJson(`(() => { app.setting.open(); app.setting.openTabById("quickadd"); return true; })()`); await expect.poll(() => obsidian.dev.evalJson(`(() => { @@ -64,6 +64,6 @@ it("shows the import result in view, with a single Close", async () => { } finally { await pressKey(obsidian, "Escape"); await obsidian.dev.evalJson("app.setting.close(); true"); - await obsidian.dev.evalJson(`app.vault.setConfig('settingsPopoutWindow', ${JSON.stringify(popout)}), true`); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", popout)}, true`); } }); diff --git a/tests/e2e/prompt-cancellation.test.ts b/tests/e2e/prompt-cancellation.test.ts index 72c3b7a0a..999713046 100644 --- a/tests/e2e/prompt-cancellation.test.ts +++ b/tests/e2e/prompt-cancellation.test.ts @@ -9,7 +9,7 @@ import { ChoiceCommand } from "../../src/types/macros/ChoiceCommand"; import { ConditionalCommand } from "../../src/types/macros/Conditional/ConditionalCommand"; import { UserScript } from "../../src/types/macros/UserScript"; import { createQuickAddE2EHarness, seedVaultFile } from "./e2eVault"; -import { insertText, jsLiteral, POLL_OPTS, pressKey } from "./uiHelpers"; +import { insertText, jsLiteral, POLL_OPTS, pressKey, setVaultConfig } from "./uiHelpers"; // Pressing Escape in a prompt is a normal way to stop a run. It must not land in // Obsidian's `dev:errors`, which records every unhandled promise rejection (even one @@ -246,7 +246,7 @@ it("Escape in the macro builder's script pickers leaves dev:errors empty", async // Settings in the main window, as in conditional-branch-persistence.test.ts: // the popout's DOM adoption is nondeterministic under CLI automation. const popout = await obsidian.dev.evalJson("app.vault.getConfig('settingsPopoutWindow') ?? true"); - await obsidian.dev.evalJson("app.vault.setConfig('settingsPopoutWindow', false), true"); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", false)}, true`); const click = (selector: string, text?: string) => obsidian.dev.evalJson(`(() => { const all = Array.from(document.querySelectorAll(${jsLiteral(selector)})) .filter((el) => ${jsLiteral(text ?? null)} === null || el.textContent.trim() === ${jsLiteral(text ?? null)}); @@ -286,6 +286,6 @@ it("Escape in the macro builder's script pickers leaves dev:errors empty", async expect(await devErrors(obsidian)).toBe(NO_ERRORS); } finally { await escapeUntilClosed(obsidian); - await obsidian.dev.evalJson(`(() => { app.setting.close(); app.vault.setConfig('settingsPopoutWindow', ${popout}); return true; })()`); + await obsidian.dev.evalJson(`(() => { app.setting.close(); ${setVaultConfig("settingsPopoutWindow", popout)}; return true; })()`); } }); diff --git a/tests/e2e/script-picker-paths.test.ts b/tests/e2e/script-picker-paths.test.ts index 017037170..e50c54a1a 100644 --- a/tests/e2e/script-picker-paths.test.ts +++ b/tests/e2e/script-picker-paths.test.ts @@ -2,7 +2,7 @@ import { expect, it } from "vitest"; import { MacroChoice } from "../../src/types/choices/MacroChoice"; import type IChoice from "../../src/types/choices/IChoice"; import { createQuickAddE2EHarness, seedVaultFile } from "./e2eVault"; -import { leaveSettingsPage, POLL_OPTS, pressKey, typeInto } from "./uiHelpers"; +import { leaveSettingsPage, POLL_OPTS, pressKey, setVaultConfig, typeInto } from "./uiHelpers"; // #941/#942: two `view.js` files in different folders were identical "view" rows // in both script pickers, and picking the second one from the inline typeahead @@ -28,7 +28,7 @@ it("adds same-named scripts by path from the typeahead and Browse, and runs each // Settings in the main window, as in conditional-branch-persistence.test.ts: // the popout's DOM adoption is nondeterministic under CLI automation. const popout = await obsidian.dev.evalJson("app.vault.getConfig('settingsPopoutWindow') ?? true"); - await obsidian.dev.evalJson("app.vault.setConfig('settingsPopoutWindow', false), true"); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", false)}, true`); const click = (selector: string, text?: string) => obsidian.dev.evalJson(`(() => { const target = Array.from(document.querySelectorAll(${JSON.stringify(selector)})) .filter((el) => ${JSON.stringify(text ?? null)} === null || el.textContent.trim() === ${JSON.stringify(text ?? null)}) @@ -105,7 +105,7 @@ it("adds same-named scripts by path from the typeahead and Browse, and runs each return data.choices[0].macro.commands.map((c) => c.path); })()`), POLL_OPTS).toEqual([progress, books]); } finally { - await obsidian.dev.evalJson(`(() => { app.setting.close(); app.vault.setConfig('settingsPopoutWindow', ${popout}); return true; })()`); + await obsidian.dev.evalJson(`(() => { app.setting.close(); ${setVaultConfig("settingsPopoutWindow", popout)}; return true; })()`); } await obsidian.dev.evalJson("(() => { window.__qaScriptPickerRuns = []; return true; })()"); diff --git a/tests/e2e/script-syntax-error.test.ts b/tests/e2e/script-syntax-error.test.ts index f11011f0d..db1d0016e 100644 --- a/tests/e2e/script-syntax-error.test.ts +++ b/tests/e2e/script-syntax-error.test.ts @@ -3,7 +3,7 @@ import type IChoice from "../../src/types/choices/IChoice"; import { MacroChoice } from "../../src/types/choices/MacroChoice"; import { UserScript } from "../../src/types/macros/UserScript"; import { createQuickAddE2EHarness, seedVaultFile } from "./e2eVault"; -import { jsLiteral, leaveSettingsPage, POLL_OPTS } from "./uiHelpers"; +import { jsLiteral, leaveSettingsPage, POLL_OPTS, setVaultConfig } from "./uiHelpers"; const getContext = createQuickAddE2EHarness("script-syntax-error"); @@ -39,7 +39,7 @@ it("names the file and line of a user script syntax error on run, in the CLI, an await obsidian.dev.evalJson(clearNotices); const popout = await obsidian.dev.evalJson("app.vault.getConfig('settingsPopoutWindow') ?? true"); - await obsidian.dev.evalJson("app.vault.setConfig('settingsPopoutWindow', false), true"); + await obsidian.dev.evalJson(`${setVaultConfig("settingsPopoutWindow", false)}, true`); try { await obsidian.dev.evalJson(`(() => { app.setting.open(); app.setting.openTabById("quickadd"); return true; })()`); await expect.poll(() => obsidian.dev.evalJson(`(() => { @@ -53,6 +53,6 @@ it("names the file and line of a user script syntax error on run, in the CLI, an ]); await leaveSettingsPage(obsidian); } finally { - await obsidian.dev.evalJson(`(() => { app.setting.close(); app.vault.setConfig('settingsPopoutWindow', ${popout}); return true; })()`); + await obsidian.dev.evalJson(`(() => { app.setting.close(); ${setVaultConfig("settingsPopoutWindow", popout)}; return true; })()`); } }); diff --git a/tests/e2e/settings-popout-save.test.ts b/tests/e2e/settings-popout-save.test.ts index a92c8be00..0ffcf7d6e 100644 --- a/tests/e2e/settings-popout-save.test.ts +++ b/tests/e2e/settings-popout-save.test.ts @@ -4,6 +4,7 @@ import { expect, it } from "vitest"; import type IChoice from "../../src/types/choices/IChoice"; import { TemplateChoice } from "../../src/types/choices/TemplateChoice"; import { createQuickAddE2EHarness } from "./e2eVault"; +import { setVaultConfig } from "./uiHelpers"; const getContext = createQuickAddE2EHarness("settings-popout-save"); @@ -25,7 +26,7 @@ for (const mainWindow of ["visible", "hidden"] as const) { const inMemory = await obsidian.dev.evalJsonAsync(`(async () => { const remote = require("@electron/remote"); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - app.vault.setConfig("settingsPopoutWindow", true); + ${setVaultConfig("settingsPopoutWindow", true)}; app.setting.open(); app.setting.openTabById("quickadd"); let doc; @@ -76,7 +77,7 @@ for (const mainWindow of ["visible", "hidden"] as const) { await obsidian.dev.evalJson(`(() => { require("@electron/remote").getCurrentWindow().show(); app.setting.close(); - app.vault.setConfig("settingsPopoutWindow", ${JSON.stringify(popout)}); + ${setVaultConfig("settingsPopoutWindow", popout)}; return true; })()`); } diff --git a/tests/e2e/template-cursor.test.ts b/tests/e2e/template-cursor.test.ts index 52ffe5bb4..ca4942496 100644 --- a/tests/e2e/template-cursor.test.ts +++ b/tests/e2e/template-cursor.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { TemplateChoice } from "../../src/types/choices/TemplateChoice"; import type IChoice from "../../src/types/choices/IChoice"; import { createQuickAddE2EHarness, seedVaultFile } from "./e2eVault"; -import { insertText, POLL_OPTS, pressKey, typeInto, waitForElement, expectNoPrompt } from "./uiHelpers"; +import { insertText, POLL_OPTS, pressKey, setVaultConfig, typeInto, waitForElement, expectNoPrompt } from "./uiHelpers"; const getContext = createQuickAddE2EHarness("template-cursor"); @@ -375,7 +375,7 @@ describe("Template cursor markers in native Obsidian", () => { const previousUpdateLinks = await obsidian.dev.evalJson('app.vault.getConfig("alwaysUpdateLinks")'); try { await obsidian.dev.evalJsonAsync(`(async () => { - app.vault.setConfig("alwaysUpdateLinks", true); + ${setVaultConfig("alwaysUpdateLinks", true)}; const leaf = app.workspace.activeLeaf; await leaf.setViewState({ type: "markdown", state: { file: ${JSON.stringify(path)}, mode: "source", source: false } }); leaf.view.editor.focus(); @@ -408,7 +408,7 @@ describe("Template cursor markers in native Obsidian", () => { expect(await obsidian.dev.evalJson("app.workspace.activeLeaf.view.getState().source")).toBe(false); } finally { await obsidian.dev.evalJson(`(() => { - app.vault.setConfig("alwaysUpdateLinks", ${JSON.stringify(previousUpdateLinks)}); + ${setVaultConfig("alwaysUpdateLinks", previousUpdateLinks)}; for (const close of document.querySelectorAll(".modal-close-button")) close.click(); return true; })()`); diff --git a/tests/e2e/uiHelpers.ts b/tests/e2e/uiHelpers.ts index b0fe19995..3403ca262 100644 --- a/tests/e2e/uiHelpers.ts +++ b/tests/e2e/uiHelpers.ts @@ -23,6 +23,15 @@ export function jsLiteral(value: unknown): string { return JSON.stringify(value).replace(/[<>\b\f\n\r\t\0\u2028\u2029]/g, (char) => UNSAFE_IN_CODE[char]); } +/** + * Page code that sets vault option `key` in memory only. `app.vault.setConfig` + * also writes app.json, and under load Obsidian reads that write back after a + * spec restores the option, leaving it changed for every later spec (#2207). + */ +export function setVaultConfig(key: string, value: unknown): string { + return `void (app.vault.config[${jsLiteral(key)}] = ${jsLiteral(value)})`; +} + export async function waitForElement(obsidian: ObsidianClient, selector: string) { await expect.poll(() => obsidian.dev.evalJson( `Boolean(document.querySelector(${JSON.stringify(selector)})?.getClientRects().length)`,