mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-21 18:17:05 +00:00
Verify P2P pane in a fresh mobile session
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { mkdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
assertLocatorHasMinimumTouchTarget,
|
||||
assertLocatorWithinSafeArea,
|
||||
@@ -12,13 +14,20 @@ export const desktopViewport = { width: 1024, height: 768 } as const;
|
||||
export const iPhoneSafeArea = { top: 47, right: 0, bottom: 34, left: 0 } as const;
|
||||
|
||||
type ObsidianTestApp = {
|
||||
isMobile?: boolean;
|
||||
emulateMobile?: (mobile: boolean) => void;
|
||||
plugins?: { plugins: Record<string, unknown> };
|
||||
workspace?: { layoutReady?: boolean };
|
||||
};
|
||||
|
||||
type ObsidianTestGlobal = typeof globalThis & { app?: ObsidianTestApp };
|
||||
|
||||
export async function setObsidianMobileTestMode(port: number, enabled: boolean, timeoutMs: number): Promise<void> {
|
||||
async function applyObsidianMobileTestMode(
|
||||
port: number,
|
||||
enabled: boolean,
|
||||
timeoutMs: number,
|
||||
waitForLiveSync: boolean
|
||||
): Promise<void> {
|
||||
await withObsidianPage(port, async (page) => {
|
||||
await page.setViewportSize(enabled ? mobileViewport : desktopViewport);
|
||||
await page.evaluate((nextEnabled) => {
|
||||
@@ -28,17 +37,49 @@ export async function setObsidianMobileTestMode(port: number, enabled: boolean,
|
||||
}
|
||||
obsidianApp.emulateMobile(nextEnabled);
|
||||
}, enabled);
|
||||
await page.waitForFunction(
|
||||
(nextEnabled) => {
|
||||
// Obsidian reopens its workspace layout when platform emulation
|
||||
// changes. Loading a controlled plug-in before that transition has
|
||||
// completed can leave the plug-in enabled but absent from the active
|
||||
// renderer.
|
||||
try {
|
||||
await page.waitForFunction(
|
||||
({ nextEnabled, waitForLiveSync }) => {
|
||||
const obsidianApp = (globalThis as ObsidianTestGlobal).app;
|
||||
return (
|
||||
document.body.classList.contains("is-mobile") === nextEnabled &&
|
||||
obsidianApp?.workspace?.layoutReady === true &&
|
||||
(!waitForLiveSync || obsidianApp?.plugins?.plugins["obsidian-livesync"] !== undefined)
|
||||
);
|
||||
},
|
||||
{ nextEnabled: enabled, waitForLiveSync },
|
||||
{ timeout: timeoutMs }
|
||||
);
|
||||
} catch (error) {
|
||||
const state = await page.evaluate(() => {
|
||||
const obsidianApp = (globalThis as ObsidianTestGlobal).app;
|
||||
return (
|
||||
document.body.classList.contains("is-mobile") === nextEnabled &&
|
||||
obsidianApp?.plugins?.plugins["obsidian-livesync"] !== undefined
|
||||
);
|
||||
},
|
||||
enabled,
|
||||
{ timeout: timeoutMs }
|
||||
);
|
||||
return {
|
||||
appIsMobile: obsidianApp?.isMobile ?? null,
|
||||
bodyClasses: document.body.className,
|
||||
documentReadyState: document.readyState,
|
||||
liveSyncLoaded: obsidianApp?.plugins?.plugins["obsidian-livesync"] !== undefined,
|
||||
viewport: { width: window.innerWidth, height: window.innerHeight },
|
||||
workspaceLayoutReady: obsidianApp?.workspace?.layoutReady ?? null,
|
||||
};
|
||||
});
|
||||
const outputDirectory = process.env.E2E_OBSIDIAN_DIAGNOSTICS_DIR ?? "/tmp/obsidian-livesync-e2e";
|
||||
await mkdir(outputDirectory, { recursive: true });
|
||||
const screenshotPath = join(
|
||||
outputDirectory,
|
||||
waitForLiveSync
|
||||
? "mobile-mode-transition.failure.png"
|
||||
: "mobile-mode-before-plugin-start.failure.png"
|
||||
);
|
||||
await page.screenshot({ path: screenshotPath, fullPage: true });
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(
|
||||
`Obsidian mobile-mode transition did not settle: ${JSON.stringify(state)}; screenshot=${screenshotPath}; cause=${detail}`
|
||||
);
|
||||
}
|
||||
await page.evaluate(
|
||||
(safeArea) => {
|
||||
for (const edge of ["top", "right", "bottom", "left"] as const) {
|
||||
@@ -52,6 +93,19 @@ export async function setObsidianMobileTestMode(port: number, enabled: boolean,
|
||||
});
|
||||
}
|
||||
|
||||
/** Enters mobile emulation before LiveSync's first load in a controlled session. */
|
||||
export async function setObsidianMobileTestModeBeforePluginStart(
|
||||
port: number,
|
||||
enabled: boolean,
|
||||
timeoutMs: number
|
||||
): Promise<void> {
|
||||
await applyObsidianMobileTestMode(port, enabled, timeoutMs, false);
|
||||
}
|
||||
|
||||
export async function setObsidianMobileTestMode(port: number, enabled: boolean, timeoutMs: number): Promise<void> {
|
||||
await applyObsidianMobileTestMode(port, enabled, timeoutMs, true);
|
||||
}
|
||||
|
||||
export async function assertMobileDialogueLayout(page: Page, container: Locator, label: string): Promise<void> {
|
||||
const dialogue = container.locator(".modal").last();
|
||||
const closeButton = dialogue.locator(".modal-close-button");
|
||||
|
||||
@@ -52,4 +52,36 @@ describe("LiveSync real-Obsidian session", () => {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards instance-scoped lifecycle hooks and the selected plug-in start mode", async () => {
|
||||
const beforePluginStart = vi.fn(async () => undefined);
|
||||
const vault = {
|
||||
path: "/tmp/mobile-vault",
|
||||
statePath: "/tmp/mobile-state",
|
||||
name: "mobile-vault",
|
||||
id: "mobile-vault-id",
|
||||
homePath: "/tmp/mobile-state/home",
|
||||
xdgConfigPath: "/tmp/mobile-state/xdg-config",
|
||||
xdgCachePath: "/tmp/mobile-state/xdg-cache",
|
||||
xdgDataPath: "/tmp/mobile-state/xdg-data",
|
||||
userDataPath: "/tmp/mobile-state/user-data",
|
||||
processMarker: "/tmp/mobile-state",
|
||||
dispose: vi.fn(async () => undefined),
|
||||
};
|
||||
|
||||
await startObsidianLiveSyncSession({
|
||||
binary: "/Applications/Obsidian",
|
||||
cliBinary: "obsidian-cli",
|
||||
vault,
|
||||
pluginStartup: "controlled",
|
||||
lifecycle: { beforePluginStart },
|
||||
});
|
||||
|
||||
expect(startObsidianPluginSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
lifecycle: { beforePluginStart },
|
||||
pluginStartup: "controlled",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { startObsidianPluginSession, type ObsidianPluginSession } from "@vrtmrz/obsidian-test-session";
|
||||
import {
|
||||
startObsidianPluginSession,
|
||||
type ObsidianPluginSession,
|
||||
type ObsidianPluginSessionLifecycle,
|
||||
type ObsidianPluginStartupMode,
|
||||
} from "@vrtmrz/obsidian-test-session";
|
||||
import type { TemporaryVault } from "./vault.ts";
|
||||
|
||||
export type ObsidianLiveSyncSession = ObsidianPluginSession;
|
||||
@@ -11,6 +16,8 @@ export type StartObsidianLiveSyncSessionOptions = {
|
||||
startupGraceMs?: number;
|
||||
pluginData?: Record<string, unknown>;
|
||||
localStorageEntries?: Readonly<Record<string, string>>;
|
||||
pluginStartup?: ObsidianPluginStartupMode;
|
||||
lifecycle?: ObsidianPluginSessionLifecycle;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
};
|
||||
|
||||
@@ -26,6 +33,8 @@ export async function startObsidianLiveSyncSession(
|
||||
startupGraceMs: options.startupGraceMs,
|
||||
pluginData: options.pluginData,
|
||||
localStorageEntries: options.localStorageEntries,
|
||||
pluginStartup: options.pluginStartup,
|
||||
lifecycle: options.lifecycle,
|
||||
env: options.env,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user