feat(FN-2403): add project setup step to onboarding flow

- Introduce a new "project-setup" onboarding step between GitHub and first-task actions
- Gate first-task navigation on project selection and add contextual setup-wizard guidance
- Centralize ordered onboarding step definitions in shared state and reuse them for navigation/progress labels
- Extract ModelOnboardingModal styles into a dedicated component CSS file and update tests/mocks for the new step order
This commit is contained in:
Fusion
2026-04-25 06:35:52 -07:00
committed by gsxdsm
parent dbc9446a32
commit 81193e734a
13 changed files with 1508 additions and 1341 deletions

View File

@@ -60,6 +60,7 @@ vi.mock("../model-onboarding-state", () => ({
markStepSkipped: (...args: unknown[]) => mockMarkStepSkipped(...args),
getSkippedSteps: (...args: unknown[]) => mockGetSkippedSteps(...args),
getStepData: (...args: unknown[]) => mockGetStepData(...args),
ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "first-task"],
}));
const mockTrackOnboardingEvent = vi.fn();
@@ -122,9 +123,17 @@ async function navigateToGitHubStep() {
});
}
async function navigateToFirstTaskStep() {
async function navigateToProjectSetupStep() {
await navigateToGitHubStep();
fireEvent.click(screen.getByText("Next →"));
await waitFor(() => {
expect(screen.getByText("Set Up Your Project")).toBeTruthy();
});
}
async function navigateToFirstTaskStep() {
await navigateToProjectSetupStep();
fireEvent.click(screen.getByText("Next →"));
await waitFor(() => {
expect(screen.getByText("Create Your First Task")).toBeTruthy();
});
@@ -1394,6 +1403,12 @@ describe("ModelOnboardingModal", () => {
fireEvent.click(screen.getByText("Continue without GitHub →"));
await waitFor(() => {
expect(screen.getByText("Set Up Your Project")).toBeTruthy();
});
fireEvent.click(screen.getByText("Next →"));
await waitFor(() => {
expect(screen.getByText("Create Your First Task")).toBeTruthy();
});
@@ -1462,10 +1477,10 @@ describe("ModelOnboardingModal", () => {
/>,
);
await navigateToFirstTaskStep();
await navigateToProjectSetupStep();
expect(screen.getByTestId("onboarding-project-prerequisite")).toBeTruthy();
expect(screen.getByText(/A project must be selected before you can create tasks or import from GitHub/)).toBeTruthy();
expect(screen.getByText(/A project is required before first-task actions are available/)).toBeTruthy();
expect(screen.queryByTestId("onboarding-first-task-input")).toBeNull();
expect(screen.queryByText("Create a New Task")).toBeNull();
expect(screen.queryByText("Import from GitHub")).toBeNull();
@@ -1482,7 +1497,7 @@ describe("ModelOnboardingModal", () => {
/>,
);
await navigateToFirstTaskStep();
await navigateToProjectSetupStep();
fireEvent.click(screen.getByTestId("onboarding-open-setup-wizard"));
expect(onOpenSetupWizard).toHaveBeenCalledTimes(1);
@@ -1975,6 +1990,12 @@ describe("ModelOnboardingModal", () => {
fireEvent.click(screen.getByText("← Back"));
await waitFor(() => {
expect(screen.getByText("Set Up Your Project")).toBeTruthy();
});
fireEvent.click(screen.getByText("← Back"));
await waitFor(() => {
expect(screen.getByText("Connect GitHub")).toBeTruthy();
});
@@ -2101,7 +2122,7 @@ describe("ModelOnboardingModal", () => {
expect(aiSetupIndicator).toHaveClass("done");
expect(githubIndicator).toHaveClass("done");
expect(firstTaskIndicator).toHaveClass("done");
expect(document.querySelectorAll(".model-onboarding-step-connector.done")).toHaveLength(2);
expect(document.querySelectorAll(".model-onboarding-step-connector.done")).toHaveLength(3);
// Click Get Started to close
fireEvent.click(screen.getByText("Get Started"));
@@ -2396,6 +2417,12 @@ describe("ModelOnboardingModal", () => {
// Navigate back to see if the model dropdown has the saved value
fireEvent.click(screen.getByText("← Back"));
await waitFor(() => {
expect(screen.getByText("Set Up Your Project")).toBeTruthy();
});
fireEvent.click(screen.getByText("← Back"));
await waitFor(() => {
expect(screen.getByText("Connect GitHub")).toBeTruthy();
});
@@ -2715,7 +2742,14 @@ describe("ModelOnboardingModal", () => {
// Click Next without connecting GitHub
fireEvent.click(screen.getByText("Next →"));
// Should advance to First Task step
// Should advance to Project Setup first
await waitFor(() => {
expect(screen.getByText("Set Up Your Project")).toBeTruthy();
});
fireEvent.click(screen.getByText("Next →"));
// Then advance to First Task step
await waitFor(() => {
expect(screen.getByText("Create Your First Task")).toBeTruthy();
});
@@ -2772,9 +2806,13 @@ describe("ModelOnboardingModal", () => {
expect(screen.getByText("Optional")).toBeTruthy();
});
// Navigate to First Task step
// Navigate through Project Setup to First Task step
fireEvent.click(screen.getByText("Next →"));
await waitFor(() => {
expect(screen.getByText("Set Up Your Project")).toBeTruthy();
});
fireEvent.click(screen.getByText("Next →"));
await waitFor(() => {
expect(screen.getByText("Create Your First Task")).toBeTruthy();
});
@@ -2815,7 +2853,14 @@ describe("ModelOnboardingModal", () => {
// Click Skip GitHub
fireEvent.click(screen.getByText("Skip GitHub →"));
// Should advance to First Task step
// Should advance to Project Setup first
await waitFor(() => {
expect(screen.getByText("Set Up Your Project")).toBeTruthy();
});
fireEvent.click(screen.getByText("Next →"));
// Then advance to First Task step
await waitFor(() => {
expect(screen.getByText("Create Your First Task")).toBeTruthy();
});