mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: resolve 41 bugs and wire 21 unimplemented features in image editor
Canvas rendering: - Fix Konva filter application order (filters before cache) - Implement 6 missing filters (motionBlur, radialBlur, surfaceBlur, vignette, grain, sharpen) - Implement exposure, vibrance, warmth adjustments as custom Konva filters - Apply layer blend modes via globalCompositeOperation - Apply object effects (drop shadow, outer glow, stroke) to all shapes - Mount SmartGuidesOverlay during move tool drag - Clip pixel grid to visible viewport (200-line cap for performance) Store logic: - resizeImage now scales all objects proportionally (points, radii, fontSize) - rotate/flip/trim handle line/arrow points arrays and center-based objects - applyCrop creates cropped source image via offscreen canvas - invertSelection creates mask from bounds when no mask exists - cutObjects uses single atomic set() to prevent race conditions - sendToBack respects layer ordering in multi-layer documents - Add batchNudge() and commitHistory() for undoable nudge operations - Add updateLayerThumbnail() method Tool hooks: - Fix clone stamp/dodge/burn perf (toDataURL only on mouseUp, not every move) - Fix magic wand zoom/pixelRatio with explicit stage.toCanvas() viewport - Fix eyedropper sampling with unzoomed canvas export - Fix selection tool stale closure via isDrawingRef - Implement polygonal lasso (click-to-place vertices, double-click to close) - Implement selection subtract mode (geometric and mask-based) - Implement gradient live preview during drag - Fix transform/move tool to persist changes and handle ellipse/polygon/star UI wiring: - Mount rulers and guidelines in editor page - Wire histogram with live canvas imageData - Wire autosave recovery with blob-to-dataURL conversion - Wire fill dialog to Shift+Backspace shortcut - Wire eyedropper and transform options to options bar - Fix history panel undo/redo button reactive state via useSyncExternalStore - Fix layer row name click to select layer (timer-based click/dblclick) - Fix zoom animation coordinate drift with progressive store sync - Fix copy merged to use Konva stage composite export Tests: - 49 new unit tests (store fixes + konva filters) - 8 new E2E test files with 39 test cases
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { createNewDocument, drawOnCanvas, expect, selectTool, test } from "./helpers";
|
||||
|
||||
test.describe("Editor Autosave", () => {
|
||||
test("editor saves state periodically (check localStorage has autosave data)", async ({
|
||||
editorPage: page,
|
||||
}) => {
|
||||
test.slow();
|
||||
|
||||
await createNewDocument(page);
|
||||
|
||||
// Draw something to mark the canvas dirty
|
||||
await selectTool(page, "brush");
|
||||
await drawOnCanvas(page, 100, 100, 300, 300);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Manually trigger autosave by calling saveEditorState from the page context.
|
||||
// The autosave interval is 60s which is too long for E2E, so invoke directly.
|
||||
await page.evaluate(async () => {
|
||||
// The saveEditorState function writes to localStorage under this key
|
||||
const { saveEditorState } = await import("/src/components/editor/common/export-dialog.tsx");
|
||||
await saveEditorState();
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify localStorage contains the autosave key
|
||||
const autosaveData = await page.evaluate(() => {
|
||||
return localStorage.getItem("snapotter-editor-autosave");
|
||||
});
|
||||
|
||||
expect(autosaveData).not.toBeNull();
|
||||
|
||||
// Parse and verify the structure
|
||||
const parsed = JSON.parse(autosaveData!);
|
||||
expect(parsed.version).toBe(1);
|
||||
expect(parsed.timestamp).toBeGreaterThan(0);
|
||||
expect(parsed.state).toBeDefined();
|
||||
expect(parsed.state.canvasSize).toBeDefined();
|
||||
expect(parsed.state.canvasSize.width).toBeGreaterThan(0);
|
||||
expect(parsed.state.canvasSize.height).toBeGreaterThan(0);
|
||||
expect(parsed.state.layers).toBeDefined();
|
||||
expect(Array.isArray(parsed.state.layers)).toBe(true);
|
||||
});
|
||||
|
||||
test("modified content persists across page reload", async ({ editorPage: page }) => {
|
||||
test.slow();
|
||||
|
||||
await createNewDocument(page);
|
||||
|
||||
// Draw something to create content
|
||||
await selectTool(page, "brush");
|
||||
await drawOnCanvas(page, 100, 100, 300, 300);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Trigger autosave manually
|
||||
await page.evaluate(async () => {
|
||||
const { saveEditorState } = await import("/src/components/editor/common/export-dialog.tsx");
|
||||
await saveEditorState();
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify autosave data exists before reload
|
||||
const dataBefore = await page.evaluate(() => {
|
||||
return localStorage.getItem("snapotter-editor-autosave");
|
||||
});
|
||||
expect(dataBefore).not.toBeNull();
|
||||
|
||||
// Reload the page
|
||||
await page.reload();
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// After reload, localStorage should still have the autosave data
|
||||
const dataAfter = await page.evaluate(() => {
|
||||
return localStorage.getItem("snapotter-editor-autosave");
|
||||
});
|
||||
expect(dataAfter).not.toBeNull();
|
||||
|
||||
// The data should contain the same canvas dimensions
|
||||
const parsedBefore = JSON.parse(dataBefore!);
|
||||
const parsedAfter = JSON.parse(dataAfter!);
|
||||
expect(parsedAfter.state.canvasSize.width).toBe(parsedBefore.state.canvasSize.width);
|
||||
expect(parsedAfter.state.canvasSize.height).toBe(parsedBefore.state.canvasSize.height);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user