feat(FN-1940): add onboarding first-task success handoff
- Track the first task created from onboarding in AppModals and pass it back into the onboarding modal - Keep onboarding open after launching New Task, then switch to a task-created success state when a task is returned - Add success actions to view the created task or return to the dashboard, while preserving the no-task completion path - Style the task-created success panel and add comprehensive ModelOnboardingModal tests for transition, rendering, and button behavior
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import type { ProjectInfo } from "../api";
|
||||
import type { ColorTheme, Column, MergeResult, Task, TaskCreateInput, ThemeMode } from "@fusion/core";
|
||||
import type { UseProjectActionsResult } from "../hooks/useProjectActions";
|
||||
@@ -75,9 +76,42 @@ export function AppModals({
|
||||
onSettingsClose,
|
||||
onReopenOnboarding,
|
||||
}: AppModalsProps) {
|
||||
const [firstCreatedTask, setFirstCreatedTask] = useState<Task | null>(null);
|
||||
|
||||
// Use the override handler if provided, otherwise fall back to modalManager.closeSettings
|
||||
const handleSettingsClose = onSettingsClose ?? modalManager.closeSettings;
|
||||
|
||||
const handleOpenNewTask = useCallback(() => {
|
||||
modalManager.openNewTask();
|
||||
}, [modalManager]);
|
||||
|
||||
const handleOpenGitHubImport = useCallback(() => {
|
||||
modalManager.openGitHubImport();
|
||||
}, [modalManager]);
|
||||
|
||||
const handleOnboardingViewTask = useCallback((task: Task) => {
|
||||
setFirstCreatedTask(null);
|
||||
modalManager.closeModelOnboarding();
|
||||
modalManager.openDetailTask(task);
|
||||
}, [modalManager]);
|
||||
|
||||
const handleModalCreateWithOnboardingTracking = useCallback(
|
||||
async (input: TaskCreateInput): Promise<Task> => {
|
||||
const task = await taskHandlers.handleModalCreate(input);
|
||||
if (modalManager.modelOnboardingOpen) {
|
||||
setFirstCreatedTask(task);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
[taskHandlers.handleModalCreate, modalManager.modelOnboardingOpen],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!modalManager.modelOnboardingOpen && firstCreatedTask) {
|
||||
setFirstCreatedTask(null);
|
||||
}
|
||||
}, [modalManager.modelOnboardingOpen, firstCreatedTask]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalManager.detailTask && (
|
||||
@@ -193,7 +227,7 @@ export function AppModals({
|
||||
isOpen={modalManager.newTaskModalOpen}
|
||||
onClose={modalManager.closeNewTask}
|
||||
tasks={tasks}
|
||||
onCreateTask={taskHandlers.handleModalCreate}
|
||||
onCreateTask={handleModalCreateWithOnboardingTracking}
|
||||
addToast={addToast}
|
||||
projectId={projectId}
|
||||
onPlanningMode={modalManager.openPlanningWithInitialPlan}
|
||||
@@ -253,6 +287,10 @@ export function AppModals({
|
||||
<ModelOnboardingModal
|
||||
onComplete={projectActions.handleModelOnboardingComplete}
|
||||
addToast={addToast}
|
||||
onOpenNewTask={handleOpenNewTask}
|
||||
onOpenGitHubImport={handleOpenGitHubImport}
|
||||
firstCreatedTask={firstCreatedTask}
|
||||
onViewTask={handleOnboardingViewTask}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -421,6 +421,10 @@ export interface ModelOnboardingModalProps {
|
||||
onOpenNewTask?: () => void;
|
||||
/** Optional callback when user wants to open GitHub import */
|
||||
onOpenGitHubImport?: () => void;
|
||||
/** First task created from the onboarding flow, if available */
|
||||
firstCreatedTask?: Task | null;
|
||||
/** Optional callback when user wants to open the created task detail */
|
||||
onViewTask?: (task: Task) => void;
|
||||
}
|
||||
|
||||
/** Outcome states for OAuth login attempts */
|
||||
@@ -448,6 +452,8 @@ export function ModelOnboardingModal({
|
||||
addToast,
|
||||
onOpenNewTask,
|
||||
onOpenGitHubImport,
|
||||
firstCreatedTask,
|
||||
onViewTask,
|
||||
}: ModelOnboardingModalProps) {
|
||||
// Initialize from persisted state if available (allows resume from last step)
|
||||
const persistedState = getOnboardingState();
|
||||
@@ -462,6 +468,7 @@ export function ModelOnboardingModal({
|
||||
const [step, setStep] = useState<OnboardingStep>(initialStep);
|
||||
const [completedSteps, setCompletedSteps] = useState<OnboardingStep[]>(persistedCompletedSteps);
|
||||
const [skippedSteps, setSkippedSteps] = useState<OnboardingStep[]>(persistedSkippedSteps);
|
||||
const [showTaskCreated, setShowTaskCreated] = useState(false);
|
||||
const [authProviders, setAuthProviders] = useState<AuthProvider[]>([]);
|
||||
const [authLoading, setAuthLoading] = useState(true);
|
||||
const [authActionInProgress, setAuthActionInProgress] = useState<string | null>(null);
|
||||
@@ -479,6 +486,7 @@ export function ModelOnboardingModal({
|
||||
return state?.stepData?.github?.skipped === true;
|
||||
});
|
||||
const pollCountRef = useRef<number>(0);
|
||||
const previousCreatedTaskRef = useRef<Task | null | undefined>(firstCreatedTask);
|
||||
|
||||
// Initialize skippedProviders from persisted state
|
||||
const [skippedProviders, setSkippedProviders] = useState<Record<string, boolean>>(
|
||||
@@ -506,6 +514,21 @@ export function ModelOnboardingModal({
|
||||
}
|
||||
}, [step, completedSteps, skippedSteps]);
|
||||
|
||||
useEffect(() => {
|
||||
const hadCreatedTask = previousCreatedTaskRef.current != null;
|
||||
const hasCreatedTask = firstCreatedTask != null;
|
||||
|
||||
if (!hadCreatedTask && hasCreatedTask) {
|
||||
setShowTaskCreated(true);
|
||||
}
|
||||
|
||||
if (!hasCreatedTask) {
|
||||
setShowTaskCreated(false);
|
||||
}
|
||||
|
||||
previousCreatedTaskRef.current = firstCreatedTask;
|
||||
}, [firstCreatedTask]);
|
||||
|
||||
// Auto-mark unconnected providers as skipped when leaving ai-setup step
|
||||
// Only skip if NO providers are connected (if at least one is connected, others remain "Not connected")
|
||||
const prevStepRef = useRef<OnboardingStep>(initialStep);
|
||||
@@ -1138,11 +1161,9 @@ export function ModelOnboardingModal({
|
||||
setSaving(false);
|
||||
}
|
||||
|
||||
// Close modal and trigger callback
|
||||
setIsOpen(false);
|
||||
onComplete();
|
||||
// Keep onboarding open so task creation can hand back to a success state
|
||||
onOpenNewTask?.();
|
||||
}, [selectedModel, availableModels, onComplete, onOpenNewTask]);
|
||||
}, [selectedModel, availableModels, onOpenNewTask]);
|
||||
|
||||
// Handle GitHub import CTA - mark complete, close modal, then open GitHub import
|
||||
const handleOpenGitHubImport = useCallback(async () => {
|
||||
@@ -1204,6 +1225,19 @@ export function ModelOnboardingModal({
|
||||
onComplete();
|
||||
}, [onComplete]);
|
||||
|
||||
const handleViewCreatedTask = useCallback(() => {
|
||||
if (!firstCreatedTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
onViewTask?.(firstCreatedTask);
|
||||
onComplete();
|
||||
}, [firstCreatedTask, onViewTask, onComplete]);
|
||||
|
||||
const handleGoToDashboard = useCallback(() => {
|
||||
onComplete();
|
||||
}, [onComplete]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const oauthProviders = authProviders.filter(
|
||||
@@ -1300,6 +1334,9 @@ export function ModelOnboardingModal({
|
||||
});
|
||||
}
|
||||
|
||||
const firstCreatedTaskPreview =
|
||||
firstCreatedTask?.description?.split("\n")[0]?.trim() || firstCreatedTask?.title || "";
|
||||
|
||||
return (
|
||||
<div
|
||||
className="modal-overlay open"
|
||||
@@ -1820,54 +1857,86 @@ export function ModelOnboardingModal({
|
||||
Your workspace is ready. Here's how to get started:
|
||||
</p>
|
||||
|
||||
<ReadinessSummary items={readinessItems} />
|
||||
|
||||
<OnboardingDisclosure summary="What happens when I create a task?">
|
||||
<p className="onboarding-helper-text">
|
||||
A task describes something you want done. Fusion's AI agents will read
|
||||
your description and work on implementing it. You can track progress on
|
||||
the board and review the results.
|
||||
</p>
|
||||
</OnboardingDisclosure>
|
||||
|
||||
<div className="onboarding-cta-options">
|
||||
<button
|
||||
className="onboarding-cta-card primary"
|
||||
onClick={handleOpenNewTask}
|
||||
disabled={saving}
|
||||
>
|
||||
<div className="cta-icon">
|
||||
<Plus size={24} />
|
||||
{showTaskCreated && firstCreatedTask ? (
|
||||
<div className="onboarding-task-created">
|
||||
<CheckCircle size={56} className="success-icon" />
|
||||
<h3 className="onboarding-task-created__title">Your first task is ready!</h3>
|
||||
<div className="onboarding-task-created__task-id">{firstCreatedTask.id}</div>
|
||||
{firstCreatedTaskPreview && (
|
||||
<p className="onboarding-task-created__description">{firstCreatedTaskPreview}</p>
|
||||
)}
|
||||
<p className="onboarding-task-created__hint">
|
||||
Your task has been created and will appear on the board.
|
||||
</p>
|
||||
<div className="onboarding-task-created__actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={handleViewCreatedTask}
|
||||
>
|
||||
View Task
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn"
|
||||
onClick={handleGoToDashboard}
|
||||
>
|
||||
Go to Dashboard
|
||||
</button>
|
||||
</div>
|
||||
<div className="cta-content">
|
||||
<strong>Create a New Task</strong>
|
||||
<span>Describe what you need built and AI will work on it</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<ReadinessSummary items={readinessItems} />
|
||||
|
||||
<button
|
||||
className={`onboarding-cta-card${!isGithubAuthenticated ? " onboarding-cta-card--disabled" : ""}`}
|
||||
data-testid="cta-github-import"
|
||||
onClick={handleOpenGitHubImport}
|
||||
disabled={saving}
|
||||
>
|
||||
<div className="cta-icon">
|
||||
<GitPullRequest size={24} />
|
||||
</div>
|
||||
<div className="cta-content">
|
||||
<strong>Import from GitHub</strong>
|
||||
<span>Turn GitHub issues into tasks you can track here</span>
|
||||
{!isGithubAuthenticated && (
|
||||
<small className="onboarding-cta-note">Requires GitHub connection</small>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<OnboardingDisclosure summary="What happens when I create a task?">
|
||||
<p className="onboarding-helper-text">
|
||||
A task describes something you want done. Fusion's AI agents will read
|
||||
your description and work on implementing it. You can track progress on
|
||||
the board and review the results.
|
||||
</p>
|
||||
</OnboardingDisclosure>
|
||||
|
||||
<p className="onboarding-skip-note">
|
||||
You can create tasks anytime from the board, or use{" "}
|
||||
<code>fn task create</code> in the terminal.
|
||||
</p>
|
||||
<div className="onboarding-cta-options">
|
||||
<button
|
||||
className="onboarding-cta-card primary"
|
||||
onClick={handleOpenNewTask}
|
||||
disabled={saving}
|
||||
>
|
||||
<div className="cta-icon">
|
||||
<Plus size={24} />
|
||||
</div>
|
||||
<div className="cta-content">
|
||||
<strong>Create a New Task</strong>
|
||||
<span>Describe what you need built and AI will work on it</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`onboarding-cta-card${!isGithubAuthenticated ? " onboarding-cta-card--disabled" : ""}`}
|
||||
data-testid="cta-github-import"
|
||||
onClick={handleOpenGitHubImport}
|
||||
disabled={saving}
|
||||
>
|
||||
<div className="cta-icon">
|
||||
<GitPullRequest size={24} />
|
||||
</div>
|
||||
<div className="cta-content">
|
||||
<strong>Import from GitHub</strong>
|
||||
<span>Turn GitHub issues into tasks you can track here</span>
|
||||
{!isGithubAuthenticated && (
|
||||
<small className="onboarding-cta-note">Requires GitHub connection</small>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="onboarding-skip-note">
|
||||
You can create tasks anytime from the board, or use{" "}
|
||||
<code>fn task create</code> in the terminal.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1916,7 +1985,7 @@ export function ModelOnboardingModal({
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === "first-task" && (
|
||||
{step === "first-task" && !showTaskCreated && (
|
||||
<>
|
||||
<button className="btn btn-sm" onClick={handleBack}>
|
||||
← Back
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, fireEvent, waitFor, act, within } from "@testing-library/react";
|
||||
import { ModelOnboardingModal } from "../ModelOnboardingModal";
|
||||
import type { AuthProvider } from "../../api";
|
||||
import type { Task } from "@fusion/core";
|
||||
|
||||
// Mock the API module
|
||||
const mockFetchAuthStatus = vi.fn();
|
||||
@@ -94,6 +95,12 @@ const defaultModels = [
|
||||
{ provider: "openai", id: "gpt-4o", name: "GPT-4o", reasoning: false, contextWindow: 128000 },
|
||||
];
|
||||
|
||||
const createdTaskMock = {
|
||||
id: "FN-0001",
|
||||
title: "Initial task",
|
||||
description: "Implement onboarding success flow\nAdditional details that should not render",
|
||||
} as unknown as Task;
|
||||
|
||||
// Navigate through steps helper
|
||||
async function navigateToGitHubStep() {
|
||||
await waitFor(() => {
|
||||
@@ -1225,6 +1232,229 @@ describe("ModelOnboardingModal", () => {
|
||||
expect(screen.getByText("Import from GitHub")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows task-created success view after firstCreatedTask transitions from null", async () => {
|
||||
mockGetOnboardingState.mockReturnValue({
|
||||
currentStep: "first-task",
|
||||
completedSteps: ["ai-setup", "github"],
|
||||
skippedSteps: [],
|
||||
updatedAt: "2026-04-17T00:00:00.000Z",
|
||||
dismissed: false,
|
||||
completed: false,
|
||||
stepData: {},
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<ModelOnboardingModal
|
||||
onComplete={vi.fn()}
|
||||
addToast={vi.fn()}
|
||||
firstCreatedTask={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Create Your First Task")).toBeTruthy();
|
||||
});
|
||||
|
||||
rerender(
|
||||
<ModelOnboardingModal
|
||||
onComplete={vi.fn()}
|
||||
addToast={vi.fn()}
|
||||
firstCreatedTask={createdTaskMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Your first task is ready!")).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(screen.queryByText("Create a New Task")).toBeNull();
|
||||
expect(screen.getByText(createdTaskMock.id)).toBeTruthy();
|
||||
expect(screen.getByText("Implement onboarding success flow")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows the created task ID in the success view", async () => {
|
||||
mockGetOnboardingState.mockReturnValue({
|
||||
currentStep: "first-task",
|
||||
completedSteps: ["ai-setup", "github"],
|
||||
skippedSteps: [],
|
||||
updatedAt: "2026-04-17T00:00:00.000Z",
|
||||
dismissed: false,
|
||||
completed: false,
|
||||
stepData: {},
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<ModelOnboardingModal
|
||||
onComplete={vi.fn()}
|
||||
addToast={vi.fn()}
|
||||
firstCreatedTask={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Create Your First Task")).toBeTruthy();
|
||||
});
|
||||
|
||||
rerender(
|
||||
<ModelOnboardingModal
|
||||
onComplete={vi.fn()}
|
||||
addToast={vi.fn()}
|
||||
firstCreatedTask={createdTaskMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("FN-0001")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows the first line of created task description in the success view", async () => {
|
||||
mockGetOnboardingState.mockReturnValue({
|
||||
currentStep: "first-task",
|
||||
completedSteps: ["ai-setup", "github"],
|
||||
skippedSteps: [],
|
||||
updatedAt: "2026-04-17T00:00:00.000Z",
|
||||
dismissed: false,
|
||||
completed: false,
|
||||
stepData: {},
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<ModelOnboardingModal
|
||||
onComplete={vi.fn()}
|
||||
addToast={vi.fn()}
|
||||
firstCreatedTask={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Create Your First Task")).toBeTruthy();
|
||||
});
|
||||
|
||||
rerender(
|
||||
<ModelOnboardingModal
|
||||
onComplete={vi.fn()}
|
||||
addToast={vi.fn()}
|
||||
firstCreatedTask={createdTaskMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Implement onboarding success flow")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("View Task button calls onViewTask with task and then onComplete", async () => {
|
||||
mockGetOnboardingState.mockReturnValue({
|
||||
currentStep: "first-task",
|
||||
completedSteps: ["ai-setup", "github"],
|
||||
skippedSteps: [],
|
||||
updatedAt: "2026-04-17T00:00:00.000Z",
|
||||
dismissed: false,
|
||||
completed: false,
|
||||
stepData: {},
|
||||
});
|
||||
|
||||
const onComplete = vi.fn();
|
||||
const onViewTask = vi.fn();
|
||||
const { rerender } = render(
|
||||
<ModelOnboardingModal
|
||||
onComplete={onComplete}
|
||||
addToast={vi.fn()}
|
||||
onViewTask={onViewTask}
|
||||
firstCreatedTask={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Create Your First Task")).toBeTruthy();
|
||||
});
|
||||
|
||||
rerender(
|
||||
<ModelOnboardingModal
|
||||
onComplete={onComplete}
|
||||
addToast={vi.fn()}
|
||||
onViewTask={onViewTask}
|
||||
firstCreatedTask={createdTaskMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: "View Task" })).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "View Task" }));
|
||||
|
||||
expect(onViewTask).toHaveBeenCalledWith(createdTaskMock);
|
||||
expect(onComplete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Go to Dashboard button calls onComplete", async () => {
|
||||
mockGetOnboardingState.mockReturnValue({
|
||||
currentStep: "first-task",
|
||||
completedSteps: ["ai-setup", "github"],
|
||||
skippedSteps: [],
|
||||
updatedAt: "2026-04-17T00:00:00.000Z",
|
||||
dismissed: false,
|
||||
completed: false,
|
||||
stepData: {},
|
||||
});
|
||||
|
||||
const onComplete = vi.fn();
|
||||
const onViewTask = vi.fn();
|
||||
const { rerender } = render(
|
||||
<ModelOnboardingModal
|
||||
onComplete={onComplete}
|
||||
addToast={vi.fn()}
|
||||
onViewTask={onViewTask}
|
||||
firstCreatedTask={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Create Your First Task")).toBeTruthy();
|
||||
});
|
||||
|
||||
rerender(
|
||||
<ModelOnboardingModal
|
||||
onComplete={onComplete}
|
||||
addToast={vi.fn()}
|
||||
onViewTask={onViewTask}
|
||||
firstCreatedTask={createdTaskMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: "Go to Dashboard" })).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Go to Dashboard" }));
|
||||
|
||||
expect(onComplete).toHaveBeenCalled();
|
||||
expect(onViewTask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps CTA cards visible and success hidden when firstCreatedTask is not provided", async () => {
|
||||
render(<ModelOnboardingModal onComplete={vi.fn()} addToast={vi.fn()} firstCreatedTask={null} />);
|
||||
|
||||
await navigateToFirstTaskStep();
|
||||
|
||||
expect(screen.getByText("Create a New Task")).toBeTruthy();
|
||||
expect(screen.queryByText("Your first task is ready!")).toBeNull();
|
||||
});
|
||||
|
||||
it("Finish Setup still transitions to complete step when no task is created", async () => {
|
||||
render(<ModelOnboardingModal onComplete={vi.fn()} addToast={vi.fn()} firstCreatedTask={null} />);
|
||||
|
||||
await navigateToFirstTaskStep();
|
||||
|
||||
fireEvent.click(screen.getByText("Finish Setup"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("All Set!")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("Import from GitHub card shows connection note when GitHub not connected", async () => {
|
||||
render(<ModelOnboardingModal onComplete={vi.fn()} addToast={vi.fn()} />);
|
||||
|
||||
@@ -1306,7 +1536,7 @@ describe("ModelOnboardingModal", () => {
|
||||
});
|
||||
|
||||
describe("completion", () => {
|
||||
it("completes onboarding and calls onOpenNewTask callback", async () => {
|
||||
it("marks onboarding complete and opens New Task without closing onboarding immediately", async () => {
|
||||
const onComplete = vi.fn();
|
||||
const onOpenNewTask = vi.fn();
|
||||
|
||||
@@ -1348,9 +1578,10 @@ describe("ModelOnboardingModal", () => {
|
||||
);
|
||||
});
|
||||
|
||||
// Should close modal and call both callbacks
|
||||
expect(onComplete).toHaveBeenCalled();
|
||||
// Should open New Task flow but keep onboarding open for success handoff
|
||||
expect(onOpenNewTask).toHaveBeenCalled();
|
||||
expect(onComplete).not.toHaveBeenCalled();
|
||||
expect(screen.getByText("Create Your First Task")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("completes onboarding and calls onOpenGitHubImport callback", async () => {
|
||||
|
||||
Reference in New Issue
Block a user