mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
* fix(client): 3 defect(s) (OC-0037, OC-0063, OC-0116) Route the tray Status submenu through saveUserStatus() (mapping the legacy "offline" to "invisible") so notifications, autoIdle, and reconnect presence restore all agree with the tray's choice; build the connected overlay from the auth_ok payload instead of a pre-dispatch authStore snapshot; keep the TOTP overlay open across a rejected verify (totpPending latch) and retain the partial token for the retry instead of clearing it in finally. Hand-applied combined cluster preserved from the previous fix run's overlap-guard block (both clusters edit main.ts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(voice): 2 defect(s) (OC-0010, OC-0011) * fix(ws): 1 defect(s) (OC-0050) * fix(db): 1 defect(s) (OC-0052) * fix(client): 1 defect(s) (OC-0054) * fix(client): 1 defect(s) (OC-0059) * fix(auth): 1 defect(s) (OC-0061) * fix(ws): 1 defect(s) (OC-0062) * fix(client): 1 defect(s) (OC-0064) * fix(service): 1 defect(s) (OC-0070) * fix(ws): 1 defect(s) (OC-0073) * fix(service): 2 defect(s) (OC-0075, OC-0120) * fix(admin): 1 defect(s) (OC-0076) * fix(voice): 1 defect(s) (OC-0084) * fix(client): 2 defect(s) (OC-0085, OC-0094) Scope collapsed-category persistence to the connected host instead of the server display name, and stop the DM back button from jumping to the first text channel when DM mode was entered without recording channelBeforeDm. * fix(service): 1 defect(s) (OC-0087) * fix(client): 1 defect(s) (OC-0089) * fix(ws): 1 defect(s) (OC-0091) * fix(api): 1 defect(s) (OC-0093) * fix(identity): 1 defect(s) (OC-0118) * fix(dm): 1 defect(s) (OC-0119) * fix(voice): 1 defect(s) (OC-0135) * fix(api): 1 defect(s) (OC-0137) * fix(client): 1 defect(s) (OC-0142) * fix(client): 1 defect(s) (OC-0144) * fix(admin): 1 defect(s) (OC-0145) * fix(updater): 1 defect(s) (OC-0146) * fix(client): 1 defect(s) (OC-0150) * fix(mentions): 1 defect(s) (OC-0131) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
// Regression test for OC-0116: a rejected TOTP verify tore down the TOTP
|
|
// overlay (transitionTo("error", ...) leaves formState "totp", and
|
|
// updateTotpOverlay hides the overlay for every non-"totp" state), so the
|
|
// user was dropped back on the login form with no way to re-enter the code.
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { createConnectPage } from "../../src/pages/ConnectPage";
|
|
import type { ConnectPageCallbacks, SimpleProfile } from "../../src/pages/ConnectPage";
|
|
|
|
vi.mock("../../src/lib/credentials", () => ({
|
|
loadCredential: vi.fn().mockResolvedValue(null),
|
|
}));
|
|
|
|
vi.mock("../../src/components/SettingsOverlay", () => ({
|
|
createSettingsOverlay: () => ({
|
|
mount: vi.fn(),
|
|
destroy: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
function makeCallbacks(overrides: Partial<ConnectPageCallbacks> = {}): ConnectPageCallbacks {
|
|
return {
|
|
onLogin: vi.fn().mockResolvedValue(undefined),
|
|
onRegister: vi.fn().mockResolvedValue(undefined),
|
|
onTotpSubmit: vi.fn().mockResolvedValue(undefined),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
const testProfiles: SimpleProfile[] = [{ name: "Test Server", host: "localhost:8443" }];
|
|
|
|
describe("LoginForm TOTP retry after a rejected verify", () => {
|
|
let container: HTMLDivElement;
|
|
|
|
beforeEach(() => {
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
});
|
|
|
|
afterEach(() => {
|
|
container.remove();
|
|
});
|
|
|
|
it("keeps the TOTP overlay open and lets a second code be submitted", async () => {
|
|
const onTotpSubmit = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error("Invalid verification code"))
|
|
.mockResolvedValueOnce(undefined);
|
|
const page = createConnectPage(makeCallbacks({ onTotpSubmit }), testProfiles);
|
|
page.mount(container);
|
|
page.showTotp();
|
|
|
|
const totpOverlay = container.querySelector(".totp-overlay") as HTMLDivElement;
|
|
const totpInput = container.querySelector(".totp-overlay input") as HTMLInputElement;
|
|
const verifyBtn = container.querySelector(".totp-overlay .btn-primary") as HTMLButtonElement;
|
|
|
|
totpInput.value = "111111";
|
|
verifyBtn.click();
|
|
|
|
await vi.waitFor(() => expect(onTotpSubmit).toHaveBeenCalledTimes(1));
|
|
// Verify button re-enables once the rejected promise settles.
|
|
await vi.waitFor(() => expect(verifyBtn.disabled).toBe(false));
|
|
|
|
// The overlay must stay up so the code can be re-entered, instead of
|
|
// being hidden because formState moved to "error".
|
|
expect(totpOverlay.classList.contains("totp-overlay--hidden")).toBe(false);
|
|
|
|
// A retry with a fresh code must actually reach onTotpSubmit again.
|
|
totpInput.value = "222222";
|
|
verifyBtn.click();
|
|
|
|
await vi.waitFor(() => expect(onTotpSubmit).toHaveBeenCalledTimes(2));
|
|
expect(onTotpSubmit).toHaveBeenNthCalledWith(2, "222222");
|
|
|
|
page.destroy?.();
|
|
});
|
|
});
|