Browse Source
- Add WelcomeModal tests for rendering, navigation, file loading, and view transitions - Add QuickstartInitialView tests for option buttons, external links, and accessibility - Mock fetch for community workflows API and getAllPresets template function - Test file input handling with valid/invalid workflow files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>handoff-20260429-1057
2 changed files with 638 additions and 0 deletions
@ -0,0 +1,243 @@ |
|||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; |
||||
|
import { render, screen, fireEvent } from "@testing-library/react"; |
||||
|
import { QuickstartInitialView } from "@/components/quickstart/QuickstartInitialView"; |
||||
|
|
||||
|
describe("QuickstartInitialView", () => { |
||||
|
const mockOnSelectBlankCanvas = vi.fn(); |
||||
|
const mockOnSelectTemplates = vi.fn(); |
||||
|
const mockOnSelectVibe = vi.fn(); |
||||
|
const mockOnSelectLoad = vi.fn(); |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
vi.clearAllMocks(); |
||||
|
}); |
||||
|
|
||||
|
afterEach(() => { |
||||
|
vi.restoreAllMocks(); |
||||
|
}); |
||||
|
|
||||
|
describe("Basic Rendering", () => { |
||||
|
it("should render the Node Banana title and logo", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
expect(screen.getByText("Node Banana")).toBeInTheDocument(); |
||||
|
expect(screen.getByAltText("")).toBeInTheDocument(); // Logo image
|
||||
|
}); |
||||
|
|
||||
|
it("should render the description text", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
expect( |
||||
|
screen.getByText(/node based workflow editor for AI image generation/i) |
||||
|
).toBeInTheDocument(); |
||||
|
}); |
||||
|
|
||||
|
it("should render all four option buttons", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
expect(screen.getByText("Blank canvas")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Load workflow")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Templates")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Prompt a workflow")).toBeInTheDocument(); |
||||
|
}); |
||||
|
|
||||
|
it("should render option descriptions", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
expect(screen.getByText("Start from scratch")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Open existing file")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Pre-built workflows")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Describe what you want")).toBeInTheDocument(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("Blank Canvas Option", () => { |
||||
|
it("should call onSelectBlankCanvas when clicked", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
fireEvent.click(screen.getByText("Blank canvas")); |
||||
|
|
||||
|
expect(mockOnSelectBlankCanvas).toHaveBeenCalledTimes(1); |
||||
|
}); |
||||
|
|
||||
|
it("should display correct description for blank canvas", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
expect(screen.getByText("Start from scratch")).toBeInTheDocument(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("Load Workflow Option", () => { |
||||
|
it("should call onSelectLoad when clicked", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
fireEvent.click(screen.getByText("Load workflow")); |
||||
|
|
||||
|
expect(mockOnSelectLoad).toHaveBeenCalledTimes(1); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("Templates Option", () => { |
||||
|
it("should call onSelectTemplates when clicked", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
fireEvent.click(screen.getByText("Templates")); |
||||
|
|
||||
|
expect(mockOnSelectTemplates).toHaveBeenCalledTimes(1); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("Prompt a Workflow Option", () => { |
||||
|
it("should call onSelectVibe when clicked", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
fireEvent.click(screen.getByText("Prompt a workflow")); |
||||
|
|
||||
|
expect(mockOnSelectVibe).toHaveBeenCalledTimes(1); |
||||
|
}); |
||||
|
|
||||
|
it("should display Beta badge on prompt option", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
expect(screen.getByText("Beta")).toBeInTheDocument(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("External Links", () => { |
||||
|
it("should render Discord link with correct URL", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const discordLink = screen.getByText("Discord").closest("a"); |
||||
|
expect(discordLink).toHaveAttribute( |
||||
|
"href", |
||||
|
"https://discord.com/invite/89Nr6EKkTf" |
||||
|
); |
||||
|
expect(discordLink).toHaveAttribute("target", "_blank"); |
||||
|
expect(discordLink).toHaveAttribute("rel", "noopener noreferrer"); |
||||
|
}); |
||||
|
|
||||
|
it("should render Twitter/X link with correct URL", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const twitterLink = screen.getByText("Willie").closest("a"); |
||||
|
expect(twitterLink).toHaveAttribute("href", "https://x.com/ReflctWillie"); |
||||
|
expect(twitterLink).toHaveAttribute("target", "_blank"); |
||||
|
expect(twitterLink).toHaveAttribute("rel", "noopener noreferrer"); |
||||
|
}); |
||||
|
|
||||
|
it("should render docs coming soon text (not as a link)", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
expect(screen.getByText("Docs (coming soon)")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Docs (coming soon)").closest("a")).toBeNull(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("Accessibility", () => { |
||||
|
it("should have all buttons as interactive button elements", () => { |
||||
|
render( |
||||
|
<QuickstartInitialView |
||||
|
onSelectBlankCanvas={mockOnSelectBlankCanvas} |
||||
|
onSelectTemplates={mockOnSelectTemplates} |
||||
|
onSelectVibe={mockOnSelectVibe} |
||||
|
onSelectLoad={mockOnSelectLoad} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const buttons = screen.getAllByRole("button"); |
||||
|
// Should have 4 option buttons
|
||||
|
expect(buttons.length).toBe(4); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,395 @@ |
|||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; |
||||
|
import { render, screen, fireEvent, waitFor, act } from "@testing-library/react"; |
||||
|
import { WelcomeModal } from "@/components/quickstart/WelcomeModal"; |
||||
|
import { WorkflowFile } from "@/store/workflowStore"; |
||||
|
|
||||
|
// Mock fetch
|
||||
|
const mockFetch = vi.fn(); |
||||
|
global.fetch = mockFetch; |
||||
|
|
||||
|
// Mock templates
|
||||
|
vi.mock("@/lib/quickstart/templates", () => ({ |
||||
|
getAllPresets: () => [ |
||||
|
{ |
||||
|
id: "product-shot", |
||||
|
name: "Product Shot", |
||||
|
description: "Place product in a new scene or environment", |
||||
|
icon: "M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4", |
||||
|
}, |
||||
|
], |
||||
|
})); |
||||
|
|
||||
|
describe("WelcomeModal", () => { |
||||
|
const mockOnWorkflowGenerated = vi.fn(); |
||||
|
const mockOnClose = vi.fn(); |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
vi.clearAllMocks(); |
||||
|
// Setup default fetch mock for community workflows
|
||||
|
mockFetch.mockImplementation((url: string) => { |
||||
|
if (url === "/api/community-workflows") { |
||||
|
return Promise.resolve({ |
||||
|
ok: true, |
||||
|
json: () => Promise.resolve({ success: true, workflows: [] }), |
||||
|
}); |
||||
|
} |
||||
|
return Promise.resolve({ |
||||
|
ok: true, |
||||
|
json: () => Promise.resolve({ success: true }), |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
afterEach(() => { |
||||
|
vi.restoreAllMocks(); |
||||
|
}); |
||||
|
|
||||
|
describe("Basic Rendering", () => { |
||||
|
it("should render welcome modal with initial view by default", () => { |
||||
|
render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
expect(screen.getByText("Node Banana")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Blank canvas")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Templates")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Prompt a workflow")).toBeInTheDocument(); |
||||
|
}); |
||||
|
|
||||
|
it("should render modal overlay with backdrop", () => { |
||||
|
const { container } = render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const backdrop = container.querySelector(".bg-black\\/50"); |
||||
|
expect(backdrop).toBeInTheDocument(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("Initial View Navigation", () => { |
||||
|
it("should call onClose when 'Blank canvas' is clicked", () => { |
||||
|
render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
fireEvent.click(screen.getByText("Blank canvas")); |
||||
|
|
||||
|
expect(mockOnClose).toHaveBeenCalled(); |
||||
|
}); |
||||
|
|
||||
|
it("should navigate to templates view when 'Templates' is clicked", async () => { |
||||
|
render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
await act(async () => { |
||||
|
fireEvent.click(screen.getByText("Templates")); |
||||
|
}); |
||||
|
|
||||
|
await waitFor(() => { |
||||
|
expect(screen.getByText("Workflow Templates")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Quick Start")).toBeInTheDocument(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it("should navigate to vibe view when 'Prompt a workflow' is clicked", () => { |
||||
|
render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
fireEvent.click(screen.getByText("Prompt a workflow")); |
||||
|
|
||||
|
expect(screen.getByText("Prompt a Workflow")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Describe your workflow")).toBeInTheDocument(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("View Transitions", () => { |
||||
|
it("should navigate back to initial view from templates view", async () => { |
||||
|
render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
// Navigate to templates
|
||||
|
await act(async () => { |
||||
|
fireEvent.click(screen.getByText("Templates")); |
||||
|
}); |
||||
|
|
||||
|
await waitFor(() => { |
||||
|
expect(screen.getByText("Workflow Templates")).toBeInTheDocument(); |
||||
|
}); |
||||
|
|
||||
|
// Click back
|
||||
|
await act(async () => { |
||||
|
fireEvent.click(screen.getByText("Back")); |
||||
|
}); |
||||
|
|
||||
|
expect(screen.getByText("Node Banana")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Blank canvas")).toBeInTheDocument(); |
||||
|
}); |
||||
|
|
||||
|
it("should navigate back to initial view from prompt view", () => { |
||||
|
render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
// Navigate to prompt view
|
||||
|
fireEvent.click(screen.getByText("Prompt a workflow")); |
||||
|
expect(screen.getByText("Prompt a Workflow")).toBeInTheDocument(); |
||||
|
|
||||
|
// Click back
|
||||
|
fireEvent.click(screen.getByText("Back")); |
||||
|
|
||||
|
expect(screen.getByText("Node Banana")).toBeInTheDocument(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("File Loading", () => { |
||||
|
it("should render hidden file input for workflow loading", () => { |
||||
|
const { container } = render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const fileInput = container.querySelector('input[type="file"]'); |
||||
|
expect(fileInput).toBeInTheDocument(); |
||||
|
expect(fileInput).toHaveAttribute("accept", ".json"); |
||||
|
expect(fileInput).toHaveClass("hidden"); |
||||
|
}); |
||||
|
|
||||
|
it("should trigger file input when 'Load workflow' is clicked", () => { |
||||
|
const { container } = render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; |
||||
|
const clickSpy = vi.spyOn(fileInput, "click"); |
||||
|
|
||||
|
fireEvent.click(screen.getByText("Load workflow")); |
||||
|
|
||||
|
expect(clickSpy).toHaveBeenCalled(); |
||||
|
}); |
||||
|
|
||||
|
it("should call onWorkflowGenerated when valid workflow file is loaded", async () => { |
||||
|
const validWorkflow: WorkflowFile = { |
||||
|
id: "test-id", |
||||
|
version: 1, |
||||
|
name: "Test Workflow", |
||||
|
edgeStyle: "curved", |
||||
|
nodes: [], |
||||
|
edges: [], |
||||
|
}; |
||||
|
|
||||
|
// Create a mock FileReader class
|
||||
|
const mockFileReader = { |
||||
|
readAsText: vi.fn(function (this: { onload: ((e: ProgressEvent<FileReader>) => void) | null; result: string }) { |
||||
|
setTimeout(() => { |
||||
|
this.onload?.({ target: { result: this.result } } as ProgressEvent<FileReader>); |
||||
|
}, 0); |
||||
|
}), |
||||
|
onload: null as ((e: ProgressEvent<FileReader>) => void) | null, |
||||
|
onerror: null, |
||||
|
result: JSON.stringify(validWorkflow), |
||||
|
}; |
||||
|
|
||||
|
vi.stubGlobal("FileReader", function FileReaderMock(this: typeof mockFileReader) { |
||||
|
Object.assign(this, mockFileReader); |
||||
|
this.readAsText = mockFileReader.readAsText.bind(this); |
||||
|
return this; |
||||
|
}); |
||||
|
|
||||
|
const { container } = render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const file = new File([JSON.stringify(validWorkflow)], "test.json", { |
||||
|
type: "application/json", |
||||
|
}); |
||||
|
|
||||
|
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; |
||||
|
|
||||
|
await act(async () => { |
||||
|
fireEvent.change(fileInput, { target: { files: [file] } }); |
||||
|
}); |
||||
|
|
||||
|
await waitFor(() => { |
||||
|
expect(mockOnWorkflowGenerated).toHaveBeenCalledWith(validWorkflow); |
||||
|
}); |
||||
|
|
||||
|
vi.unstubAllGlobals(); |
||||
|
}); |
||||
|
|
||||
|
it("should show alert for invalid workflow file format", async () => { |
||||
|
const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => {}); |
||||
|
|
||||
|
const invalidWorkflow = { foo: "bar" }; // Missing required fields
|
||||
|
|
||||
|
const mockFileReader = { |
||||
|
readAsText: vi.fn(function (this: { onload: ((e: ProgressEvent<FileReader>) => void) | null; result: string }) { |
||||
|
setTimeout(() => { |
||||
|
this.onload?.({ target: { result: this.result } } as ProgressEvent<FileReader>); |
||||
|
}, 0); |
||||
|
}), |
||||
|
onload: null as ((e: ProgressEvent<FileReader>) => void) | null, |
||||
|
onerror: null, |
||||
|
result: JSON.stringify(invalidWorkflow), |
||||
|
}; |
||||
|
|
||||
|
vi.stubGlobal("FileReader", function FileReaderMock(this: typeof mockFileReader) { |
||||
|
Object.assign(this, mockFileReader); |
||||
|
this.readAsText = mockFileReader.readAsText.bind(this); |
||||
|
return this; |
||||
|
}); |
||||
|
|
||||
|
const { container } = render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const file = new File([JSON.stringify(invalidWorkflow)], "test.json", { |
||||
|
type: "application/json", |
||||
|
}); |
||||
|
|
||||
|
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; |
||||
|
|
||||
|
await act(async () => { |
||||
|
fireEvent.change(fileInput, { target: { files: [file] } }); |
||||
|
}); |
||||
|
|
||||
|
await waitFor(() => { |
||||
|
expect(alertSpy).toHaveBeenCalledWith("Invalid workflow file format"); |
||||
|
}); |
||||
|
expect(mockOnWorkflowGenerated).not.toHaveBeenCalled(); |
||||
|
|
||||
|
vi.unstubAllGlobals(); |
||||
|
}); |
||||
|
|
||||
|
it("should show alert when file parsing fails", async () => { |
||||
|
const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => {}); |
||||
|
|
||||
|
const mockFileReader = { |
||||
|
readAsText: vi.fn(function (this: { onload: ((e: ProgressEvent<FileReader>) => void) | null; result: string }) { |
||||
|
setTimeout(() => { |
||||
|
this.onload?.({ target: { result: this.result } } as ProgressEvent<FileReader>); |
||||
|
}, 0); |
||||
|
}), |
||||
|
onload: null as ((e: ProgressEvent<FileReader>) => void) | null, |
||||
|
onerror: null, |
||||
|
result: "invalid json", |
||||
|
}; |
||||
|
|
||||
|
vi.stubGlobal("FileReader", function FileReaderMock(this: typeof mockFileReader) { |
||||
|
Object.assign(this, mockFileReader); |
||||
|
this.readAsText = mockFileReader.readAsText.bind(this); |
||||
|
return this; |
||||
|
}); |
||||
|
|
||||
|
const { container } = render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const file = new File(["invalid json"], "test.json", { |
||||
|
type: "application/json", |
||||
|
}); |
||||
|
|
||||
|
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; |
||||
|
|
||||
|
await act(async () => { |
||||
|
fireEvent.change(fileInput, { target: { files: [file] } }); |
||||
|
}); |
||||
|
|
||||
|
await waitFor(() => { |
||||
|
expect(alertSpy).toHaveBeenCalledWith("Failed to parse workflow file"); |
||||
|
}); |
||||
|
|
||||
|
vi.unstubAllGlobals(); |
||||
|
}); |
||||
|
|
||||
|
it("should not process if no file is selected", () => { |
||||
|
const { container } = render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; |
||||
|
|
||||
|
fireEvent.change(fileInput, { target: { files: [] } }); |
||||
|
|
||||
|
expect(mockOnWorkflowGenerated).not.toHaveBeenCalled(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("Workflow Selection from Child Views", () => { |
||||
|
it("should call onWorkflowGenerated when workflow is generated from templates view", async () => { |
||||
|
render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
// Navigate to templates
|
||||
|
await act(async () => { |
||||
|
fireEvent.click(screen.getByText("Templates")); |
||||
|
}); |
||||
|
|
||||
|
await waitFor(() => { |
||||
|
expect(screen.getByText("Workflow Templates")).toBeInTheDocument(); |
||||
|
}); |
||||
|
|
||||
|
// Verify templates view is showing - the actual workflow selection is tested in QuickstartTemplatesView tests
|
||||
|
expect(screen.getByText("Quick Start")).toBeInTheDocument(); |
||||
|
}); |
||||
|
|
||||
|
it("should show prompt view when navigating to vibe", () => { |
||||
|
render( |
||||
|
<WelcomeModal |
||||
|
onWorkflowGenerated={mockOnWorkflowGenerated} |
||||
|
onClose={mockOnClose} |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
// Navigate to vibe/prompt view
|
||||
|
fireEvent.click(screen.getByText("Prompt a workflow")); |
||||
|
|
||||
|
expect(screen.getByText("Prompt a Workflow")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Generate Workflow")).toBeInTheDocument(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
Loading…
Reference in new issue