mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 12:35:35 +02:00
* feat(onboarding): simplify runtime bootstrap * fix(onboarding): close private-helper reuse hole and guide-issue nav race - server: when bootstrap looks for an existing Multica Helper, require Visibility="workspace" so a private helper owned by another member can't be auto-assigned to the onboarding issue (and trigger a task as that private agent), which would have bypassed canAccessPrivateAgent. - web onboarding page: refreshMe() inside bootstrap flips hasOnboarded before onComplete fires, letting the guard's router.replace overtake onComplete's router.push to the new guide issue. Mark the page as "completing" right before navigating so the guard stays silent during the in-flight transition. Co-authored-by: multica-agent <github@multica.ai> * fix(runtimes): escape daemon command literals to satisfy i18next/no-literal-string Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: Lambda <lambda@multica.ai>
58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { describe, expect, it } from "vitest";
|
|
import { render as rtlRender, screen, type RenderOptions } from "@testing-library/react";
|
|
import { ONBOARDING_STEP_ORDER } from "@multica/core/onboarding";
|
|
import { I18nProvider } from "@multica/core/i18n/react";
|
|
import enCommon from "../../locales/en/common.json";
|
|
import enOnboarding from "../../locales/en/onboarding.json";
|
|
import { StepHeader } from "./step-header";
|
|
|
|
const TEST_RESOURCES = {
|
|
en: { common: enCommon, onboarding: enOnboarding },
|
|
};
|
|
|
|
function I18nWrapper({ children }: { children: ReactNode }) {
|
|
return (
|
|
<I18nProvider locale="en" resources={TEST_RESOURCES}>
|
|
{children}
|
|
</I18nProvider>
|
|
);
|
|
}
|
|
|
|
function render(ui: React.ReactElement, options?: RenderOptions) {
|
|
return rtlRender(ui, { wrapper: I18nWrapper, ...options });
|
|
}
|
|
|
|
describe("StepHeader", () => {
|
|
it("renders one dot per step in ONBOARDING_STEP_ORDER", () => {
|
|
const { container } = render(<StepHeader currentStep="source" />);
|
|
const dots = container.querySelectorAll('[aria-hidden="true"]');
|
|
expect(dots).toHaveLength(ONBOARDING_STEP_ORDER.length);
|
|
});
|
|
|
|
it("shows 'Step N of M' text matching the current step's position", () => {
|
|
// workspace is index 3 (after source/role/use_case) → Step 4.
|
|
render(<StepHeader currentStep="workspace" />);
|
|
expect(
|
|
screen.getByText(`Step 4 of ${ONBOARDING_STEP_ORDER.length}`),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("sets accessible progressbar attrs", () => {
|
|
render(<StepHeader currentStep="runtime" />);
|
|
const bar = screen.getByRole("progressbar");
|
|
expect(bar).toHaveAttribute("aria-valuenow", "5"); // runtime is index 4 → step 5
|
|
expect(bar).toHaveAttribute("aria-valuemax", String(ONBOARDING_STEP_ORDER.length));
|
|
});
|
|
|
|
it("falls back to step 1 when given an unknown step", () => {
|
|
// TS would normally prevent this, but at runtime the store enum and
|
|
// the flow's local step could drift during a refactor — the header
|
|
// must not crash. Assert the defensive fallback lands on step 1.
|
|
render(<StepHeader currentStep={"bogus" as never} />);
|
|
expect(
|
|
screen.getByText(`Step 1 of ${ONBOARDING_STEP_ORDER.length}`),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|