mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
A concurrent edit is preserved as `<name>.bdrive-conflict-<device>-<utc>` — the guarantee the whole shared-folder promise rests on. Until now that promise appeared in the README, the docs and syncer.go, and nowhere in the hub: a conflict copy was an ordinary row with an alarming name, and a user could only learn what it was by reading the README. conflictName is a pure function of the path, so the frontend recovers the device and the moment from the string alone — no server route, no journal field, no request. lib/conflict.ts holds the parser (anchored suffix, a strictly narrower match of the Go convention; anything malformed is null, never a throw), the listing marks the row, and ConflictBanner explains the file and links the version that kept the original name. History and the Dashboard stay out, per the spec's stated cut. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
48 lines
2.1 KiB
TypeScript
48 lines
2.1 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
import { login, wikiId } from "./helpers";
|
|
|
|
// BEA-128: a conflict copy is the guarantee the shared-folder promise rests
|
|
// on, and it used to appear nowhere in the hub. Two surfaces: the listing
|
|
// row says what the file is, the file page explains it.
|
|
|
|
const COPY = "archive/old-runbook.md.bdrive-conflict-mira-laptop-20260814T060945Z";
|
|
const NAME = COPY.split("/").pop()!;
|
|
|
|
test("the file listing flags a conflict copy", async ({ page }) => {
|
|
await login(page);
|
|
const pid = await wikiId(page);
|
|
await page.goto(`/${pid}/archive`);
|
|
const row = page.locator(".dl-row").filter({ hasText: NAME });
|
|
await expect(row.locator(".dl-conflict")).toHaveText("conflict copy");
|
|
// The label has to travel without hover — touch and screen readers get
|
|
// neither the title nor the badge's own two words.
|
|
await expect(row.locator(".dl-conflict")).toHaveAttribute(
|
|
"aria-label",
|
|
/concurrent edit from mira-laptop/,
|
|
);
|
|
// Ordinary files keep their ordinary row.
|
|
await expect(
|
|
page.locator(".dl-row").filter({ hasText: /^old-runbook\.md/ }).first().locator(".dl-conflict"),
|
|
).toHaveCount(0);
|
|
});
|
|
|
|
test("the conflict copy's page explains it and links the other version", async ({ page }) => {
|
|
await login(page);
|
|
const pid = await wikiId(page);
|
|
await page.goto(`/${pid}/${COPY}`);
|
|
const banner = page.locator(".vbanner").filter({ hasText: "Conflict copy" });
|
|
await expect(banner).toContainText("mira-laptop");
|
|
await expect(banner).toContainText("2026"); // the moment, in local time
|
|
await expect(banner).toContainText("archive/old-runbook.md");
|
|
await banner.getByRole("button", { name: /other version/i }).click();
|
|
await page.waitForURL(`/${pid}/archive/old-runbook.md`);
|
|
await expect(page.locator("#content")).toContainText("Still read, never maintained");
|
|
});
|
|
|
|
test("an ordinary file gets no conflict banner", async ({ page }) => {
|
|
await login(page);
|
|
const pid = await wikiId(page);
|
|
await page.goto(`/${pid}/archive/old-runbook.md`);
|
|
await expect(page.locator(".vbanner").filter({ hasText: "Conflict copy" })).toHaveCount(0);
|
|
});
|