* feat(projects): add local icons and colors * refactor(projects): simplify appearance handling * fix(projects): serialize appearance saves * test(app): update project appearance expectations * test: fix project appearance CI expectations * refactor(projects): organize appearance code * fix(server): adopt main's reconcile model in project appearance updates * fix(projects): preserve host-local appearance targets * fix(desktop): restore Linux development startup Ubuntu can block Electron's unprivileged user-namespace fallback while the npm-installed sandbox helper is not SUID. Disable the Chromium process sandbox only in the Linux development runner so packaged desktop policy remains unchanged. * feat(projects): add custom project icons Keep URL acquisition in the client so the host daemon only validates uploaded bytes. Project name and icon changes share one adaptive edit sheet. * refactor(settings): scope projects by host --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
47 lines
1.6 KiB
TypeScript
47 lines
1.6 KiB
TypeScript
import { Buffer } from "buffer";
|
|
import { describe, expect, it } from "vitest";
|
|
import { importProjectIconFromUrl } from "./import-project-icon";
|
|
|
|
const IMAGE = new Uint8Array([1, 2, 3, 4]);
|
|
|
|
function response(body: BodyInit, contentType: string): Response {
|
|
return new Response(body, { headers: { "content-type": contentType } });
|
|
}
|
|
|
|
describe("project icon URL import", () => {
|
|
it("turns a direct image URL into an upload payload", async () => {
|
|
const source = await importProjectIconFromUrl("https://example.com/icon.png", async () =>
|
|
response(IMAGE.buffer, "image/png"),
|
|
);
|
|
|
|
expect(source).toEqual({ type: "upload", data: Buffer.from(IMAGE).toString("base64") });
|
|
});
|
|
|
|
it("fetches a website icon on the client and returns an upload payload", async () => {
|
|
const requested: string[] = [];
|
|
const source = await importProjectIconFromUrl("https://example.com", async (input) => {
|
|
const url = input.toString();
|
|
requested.push(url);
|
|
if (url === "https://example.com/") {
|
|
return response('<link rel="icon" href="/brand.png">', "text/html");
|
|
}
|
|
return response(IMAGE.buffer, "image/png");
|
|
});
|
|
|
|
expect(requested).toEqual(["https://example.com/", "https://example.com/brand.png"]);
|
|
expect(source.type).toBe("upload");
|
|
});
|
|
|
|
it("rejects non-HTTP URLs without fetching", async () => {
|
|
let fetched = false;
|
|
|
|
await expect(
|
|
importProjectIconFromUrl("file:///etc/passwd", async () => {
|
|
fetched = true;
|
|
return response(IMAGE.buffer, "image/png");
|
|
}),
|
|
).rejects.toThrow("URL must use HTTP or HTTPS without credentials");
|
|
expect(fetched).toBe(false);
|
|
});
|
|
});
|