Files
multica/packages/views/onboarding/components/step-header.test.tsx
Jiayuan Zhang a61a8ecfed feat(onboarding): merge About-you step, collect source after agents deliver value (#5786)
Flow drops from five steps to three: role + use_case merge into a
single About-you screen (one Skip covers both; Continue stamps skip
markers on whichever group was left unanswered), and the source
question leaves onboarding entirely.

Source is now collected only by the workspace source-backfill prompt,
which additionally waits until agents/squads have completed at least
SOURCE_BACKFILL_MIN_AGENT_DONE_ISSUES (3) issues in the workspace —
attribution is asked after Multica has visibly delivered value, not
before. The count rides a limit:1 issues query keyed under
issueKeys.all so realtime invalidations keep it fresh, enabled only
for users who still owe an answer.

Server: questionnaire complete() narrows to role + use_case so the
funnel step doesn't stall on the now-deferred source; a new
metrics-only onboarding_source_submitted event (+ Prometheus counter)
tracks the backfill prompt's answer/decline transition once per user.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 01:57:44 +08:00

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="about_you" />);
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 1 (after about_you) → Step 2.
render(<StepHeader currentStep="workspace" />);
expect(
screen.getByText(`Step 2 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", "3"); // runtime is index 2 → step 3
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();
});
});