mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-22 17:13:20 +00:00
Show diagnostic name and report format choices as radio buttons
This commit is contained in:
1 parent
7c59c9adbc
commit
b332be6924
6 files changed
+122
-34
No files matched your search
@@ -87,7 +87,7 @@ npm run test:e2e:obsidian:focused -- couchdb-http-cache
|
||||
|
||||
The wrapper accepts only maintained real-Obsidian scenario names; run it with `--help` for the current list. It deliberately does not manage CouchDB, Object Storage, or the P2P signalling relay. Start the required fixture first, or use the complete service-managed suite.
|
||||
|
||||
`database-diagnostics` needs no remote service. It checks the Usage command and Hatch integrity action with an isolated Vault, including the choice before inspection, original names and pseudonyms, retained local findings when the remote is unavailable, cancellation during inspection with earlier rows retained, and unchanged settings, local revisions, and Vault files. It captures desktop results and the choice and result dialogues in mobile emulation, checking result layout, format selection, and export controls. Copy and download payloads are captured inside the isolated renderer, so the scenario leaves the host clipboard and download directory unchanged.
|
||||
`database-diagnostics` needs no remote service. It checks the Usage command and Hatch integrity action with an isolated Vault, including visible radio choices for original names and pseudonyms, their default and keyboard navigation, retained local findings when the remote is unavailable, cancellation during inspection with earlier rows retained, and unchanged settings, local revisions, and Vault files. It captures desktop results and the choice and result dialogues in mobile emulation, checking layout, radio choices for available formats, their touch targets, and export controls. Copy and download payloads are captured inside the isolated renderer, so the scenario leaves the host clipboard and download directory unchanged.
|
||||
|
||||
`database-diagnostics-couchdb` needs the CouchDB fixture with CORS enabled. It checks unencrypted and encrypted databases, both browser fetch and Obsidian's native Request API, and both name policies through the real integrity dialogue. A remote-only document with a mismatched ID must produce a finding with readable Chunks and equal content. JSON and Markdown must omit file contents and credentials. The scenario observes real requests and requires GET-only access, unchanged local settings, revisions, and Vault contents, and unchanged remote documents and Security Seed. Commonlib's separate CouchDB integration tests additionally cover a missing Security Seed without creating one.
|
||||
|
||||
|
||||
@@ -171,10 +171,19 @@ async function runCase(config: CouchDbConfig, encrypted: boolean) {
|
||||
);
|
||||
assert.equal(opened, true);
|
||||
const dialogue = page.locator(".database-diagnostic");
|
||||
await dialogue.locator("select").selectOption(names);
|
||||
await dialogue.waitFor({ state: "visible", timeout });
|
||||
const nameOptions = dialogue.getByRole("group", { name: "Names in this report", exact: true });
|
||||
assert.equal(await nameOptions.getByRole("radio").count(), 2);
|
||||
const nameLabel = names === "pseudonym" ? "Pseudonyms (recommended)" : "Original names";
|
||||
await nameOptions.getByRole("radio", { name: nameLabel, exact: true }).check();
|
||||
assert.equal(await nameOptions.getByRole("radio", { checked: true }).count(), 1);
|
||||
await dialogue.getByRole("button", { name: "Begin inspection", exact: true }).click();
|
||||
await page.locator('[data-diagnostic-phase="result"]').waitFor({ timeout });
|
||||
await dialogue.locator("select").selectOption("json");
|
||||
const formats = dialogue.getByRole("group", { name: "Report format", exact: true });
|
||||
assert.equal(await formats.getByRole("radio").count(), 2);
|
||||
for (const name of ["MARKDOWN", "JSON"])
|
||||
assert.equal(await formats.getByRole("radio", { name, exact: true }).isVisible(), true);
|
||||
await formats.getByRole("radio", { name: "JSON", exact: true }).check();
|
||||
const text = await dialogue.locator("textarea").inputValue();
|
||||
const report = JSON.parse(text) as IntegrityReport;
|
||||
assert.equal(report.status, "complete");
|
||||
@@ -189,7 +198,8 @@ async function runCase(config: CouchDbConfig, encrypted: boolean) {
|
||||
assert.equal(suspect.content.status, "available");
|
||||
assert.ok(suspect.content.chunksRead > 0);
|
||||
assert.ok(suspect.comparisons.some((comparison) => comparison.relation === "equal"));
|
||||
await dialogue.locator("select").selectOption("markdown");
|
||||
await formats.getByRole("radio", { name: "MARKDOWN", exact: true }).check();
|
||||
assert.equal(await formats.getByRole("radio", { checked: true }).count(), 1);
|
||||
const markdown = await dialogue.locator("textarea").inputValue();
|
||||
for (const reportText of [text, markdown]) {
|
||||
for (const secret of [fixtureContent.trim(), passphrase, config.password, config.uri]) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import assert from "node:assert/strict";
|
||||
import type { Page } from "playwright";
|
||||
import type { Locator, Page } from "playwright";
|
||||
import {
|
||||
assertLocatorHasMinimumTouchTarget,
|
||||
assertLocatorWithinSafeArea,
|
||||
assertNoHorizontalOverflow,
|
||||
} from "@vrtmrz/obsidian-test-session";
|
||||
import { DoctorRegulation } from "@vrtmrz/livesync-commonlib/compat/common/configForDoc";
|
||||
import { requireObsidianBinary, requireObsidianCli } from "../runner/environment.ts";
|
||||
import { evalObsidianJson } from "../runner/cli.ts";
|
||||
@@ -10,7 +15,7 @@ import {
|
||||
} from "../runner/liveSyncWorkflow.ts";
|
||||
import { startObsidianLiveSyncSession, type ObsidianLiveSyncSession } from "../runner/session.ts";
|
||||
import { captureObsidianElement, captureObsidianPage, openLiveSyncSettings, withObsidianPage } from "../runner/ui.ts";
|
||||
import { assertMobileDialogueLayout, setObsidianMobileTestMode } from "../runner/mobileUi.ts";
|
||||
import { assertMobileDialogueLayout, iPhoneSafeArea, setObsidianMobileTestMode } from "../runner/mobileUi.ts";
|
||||
import { createTemporaryVault } from "../runner/vault.ts";
|
||||
|
||||
const fixturePath = "Private/diagnostic-秘密.md";
|
||||
@@ -33,12 +38,48 @@ async function openCommand(page: Page, kind: "usage" | "integrity") {
|
||||
);
|
||||
assert.equal(opened, true);
|
||||
const dialogue = page.locator(".database-diagnostic");
|
||||
await dialogue.locator("select").selectOption("pseudonym");
|
||||
await dialogue.waitFor({ state: "visible", timeout });
|
||||
assert.equal(
|
||||
await dialogue.getByRole("group", { name: "Names in this report", exact: true }).getByRole("radio").count(),
|
||||
2,
|
||||
"Both name choices must be visible as radio buttons before inspection"
|
||||
);
|
||||
const names = dialogue.getByRole("group", { name: "Names in this report", exact: true });
|
||||
for (const name of ["Pseudonyms (recommended)", "Original names"])
|
||||
assert.equal(await names.getByRole("radio", { name, exact: true }).isVisible(), true);
|
||||
assert.equal(await names.getByRole("radio", { name: "Pseudonyms (recommended)", exact: true }).isChecked(), true);
|
||||
assert.equal(await names.getByRole("radio", { checked: true }).count(), 1);
|
||||
assert.equal(await dialogue.getAttribute("data-diagnostic-phase"), "choice");
|
||||
assert.equal(await dialogue.locator("textarea").count(), 0);
|
||||
return dialogue;
|
||||
}
|
||||
|
||||
async function assertFormatChoices(dialogue: Locator, formats: string[]): Promise<void> {
|
||||
const group = dialogue.getByRole("group", { name: "Report format", exact: true });
|
||||
assert.equal(await group.getByRole("radio").count(), formats.length);
|
||||
for (const name of formats) assert.equal(await group.getByRole("radio", { name, exact: true }).isVisible(), true);
|
||||
assert.equal(await group.getByRole("radio", { checked: true }).count(), 1);
|
||||
}
|
||||
|
||||
async function chooseFormat(dialogue: Locator, format: string): Promise<void> {
|
||||
const group = dialogue.getByRole("group", { name: "Report format", exact: true });
|
||||
const radio = group.getByRole("radio", { name: format.toUpperCase(), exact: true });
|
||||
await radio.check();
|
||||
assert.equal(await radio.isChecked(), true);
|
||||
assert.equal(await group.getByRole("radio", { checked: true }).count(), 1);
|
||||
}
|
||||
|
||||
async function assertMobileRadioTargets(page: Page, dialogue: Locator, name: string): Promise<void> {
|
||||
const radios = dialogue.getByRole("group", { name, exact: true }).getByRole("radio");
|
||||
for (let index = 0; index < (await radios.count()); index++) {
|
||||
const target = radios.nth(index).locator("..");
|
||||
const label = `${name}: ${(await target.innerText()).trim()}`;
|
||||
await assertLocatorHasMinimumTouchTarget(page, target, { label });
|
||||
await assertLocatorWithinSafeArea(page, target, { label, safeAreaInsets: iPhoneSafeArea });
|
||||
await assertNoHorizontalOverflow(page, target, { label });
|
||||
}
|
||||
}
|
||||
|
||||
async function readDatabaseState(cli: string, session: ObsidianLiveSyncSession) {
|
||||
return await evalObsidianJson<{ sequence: unknown; settings: string; names: string[] }>(
|
||||
cli,
|
||||
@@ -130,18 +171,33 @@ async function main(): Promise<void> {
|
||||
let usageFixtureRow: { path: string; id: string; revision: string } | undefined;
|
||||
await withObsidianPage(session.remoteDebuggingPort, async (page) => {
|
||||
const choice = await openCommand(page, "usage");
|
||||
await choice.getByRole("radio", { name: "Pseudonyms (recommended)", exact: true }).focus();
|
||||
await page.keyboard.press("ArrowRight");
|
||||
assert.equal(await choice.getByRole("radio", { name: "Original names", exact: true }).isChecked(), true);
|
||||
await page.keyboard.press("ArrowLeft");
|
||||
assert.equal(
|
||||
await choice.getByRole("radio", { name: "Pseudonyms (recommended)", exact: true }).isChecked(),
|
||||
true
|
||||
);
|
||||
await choice.getByRole("button", { name: "Cancel", exact: true }).click();
|
||||
await choice.waitFor({ state: "hidden", timeout });
|
||||
const dialogue = await openCommand(page, "usage");
|
||||
assert.equal(await dialogue.locator("select").inputValue(), "pseudonym");
|
||||
assert.equal(
|
||||
await dialogue.getByRole("radio", { name: "Pseudonyms (recommended)", exact: true }).isChecked(),
|
||||
true
|
||||
);
|
||||
await dialogue.getByRole("button", { name: "Begin inspection", exact: true }).click();
|
||||
await page.locator('[data-diagnostic-phase="result"]').waitFor({ timeout });
|
||||
await assertFormatChoices(dialogue, ["TSV", "JSON"]);
|
||||
assert.equal(await dialogue.getByRole("radio", { name: "TSV", exact: true }).isChecked(), true);
|
||||
const tsv = await dialogue.locator("textarea").inputValue();
|
||||
assert.equal(tsv.split("\n")[0].split("\t").length, 9);
|
||||
assert.ok(!tsv.includes(fixturePath));
|
||||
assert.ok(!tsv.includes(shadowId));
|
||||
assert.ok(!tsv.includes(fixtureContent.trim()));
|
||||
await dialogue.locator("select").selectOption("json");
|
||||
await dialogue.getByRole("radio", { name: "TSV", exact: true }).focus();
|
||||
await page.keyboard.press("ArrowRight");
|
||||
assert.equal(await dialogue.getByRole("radio", { name: "JSON", exact: true }).isChecked(), true);
|
||||
usageJSON = await dialogue.locator("textarea").inputValue();
|
||||
const report = JSON.parse(usageJSON);
|
||||
assert.equal(report.status, "complete");
|
||||
@@ -173,7 +229,7 @@ async function main(): Promise<void> {
|
||||
await dialogue.waitFor({ state: "hidden", timeout });
|
||||
|
||||
const original = await openCommand(page, "usage");
|
||||
await original.locator("select").selectOption("original");
|
||||
await original.getByRole("radio", { name: "Original names", exact: true }).check();
|
||||
await original.getByRole("button", { name: "Begin inspection", exact: true }).click();
|
||||
await page.locator('[data-diagnostic-phase="result"]').waitFor({ timeout });
|
||||
const rawTSV = await original.locator("textarea").inputValue();
|
||||
@@ -190,10 +246,15 @@ async function main(): Promise<void> {
|
||||
.filter({ has: page.getByText("Inspect database integrity", { exact: true }) });
|
||||
await setting.getByRole("button", { name: "Inspect", exact: true }).click();
|
||||
const integrity = page.locator(".database-diagnostic");
|
||||
assert.equal(await integrity.locator("select").inputValue(), "pseudonym");
|
||||
assert.equal(
|
||||
await integrity.getByRole("radio", { name: "Pseudonyms (recommended)", exact: true }).isChecked(),
|
||||
true
|
||||
);
|
||||
await integrity.getByRole("button", { name: "Begin inspection", exact: true }).click();
|
||||
await page.locator('[data-diagnostic-phase="result"]').waitFor({ timeout });
|
||||
await integrity.locator("select").selectOption("json");
|
||||
await assertFormatChoices(integrity, ["MARKDOWN", "JSON"]);
|
||||
assert.equal(await integrity.getByRole("radio", { name: "MARKDOWN", exact: true }).isChecked(), true);
|
||||
await chooseFormat(integrity, "json");
|
||||
const reportText = await integrity.locator("textarea").inputValue();
|
||||
const integrityReport = JSON.parse(reportText);
|
||||
assert.equal(integrityReport.status, "partial");
|
||||
@@ -262,6 +323,7 @@ async function main(): Promise<void> {
|
||||
const cancelledText = await dialogue.locator("textarea").inputValue();
|
||||
const cancelledReport = JSON.parse(cancelledText);
|
||||
assert.equal(cancelledReport.status, "cancelled");
|
||||
await assertFormatChoices(dialogue, ["JSON"]);
|
||||
assert.ok(
|
||||
cancelledReport.rows.some(
|
||||
(row: { path: string; id: string; revision: string }) =>
|
||||
@@ -299,6 +361,7 @@ async function main(): Promise<void> {
|
||||
await withObsidianPage(session.remoteDebuggingPort, async (page) => {
|
||||
const modal = page.locator(".modal-container").filter({ has: page.locator(".database-diagnostic") });
|
||||
await assertMobileDialogueLayout(page, modal, "Database diagnostic choice");
|
||||
await assertMobileRadioTargets(page, modal, "Names in this report");
|
||||
await modal.getByRole("button", { name: "Cancel", exact: true }).click();
|
||||
await modal.waitFor({ state: "hidden", timeout });
|
||||
});
|
||||
@@ -309,20 +372,21 @@ async function main(): Promise<void> {
|
||||
const modal = page.locator(".modal-container").filter({ has: page.locator(".database-diagnostic") });
|
||||
await assertMobileDialogueLayout(page, modal, "Database diagnostic result");
|
||||
|
||||
const format = dialogue.locator("select");
|
||||
await format.selectOption("json");
|
||||
await assertFormatChoices(dialogue, ["TSV", "JSON"]);
|
||||
await assertMobileRadioTargets(page, dialogue, "Report format");
|
||||
await chooseFormat(dialogue, "json");
|
||||
const reportText = await dialogue.locator("textarea").inputValue();
|
||||
const report = JSON.parse(reportText);
|
||||
assert.equal(report.status, "complete");
|
||||
for (const privateValue of [fixturePath, shadowId, fixtureContent.trim()])
|
||||
assert.ok(!reportText.includes(privateValue));
|
||||
await format.selectOption("tsv");
|
||||
await chooseFormat(dialogue, "tsv");
|
||||
const tsv = await dialogue.locator("textarea").inputValue();
|
||||
assert.equal(tsv.split("\n")[0].split("\t").length, 9);
|
||||
assert.ok(!tsv.includes(fixturePath));
|
||||
assert.ok(!tsv.includes(shadowId));
|
||||
assert.ok(!tsv.includes(fixtureContent.trim()));
|
||||
await format.selectOption("json");
|
||||
await chooseFormat(dialogue, "json");
|
||||
});
|
||||
const mobileResultScreenshot = await captureObsidianElement(
|
||||
session.remoteDebuggingPort,
|
||||
|
||||
Reference in new issue
Block a user