|
2 | 2 |
|
3 | 3 | import { html, render } from "lit"; |
4 | 4 | import { beforeEach, describe, expect, it, vi } from "vitest"; |
| 5 | +import { ConnectErrorDetailCodes } from "../../../packages/gateway-protocol/src/connect-error-details.js"; |
5 | 6 | import { i18n } from "../i18n/index.ts"; |
6 | 7 | import type { AppViewState } from "./app-view-state.ts"; |
7 | 8 | import type { ChatProps } from "./views/chat.ts"; |
@@ -81,6 +82,8 @@ function createState(overrides: Partial<AppViewState> = {}): AppViewState { |
81 | 82 | onboarding: false, |
82 | 83 | basePath: "", |
83 | 84 | connected: true, |
| 85 | + hasConnectedGateway: true, |
| 86 | + gatewayDeviceTokenRetryPending: false, |
84 | 87 | theme: "claw", |
85 | 88 | themeMode: "dark", |
86 | 89 | themeResolved: "dark", |
@@ -220,6 +223,7 @@ function createState(overrides: Partial<AppViewState> = {}): AppViewState { |
220 | 223 | clearCustomTheme: vi.fn(), |
221 | 224 | setBorderRadius: vi.fn(), |
222 | 225 | setTextScale: vi.fn(), |
| 226 | + setGatewayPassword: vi.fn(), |
223 | 227 | applySettings: vi.fn(), |
224 | 228 | applyLocalUserIdentity: vi.fn(), |
225 | 229 | loadOverview: vi.fn(), |
@@ -450,6 +454,86 @@ describe("renderApp assistant avatar routing", () => { |
450 | 454 | expect(chatProps.current?.error).toBe("gateway disconnected"); |
451 | 455 | }); |
452 | 456 |
|
| 457 | + it("keeps the dashboard mounted during transient reconnects after a successful connection", () => { |
| 458 | + const container = document.createElement("div"); |
| 459 | + |
| 460 | + render( |
| 461 | + renderApp( |
| 462 | + createState({ |
| 463 | + tab: "chat", |
| 464 | + connected: false, |
| 465 | + hasConnectedGateway: true, |
| 466 | + lastError: "disconnected (1006): no reason", |
| 467 | + } as Partial<AppViewState>), |
| 468 | + ), |
| 469 | + container, |
| 470 | + ); |
| 471 | + |
| 472 | + expect(container.querySelector(".shell")).toBeInstanceOf(HTMLElement); |
| 473 | + expect(container.querySelector(".login-gate")).toBeNull(); |
| 474 | + expect(chatProps.current?.error).toBe("disconnected (1006): no reason"); |
| 475 | + }); |
| 476 | + |
| 477 | + it("shows the login gate after a successful connection when auth is rejected", () => { |
| 478 | + const container = document.createElement("div"); |
| 479 | + |
| 480 | + render( |
| 481 | + renderApp( |
| 482 | + createState({ |
| 483 | + connected: false, |
| 484 | + hasConnectedGateway: true, |
| 485 | + lastError: "gateway token mismatch", |
| 486 | + lastErrorCode: "AUTH_TOKEN_MISMATCH", |
| 487 | + } as Partial<AppViewState>), |
| 488 | + ), |
| 489 | + container, |
| 490 | + ); |
| 491 | + |
| 492 | + expect(container.querySelector(".login-gate")).toBeInstanceOf(HTMLElement); |
| 493 | + expect(container.querySelector(".shell")).toBeNull(); |
| 494 | + }); |
| 495 | + |
| 496 | + it("keeps the dashboard mounted during automatic device token retries", () => { |
| 497 | + const container = document.createElement("div"); |
| 498 | + |
| 499 | + render( |
| 500 | + renderApp( |
| 501 | + createState({ |
| 502 | + tab: "chat", |
| 503 | + connected: false, |
| 504 | + hasConnectedGateway: true, |
| 505 | + gatewayDeviceTokenRetryPending: true, |
| 506 | + lastError: "gateway token mismatch", |
| 507 | + lastErrorCode: ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH, |
| 508 | + } as Partial<AppViewState>), |
| 509 | + ), |
| 510 | + container, |
| 511 | + ); |
| 512 | + |
| 513 | + expect(container.querySelector(".shell")).toBeInstanceOf(HTMLElement); |
| 514 | + expect(container.querySelector(".login-gate")).toBeNull(); |
| 515 | + expect(chatProps.current?.error).toBe("gateway token mismatch"); |
| 516 | + }); |
| 517 | + |
| 518 | + it("shows the login gate after a successful connection when auth scope must change", () => { |
| 519 | + const container = document.createElement("div"); |
| 520 | + |
| 521 | + render( |
| 522 | + renderApp( |
| 523 | + createState({ |
| 524 | + connected: false, |
| 525 | + hasConnectedGateway: true, |
| 526 | + lastError: "requested scopes are not approved", |
| 527 | + lastErrorCode: ConnectErrorDetailCodes.AUTH_SCOPE_MISMATCH, |
| 528 | + } as Partial<AppViewState>), |
| 529 | + ), |
| 530 | + container, |
| 531 | + ); |
| 532 | + |
| 533 | + expect(container.querySelector(".login-gate")).toBeInstanceOf(HTMLElement); |
| 534 | + expect(container.querySelector(".shell")).toBeNull(); |
| 535 | + }); |
| 536 | + |
453 | 537 | it("does not rebuild chat composer controls for draft-only rerenders", () => { |
454 | 538 | const container = document.createElement("div"); |
455 | 539 | const state = createState({ tab: "chat", chatMessage: "" }); |
|
0 commit comments