mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 23:35:43 +00:00
Fix generated project previews rejected by outdated size validation
This commit is contained in:
@@ -4,7 +4,7 @@ import path from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
import { hasFreshProjectThumbnail } from "./thumbnailFreshness";
|
||||
|
||||
it("rejects legacy, stale, missing and broken previews while accepting a fresh high-resolution PNG", async () => {
|
||||
it("rejects legacy, stale, missing and broken previews while accepting a fresh current-size or larger PNG", async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "recordly-preview-"));
|
||||
const file = path.join(dir, "preview.png");
|
||||
try {
|
||||
@@ -15,6 +15,10 @@ it("rejects legacy, stale, missing and broken previews while accepting a fresh h
|
||||
header.writeUInt32BE(180, 20);
|
||||
await fs.writeFile(file, header);
|
||||
expect(await hasFreshProjectThumbnail(file, 0)).toBe(false);
|
||||
header.writeUInt32BE(640, 16);
|
||||
header.writeUInt32BE(480, 20);
|
||||
await fs.writeFile(file, header);
|
||||
expect(await hasFreshProjectThumbnail(file, 0)).toBe(true);
|
||||
header.writeUInt32BE(1600, 16);
|
||||
header.writeUInt32BE(1200, 20);
|
||||
await fs.writeFile(file, header);
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import fs from "node:fs/promises";
|
||||
import {
|
||||
PROJECT_THUMBNAIL_WIDTH,
|
||||
PROJECT_THUMBNAIL_HEIGHT,
|
||||
} from "../../../src/lib/projectThumbnail";
|
||||
|
||||
// Older releases wrote 320px previews. Hide those and previews predating edits;
|
||||
// the editor replaces them with a full-resolution render when returning home.
|
||||
// the editor replaces them with a current-size render when returning home.
|
||||
export async function hasFreshProjectThumbnail(thumbnailPath: string, projectModifiedAt: number) {
|
||||
let file: Awaited<ReturnType<typeof fs.open>> | undefined;
|
||||
try {
|
||||
@@ -13,8 +17,8 @@ export async function hasFreshProjectThumbnail(thumbnailPath: string, projectMod
|
||||
return (
|
||||
bytesRead === 24 &&
|
||||
header.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) &&
|
||||
header.readUInt32BE(16) >= 1600 &&
|
||||
header.readUInt32BE(20) >= 1200
|
||||
header.readUInt32BE(16) >= PROJECT_THUMBNAIL_WIDTH &&
|
||||
header.readUInt32BE(20) >= PROJECT_THUMBNAIL_HEIGHT
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* biome-ignore-all lint/correctness/useExhaustiveDependencies: grouped editor domain objects contain the thumbnail renderer dependencies. */
|
||||
import { type RefObject, useCallback, useEffect, useRef } from "react";
|
||||
import { PROJECT_THUMBNAIL_WIDTH, PROJECT_THUMBNAIL_HEIGHT } from "@/lib/projectThumbnail";
|
||||
import { FrameRenderer } from "@/lib/exporter/frameRenderer";
|
||||
import { toFileUrl } from "../projectPersistence";
|
||||
import type { useAppearanceState } from "../state/useAppearanceState";
|
||||
@@ -112,8 +113,8 @@ export function useProjectLibraryController({
|
||||
}
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const targetWidth = 640;
|
||||
const targetHeight = 480;
|
||||
const targetWidth = PROJECT_THUMBNAIL_WIDTH;
|
||||
const targetHeight = PROJECT_THUMBNAIL_HEIGHT;
|
||||
canvas.width = targetWidth;
|
||||
canvas.height = targetHeight;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/** Shared by thumbnail generation and the persisted-preview freshness check. */
|
||||
export const PROJECT_THUMBNAIL_WIDTH = 640;
|
||||
export const PROJECT_THUMBNAIL_HEIGHT = 480;
|
||||
@@ -1,3 +1,6 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { hasFreshProjectThumbnail } from "../../electron/ipc/project/thumbnailFreshness";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { installDesktopBridge } from "./bridge";
|
||||
|
||||
@@ -140,8 +143,30 @@ test("home dashboard explains an empty library", async ({ page }) => {
|
||||
|
||||
test("autosave creates one untitled project, stays idle without edits, and refreshes its preview on exit without a saved toast", async ({
|
||||
page,
|
||||
}) => {
|
||||
}, testInfo) => {
|
||||
await installDesktopBridge(page);
|
||||
await page.addInitScript(() => {
|
||||
const save = window.electronAPI.saveProjectFile;
|
||||
window.electronAPI.saveProjectFile = async (...args) => {
|
||||
const result = await save(...args);
|
||||
if (args[3]) document.documentElement.dataset.savedThumbnail = args[3];
|
||||
return result;
|
||||
};
|
||||
window.electronAPI.listProjectFiles = async () => ({
|
||||
success: true,
|
||||
projects: [],
|
||||
entries: [
|
||||
{
|
||||
path: "/projects/preview.recordly",
|
||||
name: "Generated preview",
|
||||
updatedAt: 1,
|
||||
thumbnailPath: document.documentElement.dataset.savedThumbnail ?? null,
|
||||
isCurrent: true,
|
||||
isInProjectsDirectory: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
await page.goto("/?windowType=editor");
|
||||
await expect(page.locator("html")).toHaveAttribute("data-project-creates", "1");
|
||||
await expect(page.getByRole("button", { name: "Rename project" })).toContainText(
|
||||
@@ -158,6 +183,19 @@ test("autosave creates one untitled project, stays idle without edits, and refre
|
||||
"data-saved-thumbnail",
|
||||
/^data:image\/png;base64,/,
|
||||
);
|
||||
const thumbnail = await page.locator("html").getAttribute("data-saved-thumbnail");
|
||||
const file = testInfo.outputPath("generated-preview.png");
|
||||
await fs.mkdir(path.dirname(file), { recursive: true });
|
||||
await fs.writeFile(file, Buffer.from(thumbnail!.split(",")[1], "base64"));
|
||||
// Exercise the actual main-process acceptance check against renderer output.
|
||||
expect(await hasFreshProjectThumbnail(file, 0)).toBe(true);
|
||||
const image = page
|
||||
.getByRole("button", { name: "Generated preview", exact: true })
|
||||
.locator("img");
|
||||
await expect(image).toBeVisible();
|
||||
await expect
|
||||
.poll(() => image.evaluate((element: HTMLImageElement) => element.naturalWidth))
|
||||
.toBe(640);
|
||||
await expect(page.getByText(/Project saved/)).toHaveCount(0);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user