Clarify Hidden File Sync selectors and test normal allow-lists

This commit is contained in:
vorotamoroz committed 2026-10-23 01:11:41 +00:00
1 parent ce0524d49f
commit 9d30bed52d
11 files changed
+432 -6

No files matched your search

@@ -7,6 +7,10 @@
* remove it from this map in the same change.
*/
export const liveSyncProvisionalEnglishMessages = {
"(RegExp) Limit normal files to matching paths. Leave empty to allow all normal file paths. Hidden File Sync uses its own target patterns.":
"(RegExp) Limit normal files to matching paths. Leave empty to allow all normal file paths. Hidden File Sync uses its own target patterns.",
"(RegExp) Skip changes to matching normal and hidden files, locally and remotely.":
"(RegExp) Skip changes to matching normal and hidden files, locally and remotely.",
"Preparing follow-up report…": "Preparing follow-up report…",
"Cancelling local search. Checked ${COUNT} entries.": "Cancelling local search. Checked ${COUNT} entries.",
"Search current Vault paths and current local Metadata only. Older history and remote-only files are not searched.":
@@ -0,0 +1,127 @@
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("@/deps.ts", () => ({}));
vi.mock("@/features/HiddenFileCommon/JsonResolveModal.ts", () => ({ JsonResolveModal: class JsonResolveModal {} }));
vi.mock("@/modules/features/ModuleLog.ts", () => ({ MARK_DONE: "" }));
vi.mock("./configureHiddenFileSyncMode.ts", () => ({ configureHiddenFileSyncMode: vi.fn() }));
import type { FilePath } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { readContent } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { createOptionalFileSyncDatabase, optionalFileEntry } from "../../../test/fixtures/optionalFileSyncDatabase";
import { HiddenFileSync } from "./CmdHiddenFileSync";
const hiddenPath = ".obsidian/plugins/example/main.js" as FilePath;
const prefixedPath = `i:${hiddenPath}`;
const body = "module.exports = class ExamplePlugin {};";
const allowList = "\\.(?:md|png|pdf)$";
const fixtureOwners: Awaited<ReturnType<typeof createOptionalFileSyncDatabase>>[] = [];
async function hiddenFiles() {
const fixture = await createOptionalFileSyncDatabase();
fixtureOwners.push(fixture);
fixture.settings.usePluginSync = false;
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
Object.assign(hiddenFileSync, {
core: {
settings: fixture.settings,
services: {
...fixture.services,
vault: { isIgnoredByIgnoreFile: vi.fn(async () => false) },
},
},
cacheFileRegExps: new Map(),
cacheCustomisationSyncIgnoredFiles: new Map(),
});
return { ...fixture, hiddenFileSync };
}
afterEach(async () => {
vi.restoreAllMocks();
for (const fixture of fixtureOwners.splice(0)) await fixture.dispose();
});
describe("issue #1272: Hidden File Sync with normal-file selectors", () => {
it("stores a selected hidden file while the normal-file allow-list is set", async () => {
const fixture = await hiddenFiles();
fixture.settings.syncOnlyRegEx = allowList as typeof fixture.settings.syncOnlyRegEx;
await expect(fixture.hiddenFileSync.isTargetFile(hiddenPath)).resolves.toBe(true);
await expect(fixture.localDatabase.putDBEntry(optionalFileEntry(prefixedPath, body))).resolves.toMatchObject({
ok: true,
});
const stored = await fixture.localDatabase.getDBEntry(prefixedPath as FilePath);
expect(stored).not.toBe(false);
if (stored) expect(await readContent(stored)).toBe(body);
});
it("reads existing hidden-file content while the normal-file allow-list is set", async () => {
const fixture = await hiddenFiles();
await expect(fixture.localDatabase.putDBEntry(optionalFileEntry(prefixedPath, body))).resolves.toMatchObject({
ok: true,
});
const meta = await fixture.localDatabase.getDBEntryMeta(prefixedPath as FilePath);
if (!meta) throw new Error("The seeded hidden-file Metadata is unavailable");
fixture.settings.syncOnlyRegEx = allowList as typeof fixture.settings.syncOnlyRegEx;
await expect(fixture.hiddenFileSync.isTargetFile(hiddenPath)).resolves.toBe(true);
const entry = await fixture.localDatabase.getDBEntryFromMeta(meta);
expect(entry).not.toBe(false);
if (entry) expect(await readContent(entry)).toBe(body);
});
it("stores and reads the same hidden file when the normal-file allow-list is empty", async () => {
const fixture = await hiddenFiles();
await expect(fixture.localDatabase.putDBEntry(optionalFileEntry(prefixedPath, body))).resolves.toMatchObject({
ok: true,
});
const entry = await fixture.localDatabase.getDBEntry(prefixedPath as FilePath);
expect(entry).not.toBe(false);
if (entry) expect(await readContent(entry)).toBe(body);
});
it("continues to apply the normal-file allow-list to ordinary files", async () => {
const fixture = await hiddenFiles();
fixture.settings.syncOnlyRegEx = allowList as typeof fixture.settings.syncOnlyRegEx;
expect(fixture.localDatabase.isTargetFile("notes/example.md" as FilePath)).toBe(true);
expect(fixture.localDatabase.isTargetFile("notes/example.js" as FilePath)).toBe(false);
expect(fixture.localDatabase.isTargetFile("i:.obsidian/example.md" as FilePath)).toBe(true);
});
it("keeps Customisation Sync documents usable with the same normal-file allow-list", async () => {
const fixture = await hiddenFiles();
fixture.settings.syncOnlyRegEx = allowList as typeof fixture.settings.syncOnlyRegEx;
const path = "ix:device/PLUGIN_MAIN/example%main.js" as FilePath;
await expect(fixture.localDatabase.putDBEntry(optionalFileEntry(path, body))).resolves.toMatchObject({
ok: true,
});
const entry = await fixture.localDatabase.getDBEntry(path);
expect(entry).not.toBe(false);
if (entry) expect(await readContent(entry)).toBe(body);
});
it("keeps Hidden File Sync target and ignore patterns effective", async () => {
const fixture = await hiddenFiles();
fixture.settings.syncInternalFilesTargetPatterns =
"^\\.obsidian/plugins/" as typeof fixture.settings.syncInternalFilesTargetPatterns;
await expect(fixture.hiddenFileSync.isTargetFile(hiddenPath)).resolves.toBe(true);
await expect(fixture.hiddenFileSync.isTargetFile(".obsidian/snippets/example.css" as FilePath)).resolves.toBe(
false
);
fixture.settings.syncInternalFilesIgnorePatterns =
"main\\.js$" as typeof fixture.settings.syncInternalFilesIgnorePatterns;
await expect(fixture.hiddenFileSync.isTargetFile(hiddenPath)).resolves.toBe(false);
});
it("compatibility: applies the normal-file ignore-list to hidden documents", async () => {
const fixture = await hiddenFiles();
fixture.settings.syncOnlyRegEx = allowList as typeof fixture.settings.syncOnlyRegEx;
fixture.settings.syncIgnoreRegEx = "^\\.obsidian/" as typeof fixture.settings.syncIgnoreRegEx;
expect(fixture.localDatabase.isTargetFile(prefixedPath as FilePath)).toBe(false);
});
it("rejects hidden documents when Hidden File Sync is disabled", async () => {
const fixture = await hiddenFiles();
fixture.settings.syncInternalFiles = false;
expect(fixture.localDatabase.isTargetFile(prefixedPath as FilePath)).toBe(false);
});
});
@@ -13,7 +13,7 @@ export function paneSelector(this: ObsidianLiveSyncSettingTab, paneEl: HTMLEleme
.setName($msg("Synchronising files"))
.setDesc(
$msg(
"(RegExp) Empty to sync all files. Set filter as a regular expression to limit synchronising files."
"(RegExp) Limit normal files to matching paths. Leave empty to allow all normal file paths. Hidden File Sync uses its own target patterns."
)
);
const syncFilesControl = mount(MultipleRegExpControl, {
@@ -32,9 +32,7 @@ export function paneSelector(this: ObsidianLiveSyncSettingTab, paneEl: HTMLEleme
const nonSyncFilesSetting = new Setting(paneEl)
.setName($msg("Non-Synchronising files"))
.setDesc(
$msg("(RegExp) If this is set, any changes to local and remote files that match this will be skipped.")
);
.setDesc($msg("(RegExp) Skip changes to matching normal and hidden files, locally and remotely."));
const nonSyncFilesControl = mount(MultipleRegExpControl, {
target: nonSyncFilesSetting.controlEl,