fix(web): admin bibliothèques — correctifs complémentaires client et AdminPage

- web: AdminPage, client API (+ tests), styles

Refs: #15
This commit is contained in:
Git Agent
2026-08-23 11:36:17 +02:00
parent 39dd130c47
commit a30971f1e9
4 changed files with 53 additions and 7 deletions

View File

@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { ApiFallbackError, getApiFallback } from "./client"; import { api, ApiFallbackError, getApiFallback } from "./client";
afterEach(() => {
vi.unstubAllGlobals();
});
describe("api fallback helpers", () => { describe("api fallback helpers", () => {
it("extracts typed fallback payloads", () => { it("extracts typed fallback payloads", () => {
@ -9,4 +13,21 @@ describe("api fallback helpers", () => {
it("ignores non fallback errors", () => { it("ignores non fallback errors", () => {
expect(getApiFallback<string[]>(new Error("boom"))).toBeUndefined(); expect(getApiFallback<string[]>(new Error("boom"))).toBeUndefined();
}); });
it("does not send JSON content-type for bodyless delete requests", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "Content-Type": "application/json" }
})
);
vi.stubGlobal("fetch", fetchMock);
await api.deleteLibrary(42);
const init = fetchMock.mock.calls[0][1] as RequestInit;
const headers = new Headers(init.headers);
expect(init.method).toBe("DELETE");
expect(headers.has("Content-Type")).toBe(false);
});
}); });

View File

@ -56,15 +56,20 @@ function apiErrorMessage(detail: string, fallback: string): string {
return fallback; return fallback;
} }
function requestHeaders(options: RequestOptions): Headers {
const headers = new Headers(options.headers);
if (options.body !== undefined && !headers.has("Content-Type")) {
headers.set("Content-Type", "application/json");
}
return headers;
}
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> { async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
try { try {
const response = await fetch(`${API_BASE}${path}`, { const response = await fetch(`${API_BASE}${path}`, {
...options, ...options,
credentials: "include", credentials: "include",
headers: { headers: requestHeaders(options)
"Content-Type": "application/json",
...options.headers
}
}); });
if (!response.ok) { if (!response.ok) {

View File

@ -158,7 +158,7 @@ export function AdminPage() {
<div className="library-table"> <div className="library-table">
{libraries.map((library) => ( {libraries.map((library) => (
<div key={library.id}> <div key={library.id}>
<div> <div className="library-copy">
<strong>{library.name}</strong> <strong>{library.name}</strong>
<span>Chemin : {library.path}</span> <span>Chemin : {library.path}</span>
</div> </div>

View File

@ -284,6 +284,26 @@ h2 {
align-items: center; align-items: center;
} }
.library-copy {
display: grid;
gap: 7px;
min-width: 0;
}
.library-copy strong,
.library-copy span {
display: block;
}
.library-copy strong {
overflow-wrap: anywhere;
}
.library-copy span {
color: var(--ink-muted);
overflow-wrap: anywhere;
}
.meter { .meter {
display: block; display: block;
width: 100%; width: 100%;