mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Fixes from an adversarial review of the previous commit:
- MainPage banner: sync the banner with the current store status at mount.
The selector subscription baselines on the current value and only fires
on change, so a MainPage mounted mid-outage (status already
"reconnecting") would never show the banner — the whole retry cycle maps
to the same 3-state value. The status→banner dispatch is extracted to
ServerBanner.applyConnectionStatus and unit-tested.
- History-fetch failure is no longer silent when the channel already has
rows (live broadcasts / optimistic sends): the inline error region only
renders in an empty channel, so loadMessages now also raises a toast in
that case.
- Composer disable reason distinguishes "Reconnecting…" from
"Not connected" per the spec §3 table (it previously showed
"Reconnecting…" while disconnected, contradicting the banner).
- The single-writer wiring is extracted to
dispatcher.wireConnectionStatus(ws) and pinned by a test (it was
previously an untestable main.ts module-scope line — deleting it would
have failed zero tests).
- Docs honesty: messaging.md's transport-drop diagram arm now shows both
codes (channel full → NETWORK, closed/not-open → OFFLINE) instead of
claiming NETWORK for both; README §3's callout now explicitly lists the
voice column ("frozen" during reconnect) as a remaining gap instead of
implying the section is fully closed; the composer table documents both
offline reasons.
- New pinning tests: SidebarArea passes ws to UserBar (the production-bug
fix was previously unasserted), ServerBanner.showDisconnected,
applyConnectionStatus mapping, ChannelController onRetryLoad /
onRetry-resend / onDeleteDraft, composer reason per status, and the
history-failure toast fallback.
Verified: tsc + full client unit suite (3234 tests) + oxlint/eslint +
prettier all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { createServerBanner, applyConnectionStatus } from "@components/ServerBanner";
|
|
|
|
describe("ServerBanner", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("creates element with reconnecting-banner class", () => {
|
|
const banner = createServerBanner();
|
|
expect(banner.element.classList.contains("reconnecting-banner")).toBe(true);
|
|
banner.destroy();
|
|
});
|
|
|
|
it("showRestart adds visible class and shows countdown text", () => {
|
|
const banner = createServerBanner();
|
|
banner.showRestart(5);
|
|
|
|
expect(banner.element.classList.contains("visible")).toBe(true);
|
|
expect(banner.element.textContent).toBe("Server restarting in 5 seconds...");
|
|
|
|
banner.destroy();
|
|
});
|
|
|
|
it('showReconnecting adds visible class with "Reconnecting..." text', () => {
|
|
const banner = createServerBanner();
|
|
banner.showReconnecting();
|
|
|
|
expect(banner.element.classList.contains("visible")).toBe(true);
|
|
expect(banner.element.textContent).toBe("Reconnecting...");
|
|
|
|
banner.destroy();
|
|
});
|
|
|
|
it("hide removes visible class", () => {
|
|
const banner = createServerBanner();
|
|
banner.showReconnecting();
|
|
expect(banner.element.classList.contains("visible")).toBe(true);
|
|
|
|
banner.hide();
|
|
expect(banner.element.classList.contains("visible")).toBe(false);
|
|
|
|
banner.destroy();
|
|
});
|
|
|
|
it('showDisconnected adds visible class with "Disconnected" text', () => {
|
|
const banner = createServerBanner();
|
|
banner.showDisconnected();
|
|
|
|
expect(banner.element.classList.contains("visible")).toBe(true);
|
|
expect(banner.element.textContent).toBe("Disconnected");
|
|
|
|
banner.destroy();
|
|
});
|
|
|
|
it("applyConnectionStatus maps each store status to the right banner state", () => {
|
|
const banner = createServerBanner();
|
|
|
|
applyConnectionStatus(banner, "reconnecting");
|
|
expect(banner.element.classList.contains("visible")).toBe(true);
|
|
expect(banner.element.textContent).toBe("Reconnecting...");
|
|
|
|
applyConnectionStatus(banner, "disconnected");
|
|
expect(banner.element.classList.contains("visible")).toBe(true);
|
|
expect(banner.element.textContent).toBe("Disconnected");
|
|
|
|
applyConnectionStatus(banner, "connected");
|
|
expect(banner.element.classList.contains("visible")).toBe(false);
|
|
|
|
banner.destroy();
|
|
});
|
|
|
|
it("countdown decrements every second", () => {
|
|
const banner = createServerBanner();
|
|
banner.showRestart(3);
|
|
|
|
expect(banner.element.textContent).toBe("Server restarting in 3 seconds...");
|
|
|
|
vi.advanceTimersByTime(1000);
|
|
expect(banner.element.textContent).toBe("Server restarting in 2 seconds...");
|
|
|
|
vi.advanceTimersByTime(1000);
|
|
expect(banner.element.textContent).toBe("Server restarting in 1 seconds...");
|
|
|
|
banner.destroy();
|
|
});
|
|
|
|
it('countdown transitions to "Reconnecting..." at 0', () => {
|
|
const banner = createServerBanner();
|
|
banner.showRestart(2);
|
|
|
|
vi.advanceTimersByTime(1000); // remaining = 1
|
|
vi.advanceTimersByTime(1000); // remaining = 0 → showReconnecting
|
|
|
|
expect(banner.element.textContent).toBe("Reconnecting...");
|
|
|
|
banner.destroy();
|
|
});
|
|
|
|
it("destroy removes element from DOM", () => {
|
|
const banner = createServerBanner();
|
|
const parent = document.createElement("div");
|
|
parent.appendChild(banner.element);
|
|
|
|
expect(parent.contains(banner.element)).toBe(true);
|
|
|
|
banner.destroy();
|
|
expect(parent.contains(banner.element)).toBe(false);
|
|
});
|
|
});
|