feat(frontend): écran Appareils et parcours d'appairage nommé (#77 F1/F2)
Expose la gestion des appareils appairés introduite en B1-B4, sur une surface unique partagée par le web et le desktop. - Écran Appareils : liste, renommage, révocation unitaire ou globale, activité formatée, panneau de code éphémère. - Le parcours d'appairage demande un nom d'appareil, pour qu'une révocation porte sur quelque chose d'identifiable par l'utilisateur. - Gateways DeviceGateway en trois adapters (Tauri, HTTP, Mock), le port restant le seul contrat connu de la feature. Les erreurs sont mappées localement et le message du serveur n'est jamais affiché tel quel : un échec d'appairage ne doit pas devenir un oracle pour qui teste des codes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -5,7 +5,7 @@
|
||||
* These pin the UX invariants the screen exists for, not its styling: the mode
|
||||
* is a radio choice with consequences, the authorized-proxy field is explicitly
|
||||
* *not* a listen address, addresses come from the backend, a refusal is
|
||||
* actionable, and the pairing code is runtime-only.
|
||||
* actionable, and no pairing code is ever shown here (#77).
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
@ -142,30 +142,31 @@ describe("DeploymentSettings", () => {
|
||||
expect(screen.queryByRole("textbox", { name: /upstream/i })).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the pairing code runtime-only: absent until running, gone after stop", async () => {
|
||||
it("never shows a pairing code, running or not (#77)", async () => {
|
||||
// A code is no longer a property of a running server: it exists only when
|
||||
// asked for. Starting the server must not make one appear here — this is
|
||||
// what the old "runtime-only code" test asserted, and it can no longer be
|
||||
// true of any backend response.
|
||||
renderView();
|
||||
await settle();
|
||||
|
||||
expect(
|
||||
screen.getByText("Start the server to generate a pairing code."),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByRole("button", { name: "copy pairing code" })).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Start" }));
|
||||
await waitFor(() => expect(screen.getByText("Running")).toBeTruthy());
|
||||
|
||||
expect(
|
||||
await screen.findByRole("button", { name: "copy pairing code" }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
screen.getByText(/Temporary code. It disappears when the server stops/),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByRole("button", { name: "copy pairing code" })).toBeNull();
|
||||
expect(screen.queryByText(/generate a pairing code/i)).toBeNull();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Stop" }));
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText("Start the server to generate a pairing code."),
|
||||
).toBeTruthy(),
|
||||
);
|
||||
it("points to where pairing now lives instead of dead-ending", async () => {
|
||||
// Someone who just started the server from this screen needs to know where
|
||||
// to go next; silence would be a cul-de-sac.
|
||||
renderView();
|
||||
await settle();
|
||||
|
||||
const pairing = screen.getByText(/Pairing is managed in/);
|
||||
expect(within(pairing).getByText("Settings → Appareils")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("starts the server and reports the local URL", async () => {
|
||||
|
||||
Reference in New Issue
Block a user