|
| 1 | +import { DatabaseSync } from "node:sqlite"; |
| 2 | +import { expect, type Page, test } from "@playwright/test"; |
| 3 | +import { activeProjectDbFile, expectUndoTop, goto, toast, uniqueName } from "./helpers"; |
| 4 | + |
| 5 | +async function dismissDataDriftPrompt(page: Page) { |
| 6 | + const ignore = page.getByRole("button", { name: "Ignore for now" }); |
| 7 | + try { |
| 8 | + await expect(ignore).toBeVisible({ timeout: 2_000 }); |
| 9 | + await ignore.click(); |
| 10 | + } catch { |
| 11 | + // Most runs have no reference-data drift prompt. |
| 12 | + } |
| 13 | +} |
| 14 | + |
| 15 | +test("goals copy between blocks without replacing existing goals or recipes", async ({ |
| 16 | + page, |
| 17 | + context, |
| 18 | +}) => { |
| 19 | + await context.grantPermissions(["clipboard-read", "clipboard-write"]); |
| 20 | + const sourceData = { |
| 21 | + goals: [ |
| 22 | + { name: "iron-plate", rate: 2, unit: "min" }, |
| 23 | + { name: "copper-plate", rate: 20 / 3600, stock: 20, window: 3600 }, |
| 24 | + ], |
| 25 | + recipes: ["iron-plate"], |
| 26 | + }; |
| 27 | + const destinationData = { |
| 28 | + goals: [ |
| 29 | + { name: "stone-brick", rate: 1 }, |
| 30 | + { name: "iron-plate", rate: 99 }, |
| 31 | + ], |
| 32 | + recipes: [], |
| 33 | + }; |
| 34 | + const db = new DatabaseSync(activeProjectDbFile()); |
| 35 | + let sourceId: number; |
| 36 | + let destinationId: number; |
| 37 | + try { |
| 38 | + db.exec("PRAGMA busy_timeout = 5000"); |
| 39 | + const insert = db.prepare("INSERT INTO blocks (name, data) VALUES (?, ?)"); |
| 40 | + sourceId = Number( |
| 41 | + insert.run(uniqueName("Goal clipboard source"), JSON.stringify(sourceData)).lastInsertRowid, |
| 42 | + ); |
| 43 | + destinationId = Number( |
| 44 | + insert.run(uniqueName("Goal clipboard destination"), JSON.stringify(destinationData)) |
| 45 | + .lastInsertRowid, |
| 46 | + ); |
| 47 | + } finally { |
| 48 | + db.close(); |
| 49 | + } |
| 50 | + |
| 51 | + await goto(page, `/block/${sourceId}`); |
| 52 | + await dismissDataDriftPrompt(page); |
| 53 | + await page.getByRole("button", { name: "Copy goals" }).click(); |
| 54 | + await expect(toast(page, "Copied 2 goals.")).toBeVisible(); |
| 55 | + |
| 56 | + await goto(page, `/block/${destinationId}`); |
| 57 | + await dismissDataDriftPrompt(page); |
| 58 | + await page.getByRole("button", { name: "Paste goals" }).click(); |
| 59 | + await expect(toast(page, "Pasted 1 goal; skipped 1 already present.")).toBeVisible(); |
| 60 | + await expect(page.locator("[data-goal]")).toHaveCount(3); |
| 61 | + await expect(page.locator("[data-goal]").nth(0)).toHaveAttribute("data-goal", "stone-brick"); |
| 62 | + await expect(page.locator("[data-goal]").nth(1)).toHaveAttribute("data-goal", "iron-plate"); |
| 63 | + await expect(page.locator("[data-goal]").nth(2)).toHaveAttribute("data-goal", "copper-plate"); |
| 64 | + await expectUndoTop(page, /Undo: Paste 1 goal/); |
| 65 | + |
| 66 | + const saved = new DatabaseSync(activeProjectDbFile(), { readOnly: true }); |
| 67 | + try { |
| 68 | + const row = saved.prepare("SELECT data FROM blocks WHERE id = ?").get(destinationId) as { |
| 69 | + data: string; |
| 70 | + }; |
| 71 | + const parsed = JSON.parse(row.data) as typeof destinationData; |
| 72 | + expect(parsed.goals).toEqual([ |
| 73 | + { name: "stone-brick", rate: 1 }, |
| 74 | + { name: "iron-plate", rate: 99 }, |
| 75 | + { name: "copper-plate", rate: 20 / 3600, stock: 20, window: 3600 }, |
| 76 | + ]); |
| 77 | + expect(parsed.recipes).toEqual([]); |
| 78 | + } finally { |
| 79 | + saved.close(); |
| 80 | + } |
| 81 | +}); |
0 commit comments