476 lines
20 KiB
JavaScript
476 lines
20 KiB
JavaScript
import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
import { flushPromises, mount } from "@vue/test-utils";
|
|
import { computed, defineComponent, nextTick, ref } from "vue";
|
|
|
|
import { auth } from "../../../frontend/src/auth.js";
|
|
import { parseManifest } from "../../../frontend/src/onboarding/onboardingGuides.js";
|
|
import { statusLabel, statusPillClass, taskPillClass } from "../../../frontend/src/onboarding/onboardingLabels.js";
|
|
import { SECTION_DEFS, STEP_PREREQS, VAULTWARDEN_TEMP_STEP } from "../../../frontend/src/onboarding/onboardingSections.js";
|
|
import { useOnboardingFlow } from "../../../frontend/src/onboarding/useOnboardingFlow.js";
|
|
import { useOnboardingGuides } from "../../../frontend/src/onboarding/useOnboardingGuides.js";
|
|
import { useOnboardingNavigation } from "../../../frontend/src/onboarding/useOnboardingNavigation.js";
|
|
import OnboardingView from "../../../frontend/src/views/OnboardingView.vue";
|
|
|
|
let mockRoute = { query: {} };
|
|
|
|
jest.mock("vue-router", () => ({
|
|
useRoute: () => mockRoute,
|
|
}), { virtual: true });
|
|
|
|
function jsonResponse(body, status = 200, statusText = "OK") {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
statusText,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
}
|
|
|
|
function manifestResponse() {
|
|
return jsonResponse({
|
|
files: [
|
|
"vaultwarden/step1_website/default/001-login.png",
|
|
"vaultwarden/step1_website/default/002-create-vault.png",
|
|
"vaultwarden/step1_website/mobile/step-03-phone.webp",
|
|
"/element/step1_web_access/001-start.jpg",
|
|
"bad",
|
|
7,
|
|
],
|
|
});
|
|
}
|
|
|
|
function readyStatus(overrides = {}) {
|
|
return {
|
|
status: "awaiting_onboarding",
|
|
username: "ada",
|
|
initial_password: "temp-pass",
|
|
initial_password_revealed_at: "",
|
|
blocked: false,
|
|
tasks: [{ task: "keycloak", status: "ok" }],
|
|
onboarding: {
|
|
required_steps: [
|
|
"vaultwarden_master_password",
|
|
"keycloak_password_rotated",
|
|
"element_recovery_key",
|
|
"nextcloud_mail_integration",
|
|
"firefly_password_rotated",
|
|
"wger_password_rotated",
|
|
],
|
|
optional_steps: ["vaultwarden_browser_extension", "mail_client_setup"],
|
|
completed_steps: ["vaultwarden_master_password"],
|
|
vaultwarden: { matched: true, recovery_email: "ada@example.dev" },
|
|
keycloak: { password_rotation_requested: false },
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function installFetch(handler) {
|
|
global.fetch = jest.fn(async (url, options = {}) => handler(String(url), options));
|
|
}
|
|
|
|
function mountFlow(route = { query: {} }) {
|
|
let flow;
|
|
const wrapper = mount(defineComponent({
|
|
setup() {
|
|
flow = useOnboardingFlow(route);
|
|
return {};
|
|
},
|
|
template: "<div />",
|
|
}));
|
|
return { flow, wrapper };
|
|
}
|
|
|
|
describe("onboarding helpers and flow", () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
mockRoute = { query: {} };
|
|
auth.authenticated = false;
|
|
auth.token = "";
|
|
Object.defineProperty(navigator, "clipboard", {
|
|
configurable: true,
|
|
value: { writeText: jest.fn(async () => {}) },
|
|
});
|
|
installFetch((url) => (url.includes("/media/onboarding") ? manifestResponse() : jsonResponse(readyStatus())));
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
jest.restoreAllMocks();
|
|
auth.authenticated = false;
|
|
auth.token = "";
|
|
Reflect.deleteProperty(global, "fetch");
|
|
});
|
|
|
|
it("maps labels, static sections, and manifest files", () => {
|
|
expect(statusLabel("pending_email_verification")).toBe("confirm email");
|
|
expect(statusLabel("pending")).toBe("awaiting approval");
|
|
expect(statusLabel("accounts_building")).toBe("accounts building");
|
|
expect(statusLabel("awaiting_onboarding")).toBe("awaiting onboarding");
|
|
expect(statusLabel("ready")).toBe("ready");
|
|
expect(statusLabel("denied")).toBe("rejected");
|
|
expect(statusLabel("")).toBe("unknown");
|
|
expect(statusPillClass("denied")).toBe("pill-bad");
|
|
expect(statusPillClass("ready")).toBe("pill-info");
|
|
expect(statusPillClass("other")).toBe("pill-warn");
|
|
expect(taskPillClass("ok")).toBe("pill-ok");
|
|
expect(taskPillClass("error")).toBe("pill-bad");
|
|
expect(taskPillClass("pending")).toBe("pill-warn");
|
|
|
|
expect(STEP_PREREQS.element_recovery_key).toEqual(["keycloak_password_rotated"]);
|
|
expect(SECTION_DEFS.map((section) => section.id)).toContain("vaultwarden");
|
|
expect(VAULTWARDEN_TEMP_STEP.id).toBe("vaultwarden_store_temp_password");
|
|
|
|
const parsed = parseManifest([
|
|
"vaultwarden\\step1_website\\default\\002-second.png",
|
|
"vaultwarden/step1_website/default/001-first.png",
|
|
"vaultwarden/step1_website/default/final-shot.png",
|
|
"vaultwarden/step1_website/mobile/step-03-phone.webp",
|
|
"ignored",
|
|
null,
|
|
]);
|
|
expect(parsed.vaultwarden.step1_website.default.shots.map((shot) => shot.label)).toEqual(["first", "second", "final shot"]);
|
|
expect(parsed.vaultwarden.step1_website.mobile.shots[0].order).toBe(3);
|
|
});
|
|
|
|
it("paginates guide groups and handles lightbox state", async () => {
|
|
const done = new Set(["vaultwarden_master_password"]);
|
|
const guides = useOnboardingGuides({
|
|
isStepDone: (id) => done.has(id),
|
|
isStepBlocked: (id) => id === "blocked",
|
|
});
|
|
await guides.loadGuideShots();
|
|
|
|
const step = { id: "vaultwarden_browser_extension", guide: { service: "vaultwarden", step: "step1_website", take: 1 } };
|
|
const tailStep = { id: "vaultwarden_mobile_app", guide: { service: "vaultwarden", step: "step1_website", tail: 1 } };
|
|
const section = { steps: [{ id: "vaultwarden_master_password", guide: step.guide }, step, { id: "blocked", guide: step.guide }] };
|
|
|
|
expect(guides.guideGroups(step)[0].shots).toHaveLength(1);
|
|
expect(guides.guideGroups(tailStep)[0].shots[0].label).toBe("create vault");
|
|
const fullStep = { id: "vaultwarden_browser_extension", guide: { service: "vaultwarden", step: "step1_website" } };
|
|
const fullGroup = guides.guideGroups(fullStep)[0];
|
|
guides.guideNext(fullStep, fullGroup);
|
|
expect(guides.guideIndex(fullStep, fullGroup)).toBe(1);
|
|
guides.guidePrev(fullStep, fullGroup);
|
|
expect(guides.guideIndex(fullStep, fullGroup)).toBe(0);
|
|
expect(guides.shouldOpenGuide(step, section)).toBe(true);
|
|
guides.guideSet(step, guides.guideGroups(step)[0], 12);
|
|
expect(guides.guideIndex(step, guides.guideGroups(step)[0])).toBe(0);
|
|
guides.openLightbox(guides.guideShot(step, guides.guideGroups(step)[0]));
|
|
expect(guides.lightboxShot.value.url).toContain("001-login");
|
|
guides.closeLightbox();
|
|
expect(guides.lightboxShot.value).toBeNull();
|
|
|
|
global.fetch.mockRejectedValueOnce(new Error("offline"));
|
|
await guides.loadGuideShots();
|
|
expect(guides.guideShots.value).toEqual({});
|
|
});
|
|
|
|
it("navigates onboarding sections with lock and progress rules", () => {
|
|
const activeSectionId = ref("first");
|
|
const completed = ref(["a"]);
|
|
const sections = computed(() => [
|
|
{ id: "first", steps: [{ id: "a" }] },
|
|
{ id: "second", steps: [{ id: "b" }] },
|
|
{ id: "optional", steps: [{ id: "c" }] },
|
|
]);
|
|
const nav = useOnboardingNavigation({
|
|
sections,
|
|
activeSectionId,
|
|
isStepDone: (id) => completed.value.includes(id),
|
|
isStepRequired: (id) => id !== "c",
|
|
isStepBlocked: (id) => id === "blocked",
|
|
});
|
|
|
|
expect(nav.sectionProgress(sections.value[0])).toBe("1/1 done");
|
|
expect(nav.sectionStatusLabel(sections.value[1])).toBe("active");
|
|
nav.nextSection();
|
|
expect(activeSectionId.value).toBe("second");
|
|
expect(nav.hasPrevSection.value).toBe(true);
|
|
expect(nav.stepCardClass({ id: "c" }).optional).toBe(true);
|
|
expect(nav.sectionProgress(sections.value[2])).toBe("optional");
|
|
nav.selectSection("missing");
|
|
expect(activeSectionId.value).toBe("second");
|
|
nav.prevSection();
|
|
expect(activeSectionId.value).toBe("first");
|
|
completed.value = [];
|
|
nav.selectSection("second");
|
|
expect(activeSectionId.value).toBe("first");
|
|
});
|
|
|
|
it("checks status, copies credentials, retries, and confirms steps", async () => {
|
|
installFetch((url) => {
|
|
if (url.includes("/media/onboarding")) return manifestResponse();
|
|
if (url.includes("/retry")) return jsonResponse({ queued: true });
|
|
if (url.includes("/onboarding/attest")) {
|
|
return jsonResponse(readyStatus({
|
|
onboarding: { ...readyStatus().onboarding, completed_steps: ["vaultwarden_master_password", "mail_client_setup"] },
|
|
}));
|
|
}
|
|
if (url.includes("/keycloak-password-rotate")) {
|
|
return jsonResponse(readyStatus({
|
|
onboarding: { ...readyStatus().onboarding, keycloak: { password_rotation_requested: true } },
|
|
}));
|
|
}
|
|
return jsonResponse(readyStatus({ tasks: [{ task: "mail", status: "error" }] }));
|
|
});
|
|
const { flow, wrapper } = mountFlow();
|
|
flow.requestCode.value = "ada~abc";
|
|
await flow.check();
|
|
|
|
expect(flow.status.value).toBe("awaiting_onboarding");
|
|
expect(flow.requestUsername.value).toBe("ada");
|
|
expect(flow.showPasswordCard.value).toBe(true);
|
|
expect(flow.vaultwardenLoginEmailLower.value).toBe("ada@example.dev");
|
|
expect(flow.mailAddressLower.value).toBe("ada@bstein.dev");
|
|
expect(flow.stepNote({ id: "firefly_password_rotated" })).toContain("ada@bstein.dev");
|
|
expect(flow.stepPillLabel({ id: "keycloak_password_rotated", action: "confirm" })).toBe("ready");
|
|
expect(flow.stepPillClass({ id: "vaultwarden_browser_extension" })).toBe("pill-info");
|
|
|
|
flow.togglePassword();
|
|
expect(flow.revealPassword.value).toBe(true);
|
|
await flow.copyInitialPassword();
|
|
await flow.copyUsername();
|
|
expect(navigator.clipboard.writeText).toHaveBeenCalledWith("temp-pass");
|
|
expect(navigator.clipboard.writeText).toHaveBeenCalledWith("ada");
|
|
jest.advanceTimersByTime(1500);
|
|
expect(flow.passwordCopied.value).toBe(false);
|
|
|
|
await flow.retryProvisioning();
|
|
expect(flow.retryMessage.value).toBe("Retry requested. Check again in a moment.");
|
|
|
|
await flow.setStepCompletion("mail_client_setup", true);
|
|
expect(flow.isStepDone("mail_client_setup")).toBe(true);
|
|
|
|
await flow.requestKeycloakPasswordRotation();
|
|
expect(flow.keycloakPasswordRotationRequested.value).toBe(true);
|
|
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it("handles auth fallback, rotation checks, blocked steps, and failures", async () => {
|
|
auth.authenticated = true;
|
|
auth.token = "token";
|
|
installFetch((url) => {
|
|
if (url.includes("/media/onboarding")) return manifestResponse();
|
|
if (url.includes("/rotation/check")) return jsonResponse({ rotated: false });
|
|
if (url.includes("/onboarding/attest")) return jsonResponse({ error: "forbidden" }, 403, "Forbidden");
|
|
if (url.includes("/keycloak-password-rotate")) return jsonResponse({ error: "blocked" }, 409, "Conflict");
|
|
if (url.includes("/status")) return jsonResponse(readyStatus({
|
|
onboarding: { ...readyStatus().onboarding, completed_steps: [] },
|
|
}));
|
|
return jsonResponse({});
|
|
});
|
|
|
|
const { flow, wrapper } = mountFlow();
|
|
flow.requestCode.value = "ada~abc";
|
|
await flow.check();
|
|
|
|
await flow.setStepCompletion("element_recovery_key", true);
|
|
expect(flow.error.value).toBe("");
|
|
await flow.setStepCompletion("vaultwarden_master_password", true);
|
|
expect(flow.error.value).toBe("forbidden");
|
|
|
|
flow.error.value = "";
|
|
flow.onboarding.value.completed_steps = ["vaultwarden_master_password", "keycloak_password_rotated", "element_recovery_key"];
|
|
await flow.confirmStep({ id: "firefly_password_rotated", action: "auto" });
|
|
expect(flow.error.value).toContain("Firefly still uses");
|
|
|
|
await expect(flow.runRotationCheck("wger")).resolves.toEqual({ rotated: false });
|
|
|
|
flow.keycloakPasswordRotationRequested.value = false;
|
|
await flow.requestKeycloakPasswordRotation();
|
|
expect(flow.error.value).toBe("blocked");
|
|
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it("covers onboarding edge branches without hiding workflow failures", async () => {
|
|
const responses = { rotateCalls: 0 };
|
|
installFetch((url) => {
|
|
if (url.includes("/media/onboarding")) return manifestResponse();
|
|
if (url.includes("/rotation/check")) return jsonResponse({ rotated: true });
|
|
if (url.includes("/onboarding/attest")) return jsonResponse(readyStatus({
|
|
onboarding: { ...readyStatus().onboarding, completed_steps: ["vaultwarden_master_password", "mail_client_setup"] },
|
|
}));
|
|
if (url.includes("/keycloak-password-rotate")) {
|
|
responses.rotateCalls += 1;
|
|
if (responses.rotateCalls === 1) return jsonResponse({ error: "auth denied" }, 403, "Forbidden");
|
|
return jsonResponse(readyStatus({
|
|
onboarding: { ...readyStatus().onboarding, keycloak: { password_rotation_requested: true } },
|
|
}));
|
|
}
|
|
if (url.includes("/status")) return jsonResponse(readyStatus({
|
|
initial_password: "",
|
|
initial_password_revealed_at: "2026-04-21T00:00:00Z",
|
|
onboarding: {
|
|
...readyStatus().onboarding,
|
|
completed_steps: ["vaultwarden_master_password"],
|
|
vaultwarden: { matched: true, recovery_email: "" },
|
|
},
|
|
}));
|
|
return jsonResponse({});
|
|
});
|
|
|
|
const { flow, wrapper } = mountFlow();
|
|
flow.loading.value = true;
|
|
await flow.check();
|
|
expect(fetch).not.toHaveBeenCalledWith("/api/access/request/status", expect.anything());
|
|
flow.loading.value = false;
|
|
|
|
await flow.setStepCompletion("vaultwarden_master_password", true);
|
|
expect(flow.error.value).toBe("Request code is missing.");
|
|
await flow.retryProvisioning();
|
|
expect(flow.retryMessage.value).toBe("");
|
|
|
|
flow.requestCode.value = "ada~abc";
|
|
await flow.check();
|
|
expect(flow.passwordRevealLocked.value).toBe(true);
|
|
expect(flow.passwordRevealHint.value).toContain("already revealed");
|
|
expect(flow.vaultwardenLoginEmailLower.value).toBe("your recovery email");
|
|
|
|
flow.onboarding.value = {
|
|
required_steps: ["vaultwarden_master_password", "keycloak_password_rotated", "manual_required"],
|
|
optional_steps: ["optional_step"],
|
|
completed_steps: [],
|
|
vaultwarden: { matched: false },
|
|
};
|
|
flow.requestUsername.value = "";
|
|
expect(flow.vaultwardenLoginEmail.value).toBe("your @bstein.dev address");
|
|
flow.requestUsername.value = "Ada";
|
|
expect(flow.vaultwardenLoginEmailLower.value).toBe("ada@bstein.dev");
|
|
expect(flow.stepNote({ id: "vaultwarden_master_password" })).toContain("ada@bstein.dev");
|
|
expect(flow.stepNote({ id: "vaultwarden_store_temp_password" })).toContain("temporary Keycloak");
|
|
expect(flow.stepNote({ id: "mail_client_setup" })).toContain("ada@bstein.dev");
|
|
expect(flow.stepNote({ id: "unknown" })).toBe("");
|
|
expect(flow.stepPillLabel({ id: "element_recovery_key", action: "confirm" })).toBe("blocked");
|
|
expect(flow.stepPillLabel({ id: "auto_step", action: "auto" })).toBe("pending");
|
|
expect(flow.stepPillLabel({ id: "optional_step", action: "checkbox" })).toBe("optional");
|
|
expect(flow.stepPillLabel({ id: "manual_required", action: "checkbox" })).toBe("pending");
|
|
expect(flow.stepPillClass({ id: "element_recovery_key" })).toBe("pill-wait");
|
|
flow.onboarding.value.completed_steps = ["vaultwarden_master_password"];
|
|
expect(flow.stepPillClass({ id: "keycloak_password_rotated" })).toBe("pill-info");
|
|
flow.keycloakPasswordRotationRequested.value = true;
|
|
expect(flow.stepPillLabel({ id: "keycloak_password_rotated", action: "confirm" })).toBe("rotate now");
|
|
expect(flow.stepPillClass({ id: "keycloak_password_rotated" })).toBe("pill-warn");
|
|
|
|
flow.confirmingStepId.value = "manual_required";
|
|
expect(flow.isConfirming({ id: "manual_required" })).toBe(true);
|
|
expect(flow.confirmLabel({ id: "manual_required" })).toBe("Confirming...");
|
|
flow.confirmingStepId.value = "";
|
|
|
|
Object.defineProperty(navigator, "clipboard", {
|
|
configurable: true,
|
|
value: { writeText: jest.fn(async () => { throw new Error("copy denied"); }) },
|
|
});
|
|
await flow.copyText("secret", () => {});
|
|
expect(flow.error.value).toBe("copy denied");
|
|
await flow.copyText("", () => { throw new Error("should not run"); });
|
|
|
|
await expect(flow.runRotationCheck("firefly")).rejects.toThrow("Log in");
|
|
auth.authenticated = true;
|
|
auth.token = "token";
|
|
await expect(flow.runRotationCheck("wger")).resolves.toEqual({ rotated: true });
|
|
global.fetch.mockResolvedValueOnce(jsonResponse({ error: "rotation down" }, 500, "Server Error"));
|
|
await expect(flow.runRotationCheck("wger")).rejects.toThrow("rotation down");
|
|
|
|
flow.onboarding.value.completed_steps = ["vaultwarden_master_password"];
|
|
flow.keycloakPasswordRotationRequested.value = false;
|
|
await flow.requestKeycloakPasswordRotation();
|
|
expect(flow.keycloakPasswordRotationRequested.value).toBe(true);
|
|
await flow.requestKeycloakPasswordRotation();
|
|
expect(responses.rotateCalls).toBe(2);
|
|
|
|
await flow.confirmStep(null);
|
|
await flow.confirmStep({ id: "vaultwarden_master_password", action: "confirm" });
|
|
await flow.confirmStep({ id: "keycloak_password_rotated", action: "confirm" });
|
|
flow.onboarding.value.completed_steps = ["vaultwarden_master_password", "keycloak_password_rotated", "element_recovery_key", "firefly_password_rotated"];
|
|
await flow.confirmStep({ id: "wger_password_rotated", action: "auto" });
|
|
await flow.confirmStep({ id: "mail_client_setup", action: "confirm" });
|
|
await flow.confirmStep({ id: "jellyfin_web_access", action: "checkbox" });
|
|
await flow.toggleStep("mail_client_setup", {});
|
|
|
|
flow.requestCode.value = "";
|
|
await flow.requestKeycloakPasswordRotation();
|
|
expect(flow.error.value).toBe("Request code is missing.");
|
|
flow.requestCode.value = "ada~abc";
|
|
flow.onboarding.value.completed_steps = [];
|
|
flow.keycloakPasswordRotationRequested.value = false;
|
|
await flow.requestKeycloakPasswordRotation();
|
|
expect(flow.error.value).toBe("Complete earlier onboarding steps first.");
|
|
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it("renders onboarding page states and guide interactions", async () => {
|
|
mockRoute = { query: { code: "ada~abc" } };
|
|
installFetch((url) => {
|
|
if (url.includes("/media/onboarding")) return manifestResponse();
|
|
if (url.includes("/status")) return jsonResponse(readyStatus());
|
|
if (url.includes("/onboarding/attest")) return jsonResponse(readyStatus({
|
|
onboarding: { ...readyStatus().onboarding, completed_steps: ["vaultwarden_master_password", "vaultwarden_browser_extension"] },
|
|
}));
|
|
return jsonResponse({});
|
|
});
|
|
|
|
const wrapper = mount(OnboardingView);
|
|
await flushPromises();
|
|
|
|
expect(wrapper.text()).toContain("Onboarding");
|
|
expect(wrapper.text()).toContain("Keycloak temporary credentials");
|
|
expect(wrapper.find("input[readonly]").element.value).toBe("ada");
|
|
expect(wrapper.text()).toContain("Photo guide");
|
|
|
|
const revealButton = wrapper.findAll("button.secondary").find((button) => button.text() === "Reveal");
|
|
await revealButton.trigger("click");
|
|
await nextTick();
|
|
expect(wrapper.text()).toContain("Hide");
|
|
|
|
const checkbox = wrapper.find("input[type='checkbox']");
|
|
if (checkbox.exists()) {
|
|
await checkbox.setValue(true);
|
|
await flushPromises();
|
|
}
|
|
|
|
const img = wrapper.find(".guide-shot");
|
|
if (img.exists()) {
|
|
await img.trigger("click");
|
|
expect(wrapper.find(".lightbox").exists()).toBe(true);
|
|
await wrapper.find(".lightbox button").trigger("click");
|
|
expect(wrapper.find(".lightbox").exists()).toBe(false);
|
|
}
|
|
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it("renders non-onboarding statuses and copy fallback", async () => {
|
|
Object.defineProperty(navigator, "clipboard", { configurable: true, value: undefined });
|
|
Object.defineProperty(document, "execCommand", { configurable: true, value: jest.fn(() => true) });
|
|
installFetch((url) => {
|
|
if (url.includes("/media/onboarding")) return manifestResponse();
|
|
if (url.includes("/status")) return jsonResponse(readyStatus({
|
|
status: "accounts_building",
|
|
blocked: true,
|
|
tasks: [{ task: "mail", status: "error", detail: "smtp" }],
|
|
}));
|
|
if (url.includes("/retry")) return jsonResponse({ error: "retry failed" }, 500, "Server Error");
|
|
return jsonResponse({});
|
|
});
|
|
|
|
const { flow, wrapper } = mountFlow({ query: { request_code: "ada~abc" } });
|
|
await flushPromises();
|
|
expect(flow.blocked.value).toBe(true);
|
|
|
|
await flow.copyText("manual", () => {});
|
|
expect(document.execCommand).toHaveBeenCalledWith("copy");
|
|
|
|
await flow.retryProvisioning();
|
|
expect(flow.retryMessage.value).toBe("retry failed");
|
|
|
|
global.fetch.mockResolvedValueOnce(jsonResponse({ error: "status failed" }, 500, "Server Error"));
|
|
await flow.check();
|
|
expect(flow.error.value).toBe("status failed");
|
|
|
|
wrapper.unmount();
|
|
});
|
|
});
|