import { beforeEach, describe, expect, it, vi } from "vite-plus/test";import { PROJECT_FAVICON_MAX_DATA_URL_LENGTH } from "@t3tools/client-runtime/project-favicon-cache"; const native = vi.hoisted(() => ({ load: vi.fn(async (_url: string, options: { maxWidth: number; maxHeight: number }) => ({ width: options.maxWidth, height: options.maxHeight, release: vi.fn(), })), write: vi.fn(async () => {}), path: vi.fn(async () => "/cache/thumbnail"), read: vi.fn(), remove: vi.fn(),}));vi.mock("expo-image", () => ({ Image: { loadAsync: native.load, writeToCacheAsync: native.write, getCachePathAsync: native.path, },}));vi.mock("expo-file-system", () => ({ File: class { size = 24_000; base64 = native.read; delete = native.remove; },})); import { downscaleProjectFavicon } from "./projectFaviconCache"; const png = "iVBORw0KGgoAAAAA";const image = { url: "https://remote/icon.png" }; beforeEach(() => { vi.clearAllMocks(); native.read.mockReset().mockResolvedValue(png); native.load.mockReset().mockImplementation(async (_url, { maxWidth }) => ({ width: maxWidth, height: maxWidth, release: vi.fn(), }));}); describe("mobile project icon thumbnails", () => { it("reduces an oversized encoding and deletes temporary thumbnail files", async () => { native.read.mockResolvedValueOnce( `iVBORw0KGgo${"a".repeat(PROJECT_FAVICON_MAX_DATA_URL_LENGTH)}`, ); const thumbnail = await downscaleProjectFavicon(image, new AbortController().signal); expect(thumbnail).toBe(`data:image/png;base64,${png}`); expect(native.load.mock.calls.map(([, options]) => options.maxWidth)).toEqual([96, 48]); expect(native.remove).toHaveBeenCalledTimes(2); for (const call of native.load.mock.results) expect((await call.value).release).toHaveBeenCalledOnce(); }); it("releases a decoded image when its request was canceled", async () => { const controller = new AbortController(); const release = vi.fn(); native.load.mockImplementationOnce(async () => { controller.abort(); return { width: 96, height: 96, release }; }); await expect(downscaleProjectFavicon(image, controller.signal)).rejects.toThrow(); expect(release).toHaveBeenCalledOnce(); expect(native.write).not.toHaveBeenCalled(); }); it("rejects an image the native decoder did not downsize", async () => { const release = vi.fn(); native.load.mockResolvedValueOnce({ width: 4000, height: 3000, release }); await expect(downscaleProjectFavicon(image, new AbortController().signal)).rejects.toThrow( "not resized", ); expect(native.write).not.toHaveBeenCalled(); expect(release).toHaveBeenCalledOnce(); });});
import { beforeEach, describe, expect, it, vi } from "vite-plus/test";
import { PROJECT_FAVICON_MAX_DATA_URL_LENGTH } from "@t3tools/client-runtime/project-favicon-cache";
const native = vi.hoisted(() => ({
load: vi.fn(async (_url: string, options: { maxWidth: number; maxHeight: number }) => ({
width: options.maxWidth,
height: options.maxHeight,
release: vi.fn(),
})),
write: vi.fn(async () => {}),
path: vi.fn(async () => "/cache/thumbnail"),
read: vi.fn(),
remove: vi.fn(),
}));
vi.mock("expo-image", () => ({
Image: {
loadAsync: native.load,
writeToCacheAsync: native.write,
getCachePathAsync: native.path,
},
}));
vi.mock("expo-file-system", () => ({
File: class {
size = 24_000;
base64 = native.read;
delete = native.remove;
},
}));
import { downscaleProjectFavicon } from "./projectFaviconCache";
const png = "iVBORw0KGgoAAAAA";
const image = { url: "https://remote/icon.png" };
beforeEach(() => {
vi.clearAllMocks();
native.read.mockReset().mockResolvedValue(png);
native.load.mockReset().mockImplementation(async (_url, { maxWidth }) => ({
width: maxWidth,
height: maxWidth,
release: vi.fn(),
}));
});
describe("mobile project icon thumbnails", () => {
it("reduces an oversized encoding and deletes temporary thumbnail files", async () => {
native.read.mockResolvedValueOnce(
`iVBORw0KGgo${"a".repeat(PROJECT_FAVICON_MAX_DATA_URL_LENGTH)}`,
);
const thumbnail = await downscaleProjectFavicon(image, new AbortController().signal);
expect(thumbnail).toBe(`data:image/png;base64,${png}`);
expect(native.load.mock.calls.map(([, options]) => options.maxWidth)).toEqual([96, 48]);
expect(native.remove).toHaveBeenCalledTimes(2);
for (const call of native.load.mock.results)
expect((await call.value).release).toHaveBeenCalledOnce();
});
it("releases a decoded image when its request was canceled", async () => {
const controller = new AbortController();
const release = vi.fn();
native.load.mockImplementationOnce(async () => {
controller.abort();
return { width: 96, height: 96, release };
});
await expect(downscaleProjectFavicon(image, controller.signal)).rejects.toThrow();
expect(release).toHaveBeenCalledOnce();
expect(native.write).not.toHaveBeenCalled();
});
it("rejects an image the native decoder did not downsize", async () => {
const release = vi.fn();
native.load.mockResolvedValueOnce({ width: 4000, height: 3000, release });
await expect(downscaleProjectFavicon(image, new AbortController().signal)).rejects.toThrow(
"not resized",
);
expect(native.write).not.toHaveBeenCalled();
expect(release).toHaveBeenCalledOnce();
});
});