Files
SlabUploader/frontend/tests/e2e/helpers/draft.ts
T
grokbot_ty 610b0883fe feat(review): remove species picker; species stays from Measure
Species is chosen on the merged Measure screen; the Review select was a
duplicate. Keep species_id on the slab from createSlab (Capture→draft→Woo)
and gate Create title from the hydrated value without offering edit.
2026-09-26 08:47:40 -07:00

107 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// e2e helper: seed an in-progress device draft (stage='measure', with results) so
// the Resume path restores straight to the Photos-ready Measure step — the
// deterministic driver for the create→review→publish flow (no live camera/canvas
// in e2e). The draft is written to the real idb-keyval DB ('keyval-store'/'keyval',
// key 'slabuploader-draft') exactly as `saveDraft` would, so `resumeProbe` +
// `continueDraft` + `continueToReview` consume it unmodified.
//
// `source.blob` MUST be a decodable image: restoreFromDraft re-derives the mask
// in a Worker that decodes the blob; a failed decode falls back to `mask` (no
// results → the Continue button never renders). A 1×1 white PNG decodes fine and
// yields slabPx:0 (an empty mask) → rederiveMask resolves → stage reaches measure.
import type { Page } from '@playwright/test';
const KEY = 'slabuploader-draft';
export interface SeedMeasure {
sku: string;
lengthIn: number;
thicknessIn: number;
sqft: number;
bdft: number;
widthIn: number;
/** Woo species leaf; required for Review-ready e2e now that Review has no species picker. */
speciesId?: number | null;
}
const toEighths = (inches: number) => Math.round(inches * 8);
export async function seedMeasureDraft(
page: Page,
d: SeedMeasure,
): Promise<void> {
const widthEighths = toEighths(d.widthIn);
// Build the decodable source PNG in the page via canvas (a fixed base64 1×1
// gray+alpha PNG trips Chromium's software decoder in headless), then write
// the draft with that blob so restoreFromDraft's worker decode succeeds.
await page.evaluate(
(payload) =>
new Promise<void>((resolve, reject) => {
const c = document.createElement('canvas');
c.width = 1;
c.height = 1;
const ctx = c.getContext('2d')!;
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, 1, 1);
c.toBlob((png) => {
if (!png) { reject(new Error('canvas toBlob returned null')); return; }
const draft = {
version: 1,
stage: 'measure',
batch: [],
calibrationIndex: null,
source: { widthPx: 1, heightPx: 1, blob: png },
maskParams: { sheet: 'green', sensitivity: 50 },
mask: {
slabPx: 0,
bbox: { corners: [], longSidePx: 0, angleDeg: 0 },
slabBounds: { x: 0, y: 0, w: 0, h: 0 },
},
axisBbox: null,
lengthEighths: payload.lengthEighths,
thicknessEighths: payload.thicknessEighths,
species_id: payload.speciesId ?? null,
results: {
lengthEighths: payload.lengthEighths,
thicknessEighths: payload.thicknessEighths,
scale: 8,
widths: {
min: payload.widthEighths,
max: payload.widthEighths,
avg: payload.widthEighths,
every6In: [],
},
sqft: payload.sqft,
bdft: payload.bdft,
sourceBelowFloor: false,
},
inventoryPng: png,
overlayPng: null,
batchApply: null,
};
const req = indexedDB.open('keyval-store', 1);
req.onupgradeneeded = () => {
const db = req.result;
if (!db.objectStoreNames.contains('keyval')) db.createObjectStore('keyval');
};
req.onsuccess = () => {
const db = req.result;
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').put(draft, 'slabuploader-draft');
tx.oncomplete = () => { db.close(); resolve(); };
tx.onerror = () => { db.close(); reject(tx.error); };
};
req.onerror = () => reject(req.error);
}, 'image/png');
}),
{
lengthEighths: toEighths(d.lengthIn),
thicknessEighths: toEighths(d.thicknessIn),
widthEighths,
sqft: d.sqft,
bdft: d.bdft,
speciesId: d.speciesId ?? null,
},
);
}