Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions e2e/base-path.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ test("production deployment behind a path-preserving reverse proxy", async ({ pa
expect(new URL(oidcError.headers().location, baseURL).href).toBe(`${baseURL}${prefix}/login?error=oidc_config`);

await page.goto(`${prefix}/login`);
await expect(page.locator('link[rel="manifest"]')).toHaveAttribute("href", `${prefix}/site.webmanifest`);
await expect(page.locator('link[rel="apple-touch-icon"]')).toHaveAttribute("href", `${prefix}/apple-touch-icon.png`);
const manifestResponse = await request.get(`${prefix}/site.webmanifest`);
expect(manifestResponse.status()).toBe(200);
const manifest = (await manifestResponse.json()) as { start_url: string; icons: { src: string }[] };
expect(new URL(manifest.start_url, `${baseURL}${prefix}/site.webmanifest`).pathname).toBe(`${prefix}/`);
for (const icon of manifest.icons) {
expect((await request.get(new URL(icon.src, `${baseURL}${prefix}/site.webmanifest`).pathname)).status()).toBe(200);
}
expect((await request.get(`${prefix}/apple-touch-icon.png`)).status()).toBe(200);

await page.locator('input[type="email"]:visible').fill("user@libredb.org");
await page.locator('input[type="password"]:visible').fill("test-user");
await page.getByRole("button", { name: /sign in/i }).click();
Expand Down
Binary file added public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions public/site.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "LibreDB Studio",
"short_name": "LibreDB",
"start_url": ".",
"display": "standalone",
"icons": [
{
"src": "web-app-icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "web-app-icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#09090b",
"background_color": "#09090b"
}
Binary file added public/web-app-icon-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/web-app-icon-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ export const metadata: Metadata = {
description,
images: [previewImage],
},
manifest: withBasePath("/site.webmanifest"),
icons: {
icon: [
{ url: withBasePath("/favicon.ico?v=2"), sizes: "any" },
{ url: withBasePath("/logo.svg?v=2"), type: "image/svg+xml" },
],
shortcut: withBasePath("/favicon.ico?v=2"),
apple: withBasePath("/favicon-32x32.png?v=2"),
apple: [{ url: withBasePath("/apple-touch-icon.png"), sizes: "180x180", type: "image/png" }],
},
};

Expand Down
7 changes: 7 additions & 0 deletions tests/components/RootLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ describe("RootLayout", () => {
expect(description).not.toMatch(/\d/);
});

test("links the web app manifest and iOS home-screen icon", () => {
expect(metadata.manifest).toBe("/site.webmanifest");
expect(metadata.icons).toMatchObject({
apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
});
});

test("renders children", () => {
const { getByText } = render(
<RootLayout>
Expand Down
57 changes: 57 additions & 0 deletions tests/unit/web-app-manifest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { join } from "node:path";

const PUBLIC_DIR = join(import.meta.dir, "../../public");

interface ManifestIcon {
src: string;
sizes: string;
type: string;
}

interface WebManifest {
name: string;
short_name: string;
start_url: string;
display: string;
theme_color: string;
background_color: string;
icons: ManifestIcon[];
}

function pngDimensions(file: string): { width: number; height: number } {
const image = readFileSync(join(PUBLIC_DIR, file));
expect(image.subarray(0, 8)).toEqual(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
return { width: image.readUInt32BE(16), height: image.readUInt32BE(20) };
}

describe("web app manifest", () => {
const manifest = JSON.parse(readFileSync(join(PUBLIC_DIR, "site.webmanifest"), "utf8")) as WebManifest;

test("describes LibreDB Studio as an installable standalone app", () => {
expect(manifest).toMatchObject({
name: "LibreDB Studio",
short_name: "LibreDB",
start_url: ".",
display: "standalone",
theme_color: "#09090b",
background_color: "#09090b",
});
});

test("declares the two standard PNG app icon sizes", () => {
expect(manifest.icons).toEqual([
{ src: "web-app-icon-192x192.png", sizes: "192x192", type: "image/png" },
{ src: "web-app-icon-512x512.png", sizes: "512x512", type: "image/png" },
]);
});

test.each([
["web-app-icon-192x192.png", 192],
["web-app-icon-512x512.png", 512],
["apple-touch-icon.png", 180],
])("%s is a valid square PNG with the advertised dimensions", (file, size) => {
expect(pngDimensions(file)).toEqual({ width: size, height: size });
});
});
Loading