@runfusion/fusion 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/bin.js +28004 -16888
- package/dist/client/assets/AgentDetailView-B7QRcHJH.css +1 -0
- package/dist/client/assets/AgentDetailView-DwLmRXTY.js +18 -0
- package/dist/client/assets/{AgentsView-B3jYk8Kt.js → AgentsView-D-N6aA0P.js} +12 -7
- package/dist/client/assets/ChatView-DnCdKu8Z.js +1 -0
- package/dist/client/assets/{DevServerView-DyGDEiBP.js → DevServerView-BiA1nYtt.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-D5UIeIl6.js → DirectoryPicker-DvBviDG6.js} +1 -1
- package/dist/client/assets/{DocumentsView-DNHu1T8K.js → DocumentsView-BWXOxpuq.js} +1 -1
- package/dist/client/assets/{EvalsView-CpRobtDi.js → EvalsView-CJFbtL7i.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-DOY_oZi7.js → ExperimentalAgentOnboardingModal-DuGIPd0B.js} +1 -1
- package/dist/client/assets/InsightsView-BBpRiolN.js +11 -0
- package/dist/client/assets/{MemoryView-PSc5lGJt.js → MemoryView-48LuNkKk.js} +2 -2
- package/dist/client/assets/NodesView-CGQWSNZM.js +14 -0
- package/dist/client/assets/{PiExtensionsManager-DL_QcN56.js → PiExtensionsManager-i-7UL2oh.js} +2 -2
- package/dist/client/assets/PluginManager-DoSAykD6.js +1 -0
- package/dist/client/assets/{ResearchView-BzCcDAS4.css → ResearchView-BEI4ZSGs.css} +1 -1
- package/dist/client/assets/ResearchView-XZuRtOxE.js +1 -0
- package/dist/client/assets/SettingsModal-Ci0_sqbU.css +1 -0
- package/dist/client/assets/{SettingsModal-CUCyaAyE.js → SettingsModal-CmeF8CN4.js} +1 -1
- package/dist/client/assets/SettingsModal-DBcjf9Bu.js +31 -0
- package/dist/client/assets/SettingsModal-DWKgRxBA.css +1 -0
- package/dist/client/assets/{SetupWizardModal-BKscasuh.js → SetupWizardModal-CgtvpMX9.js} +1 -1
- package/dist/client/assets/{SkillsView-BdELqTy7.js → SkillsView-DErYRumF.js} +1 -1
- package/dist/client/assets/StashRecoveryView-B_8WIQEo.css +1 -0
- package/dist/client/assets/StashRecoveryView-QJrNS4Vg.js +1 -0
- package/dist/client/assets/{TodoView-DFNGBDNV.js → TodoView-BD9NRwq0.js} +2 -2
- package/dist/client/assets/createLucideIcon-BazL2hk5.js +21 -0
- package/dist/client/assets/dashboard-view-BWGH_fAq.js +63 -0
- package/dist/client/assets/dashboard-view-BoTzlP8b.css +1 -0
- package/dist/client/assets/dashboard-view-DdGlfuu-.css +1 -0
- package/dist/client/assets/dashboard-view-Ws9_ZnKu.js +21 -0
- package/dist/client/assets/{folder-open-k1xmUMyr.js → folder-open-CHSlllzf.js} +1 -1
- package/dist/client/assets/index-DCovGm5b.css +1 -0
- package/dist/client/assets/index-bEwSVl7B.js +692 -0
- package/dist/client/assets/{star-ne32r3Y4.js → star-BgVwWAPz.js} +1 -1
- package/dist/client/assets/{upload-MS-2Gx53.js → upload-CAzycxr9.js} +1 -1
- package/dist/client/assets/{users-C519GSjH.js → users-CZnxCCCJ.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/droid-cli/src/__tests__/index.test.ts +228 -0
- package/dist/extension.js +15810 -10205
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +36 -22
- package/dist/pi-claude-cli/src/provider.ts +7 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/manifest.json +24 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +44 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/TestRunnerPanel.test.tsx +99 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/config-flow.test.ts +91 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-view.test.tsx +40 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-views.test.ts +46 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/draft-store.test.ts +50 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/exec-mock.ts +80 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/fixtures.test.ts +40 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/registry.ts +82 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/generator.test.ts +54 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manage-view.test.tsx +98 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manifest.test.ts +36 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/registration.test.ts +29 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/run-routes.test.ts +98 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runner.test.ts +55 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runtime-availability.test.ts +61 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/validation.test.ts +30 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/wizard-routes.test.ts +61 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/workflow-integration.test.ts +19 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.css +43 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.tsx +49 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/generator.ts +95 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/redact.ts +9 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/runner.ts +79 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/types.ts +31 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/index.ts +58 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/EditDraftModal.tsx +75 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/useDrafts.ts +73 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.css +79 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.tsx +122 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/routes/wizard-routes.ts +272 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.css +70 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.tsx +98 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/run/useRunGeneratedCli.ts +37 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/__tests__/executor-runtime-env.test.ts +191 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/executor-runtime-env.ts +75 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/storage/draft-store.ts +85 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/cli-press-store.test.ts +128 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/credentials.test.ts +62 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-store.ts +427 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-types.ts +110 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/credentials.ts +95 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/steps.tsx +55 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/types.ts +33 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/validation.ts +63 -0
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/manifest.json +10 -0
- package/dist/plugins/fusion-plugin-reports/package.json +18 -2
- package/dist/plugins/fusion-plugin-reports/src/__tests__/approval.test.ts +164 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/manifest.test.ts +14 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/routes-approval.test.ts +109 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/scaffold.test.ts +60 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/share-blocks.test.ts +83 -0
- package/dist/plugins/fusion-plugin-reports/src/aggregation.ts +23 -0
- package/dist/plugins/fusion-plugin-reports/src/approval.ts +97 -0
- package/dist/plugins/fusion-plugin-reports/src/cadence.ts +23 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.css +82 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.tsx +24 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportComparisonDrawer.test.tsx +12 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportDetailPanel.test.tsx +12 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportFiltersBar.test.tsx +14 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportsView.test.tsx +27 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/api.test.ts +19 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReportSectionDiff.test.ts +11 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReports.test.ts +13 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/api.ts +85 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.css +59 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.tsx +58 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportComparisonDrawer.tsx +21 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportDetailPanel.tsx +29 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportEmptyState.tsx +3 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportFiltersBar.tsx +19 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportListItem.tsx +8 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.css +29 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.tsx +43 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ReportApprovalPanel.test.tsx +38 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ShareBlocksPanel.test.tsx +24 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/test-setup.ts +18 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/types.ts +22 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportPreview.ts +44 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportSectionDiff.ts +59 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useReports.ts +71 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useViewportMode.ts +13 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard-view.tsx +6 -0
- package/dist/plugins/fusion-plugin-reports/src/index.ts +48 -2
- package/dist/plugins/fusion-plugin-reports/src/pipeline.ts +58 -0
- package/dist/plugins/fusion-plugin-reports/src/render/__tests__/escape.test.ts +20 -0
- package/dist/plugins/fusion-plugin-reports/src/render/__tests__/html-template.test.ts +110 -0
- package/dist/plugins/fusion-plugin-reports/src/render/__tests__/standalone-html.test.ts +66 -0
- package/dist/plugins/fusion-plugin-reports/src/render/escape.ts +12 -0
- package/dist/plugins/fusion-plugin-reports/src/render/html-styles.ts +40 -0
- package/dist/plugins/fusion-plugin-reports/src/render/html-template.ts +137 -0
- package/dist/plugins/fusion-plugin-reports/src/render/index.ts +4 -0
- package/dist/plugins/fusion-plugin-reports/src/render/standalone-html.ts +75 -0
- package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +31 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/__tests__/report-export-routes.test.ts +104 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/report-approval-routes.ts +98 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/report-export-routes.ts +77 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/report-list-routes.ts +72 -0
- package/dist/plugins/fusion-plugin-reports/src/runs-store.ts +69 -0
- package/dist/plugins/fusion-plugin-reports/src/share-blocks.ts +82 -0
- package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +51 -2
- package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +6 -1
- package/dist/plugins/fusion-plugin-roadmap/bundled.js +1672 -0
- package/dist/plugins/fusion-plugin-roadmap/manifest.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +4 -41
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +2 -3
- package/skill/fusion/references/engine-tools.md +1 -1
- package/skill/fusion/references/extension-tools.md +3 -3
- package/skill/fusion/references/fusion-capabilities.md +1 -1
- package/dist/client/assets/AgentDetailView-BwJaLqZh.css +0 -1
- package/dist/client/assets/AgentDetailView-ZbHEbYRT.js +0 -18
- package/dist/client/assets/ChatView-DhPkiEGs.js +0 -1
- package/dist/client/assets/InsightsView-vp0RE8Mg.js +0 -11
- package/dist/client/assets/NodesView-DMj6HGeC.js +0 -14
- package/dist/client/assets/PluginManager-BtYKm8IT.js +0 -1
- package/dist/client/assets/ResearchView-BhWqfdV0.js +0 -1
- package/dist/client/assets/SettingsModal-BAgB4_AR.js +0 -31
- package/dist/client/assets/SettingsModal-BNSrO1M9.css +0 -1
- package/dist/client/assets/SettingsModal-DzsLquBu.css +0 -1
- package/dist/client/assets/index-Qq2JOOWx.css +0 -1
- package/dist/client/assets/index-TFYXEVpn.js +0 -692
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/api-client.test.ts +0 -101
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/index.test.ts +0 -92
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-routes.test.ts +0 -48
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-suggestions.test.ts +0 -31
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.css +0 -1299
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx +0 -2559
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/RoadmapsView.test.tsx +0 -1144
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/useRoadmaps.test.ts +0 -1756
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/api.ts +0 -70
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/test-setup.ts +0 -7
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/types.ts +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useConfirm.ts +0 -8
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useRoadmaps.ts +0 -1188
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useViewportMode.ts +0 -20
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard-view.tsx +0 -6
- package/dist/plugins/fusion-plugin-roadmap/src/index.ts +0 -74
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-routes.ts +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-schema.ts +0 -41
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.d.ts +0 -15
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.ts +0 -15
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts +0 -283
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js +0 -21
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.ts +0 -310
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts +0 -5
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js +0 -361
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.ts +0 -408
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts +0 -68
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js +0 -300
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.ts +0 -381
- package/dist/plugins/fusion-plugin-roadmap/src/server/index.d.ts +0 -3
- package/dist/plugins/fusion-plugin-roadmap/src/server/index.ts +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-handoff.test.ts +0 -445
- package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-ordering.test.ts +0 -334
- package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-store.test.ts +0 -1318
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-handoff.ts +0 -163
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts +0 -37
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js +0 -188
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.ts +0 -311
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts +0 -299
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js +0 -765
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.ts +0 -1001
|
@@ -1,1144 +0,0 @@
|
|
|
1
|
-
/* @vitest-environment jsdom */
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
4
|
-
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
5
|
-
import userEvent from "@testing-library/user-event";
|
|
6
|
-
import { RoadmapsView } from "../RoadmapsView";
|
|
7
|
-
import * as api from "../api";
|
|
8
|
-
import type {
|
|
9
|
-
Roadmap,
|
|
10
|
-
RoadmapMilestone,
|
|
11
|
-
RoadmapFeature,
|
|
12
|
-
RoadmapWithHierarchy,
|
|
13
|
-
} from "../../roadmap-types";
|
|
14
|
-
|
|
15
|
-
// Mock the API module
|
|
16
|
-
vi.mock("../api", () => ({
|
|
17
|
-
fetchRoadmaps: vi.fn(),
|
|
18
|
-
fetchRoadmap: vi.fn(),
|
|
19
|
-
createRoadmap: vi.fn(),
|
|
20
|
-
updateRoadmap: vi.fn(),
|
|
21
|
-
deleteRoadmap: vi.fn(),
|
|
22
|
-
createRoadmapMilestone: vi.fn(),
|
|
23
|
-
updateRoadmapMilestone: vi.fn(),
|
|
24
|
-
deleteRoadmapMilestone: vi.fn(),
|
|
25
|
-
createRoadmapFeature: vi.fn(),
|
|
26
|
-
updateRoadmapFeature: vi.fn(),
|
|
27
|
-
deleteRoadmapFeature: vi.fn(),
|
|
28
|
-
reorderRoadmapMilestones: vi.fn(),
|
|
29
|
-
reorderRoadmapFeatures: vi.fn(),
|
|
30
|
-
moveRoadmapFeature: vi.fn(),
|
|
31
|
-
generateFeatureSuggestions: vi.fn(),
|
|
32
|
-
generateMilestoneSuggestions: vi.fn(),
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
|
-
// Mock lucide-react icons
|
|
36
|
-
const mockConfirm = vi.fn();
|
|
37
|
-
|
|
38
|
-
vi.mock("../useConfirm", () => ({
|
|
39
|
-
useConfirm: () => ({ confirm: mockConfirm }),
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
vi.mock("lucide-react", () => ({
|
|
43
|
-
Plus: ({ size, ...props }: { size?: number }) => (
|
|
44
|
-
<svg data-testid="plus-icon" {...props}>{`Plus ${size || 16}`}</svg>
|
|
45
|
-
),
|
|
46
|
-
Pencil: (props: Record<string, unknown>) => <span data-testid="pencil-icon" {...props}>Edit</span>,
|
|
47
|
-
Trash2: (props: Record<string, unknown>) => <span data-testid="trash-icon" {...props}>Delete</span>,
|
|
48
|
-
Check: (props: Record<string, unknown>) => <span data-testid="check-icon" {...props}>Check</span>,
|
|
49
|
-
X: (props: Record<string, unknown>) => <span data-testid="x-icon" {...props}>X</span>,
|
|
50
|
-
GripVertical: (props: Record<string, unknown>) => <span data-testid="grip-icon" {...props}>Grip</span>,
|
|
51
|
-
Sparkles: (props: Record<string, unknown>) => <span data-testid="sparkles-icon" {...props}>Sparkles</span>,
|
|
52
|
-
Download: (props: Record<string, unknown>) => <span data-testid="download-icon" {...props}>Download</span>,
|
|
53
|
-
Copy: (props: Record<string, unknown>) => <span data-testid="copy-icon" {...props}>Copy</span>,
|
|
54
|
-
Loader: (props: Record<string, unknown>) => <span data-testid="loader-icon" {...props}>Loader</span>,
|
|
55
|
-
ArrowLeft: (props: Record<string, unknown>) => <span data-testid="arrow-left-icon" {...props}>ArrowLeft</span>,
|
|
56
|
-
ChevronLeft: (props: Record<string, unknown>) => <span data-testid="chevron-left-icon" {...props}>ChevronLeft</span>,
|
|
57
|
-
ChevronUp: (props: Record<string, unknown>) => <span data-testid="chevron-up-icon" {...props}>ChevronUp</span>,
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
// Viewport mode mock helper
|
|
61
|
-
function mockViewport(mode: "mobile" | "desktop") {
|
|
62
|
-
Object.defineProperty(window, "matchMedia", {
|
|
63
|
-
writable: true,
|
|
64
|
-
value: vi.fn().mockImplementation((query: string) => {
|
|
65
|
-
const isMobileQuery = query === "(max-width: 768px)";
|
|
66
|
-
const isTabletQuery = query === "(min-width: 769px) and (max-width: 1024px)";
|
|
67
|
-
return {
|
|
68
|
-
matches: mode === "mobile" ? isMobileQuery : false,
|
|
69
|
-
media: query,
|
|
70
|
-
addEventListener: vi.fn(),
|
|
71
|
-
removeEventListener: vi.fn(),
|
|
72
|
-
dispatchEvent: vi.fn(),
|
|
73
|
-
};
|
|
74
|
-
}),
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const mockRoadmaps: Roadmap[] = [
|
|
79
|
-
{
|
|
80
|
-
id: "RM-001",
|
|
81
|
-
title: "Q2 Roadmap",
|
|
82
|
-
description: "Q2 product roadmap",
|
|
83
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
84
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
id: "RM-002",
|
|
88
|
-
title: "Q3 Roadmap",
|
|
89
|
-
description: "Q3 product roadmap",
|
|
90
|
-
createdAt: "2026-01-02T00:00:00.000Z",
|
|
91
|
-
updatedAt: "2026-01-02T00:00:00.000Z",
|
|
92
|
-
},
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
const mockRoadmapHierarchy: RoadmapWithHierarchy = {
|
|
96
|
-
id: "RM-001",
|
|
97
|
-
title: "Q2 Roadmap",
|
|
98
|
-
description: "Q2 product roadmap",
|
|
99
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
100
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
101
|
-
milestones: [
|
|
102
|
-
{
|
|
103
|
-
id: "RMS-001",
|
|
104
|
-
roadmapId: "RM-001",
|
|
105
|
-
title: "Milestone 1",
|
|
106
|
-
description: "First milestone",
|
|
107
|
-
orderIndex: 0,
|
|
108
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
109
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
110
|
-
features: [
|
|
111
|
-
{
|
|
112
|
-
id: "RF-001",
|
|
113
|
-
milestoneId: "RMS-001",
|
|
114
|
-
title: "Feature 1",
|
|
115
|
-
description: "First feature",
|
|
116
|
-
orderIndex: 0,
|
|
117
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
118
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
id: "RMS-002",
|
|
124
|
-
roadmapId: "RM-001",
|
|
125
|
-
title: "Milestone 2",
|
|
126
|
-
description: "Second milestone",
|
|
127
|
-
orderIndex: 1,
|
|
128
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
129
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
130
|
-
features: [],
|
|
131
|
-
},
|
|
132
|
-
],
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const mockAddToast = vi.fn();
|
|
136
|
-
|
|
137
|
-
describe("RoadmapsView", () => {
|
|
138
|
-
beforeEach(() => {
|
|
139
|
-
vi.clearAllMocks();
|
|
140
|
-
mockConfirm.mockReset();
|
|
141
|
-
mockConfirm.mockResolvedValue(true);
|
|
142
|
-
mockViewport("desktop");
|
|
143
|
-
(api.fetchRoadmaps as ReturnType<typeof vi.fn>).mockResolvedValue(mockRoadmaps);
|
|
144
|
-
(api.fetchRoadmap as ReturnType<typeof vi.fn>).mockResolvedValue(mockRoadmapHierarchy);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
afterEach(() => {
|
|
148
|
-
vi.restoreAllMocks();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("renders roadmap list in sidebar", async () => {
|
|
152
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
153
|
-
|
|
154
|
-
await waitFor(() => {
|
|
155
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
156
|
-
});
|
|
157
|
-
expect(screen.getByText("Q3 Roadmap")).toBeInTheDocument();
|
|
158
|
-
expect(screen.getByText("Roadmaps")).toBeInTheDocument();
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("shows empty state when no roadmaps exist", async () => {
|
|
162
|
-
(api.fetchRoadmaps as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
163
|
-
|
|
164
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
165
|
-
|
|
166
|
-
await waitFor(() => {
|
|
167
|
-
expect(screen.getByText("No roadmaps yet. Click + to create one.")).toBeInTheDocument();
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("shows loading state initially", async () => {
|
|
172
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
173
|
-
|
|
174
|
-
expect(screen.getByText("Loading roadmaps...")).toBeInTheDocument();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("selects a roadmap and shows its content", async () => {
|
|
178
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
179
|
-
|
|
180
|
-
await waitFor(() => {
|
|
181
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
185
|
-
fireEvent.click(roadmapItem);
|
|
186
|
-
|
|
187
|
-
await waitFor(() => {
|
|
188
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
189
|
-
expect(screen.getByTestId("edit-roadmap-btn")).toBeInTheDocument();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// Should show milestones
|
|
193
|
-
expect(screen.getByText("Milestone 1")).toBeInTheDocument();
|
|
194
|
-
expect(screen.getByText("Milestone 2")).toBeInTheDocument();
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it("creates a new roadmap", async () => {
|
|
198
|
-
const newRoadmap = {
|
|
199
|
-
id: "RM-003",
|
|
200
|
-
title: "New Roadmap",
|
|
201
|
-
createdAt: "2026-01-03T00:00:00.000Z",
|
|
202
|
-
updatedAt: "2026-01-03T00:00:00.000Z",
|
|
203
|
-
};
|
|
204
|
-
(api.createRoadmap as ReturnType<typeof vi.fn>).mockResolvedValue(newRoadmap);
|
|
205
|
-
|
|
206
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
207
|
-
|
|
208
|
-
await waitFor(() => {
|
|
209
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
// Click create button
|
|
213
|
-
const createBtn = screen.getByTestId("create-roadmap-btn");
|
|
214
|
-
fireEvent.click(createBtn);
|
|
215
|
-
|
|
216
|
-
// Fill in the form
|
|
217
|
-
const titleInput = screen.getByTestId("create-roadmap-title");
|
|
218
|
-
await userEvent.type(titleInput, "New Roadmap");
|
|
219
|
-
|
|
220
|
-
// Submit
|
|
221
|
-
const submitBtn = screen.getByTestId("create-roadmap-submit");
|
|
222
|
-
fireEvent.click(submitBtn);
|
|
223
|
-
|
|
224
|
-
await waitFor(() => {
|
|
225
|
-
expect(api.createRoadmap).toHaveBeenCalledWith(
|
|
226
|
-
{ title: "New Roadmap" },
|
|
227
|
-
undefined
|
|
228
|
-
);
|
|
229
|
-
expect(mockAddToast).toHaveBeenCalledWith("Roadmap created", "success");
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("renders inline edit inputs with current values for milestone and feature", async () => {
|
|
234
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
235
|
-
|
|
236
|
-
await waitFor(() => {
|
|
237
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
fireEvent.click(screen.getByTestId("roadmap-item-RM-001"));
|
|
241
|
-
|
|
242
|
-
await waitFor(() => {
|
|
243
|
-
expect(screen.getByText("Milestone 1")).toBeInTheDocument();
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
fireEvent.click(screen.getByTestId("milestone-edit-RMS-001"));
|
|
247
|
-
|
|
248
|
-
const milestoneTitleInput = await screen.findByTestId("milestone-title-input-RMS-001");
|
|
249
|
-
expect(milestoneTitleInput).toHaveValue("Milestone 1");
|
|
250
|
-
|
|
251
|
-
fireEvent.click(screen.getByTestId("feature-edit-RF-001"));
|
|
252
|
-
|
|
253
|
-
const featureTitleInput = await screen.findByTestId("feature-title-input-RF-001");
|
|
254
|
-
expect(featureTitleInput).toHaveValue("Feature 1");
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it("edits a roadmap title inline", async () => {
|
|
258
|
-
(api.updateRoadmap as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
259
|
-
...mockRoadmaps[0],
|
|
260
|
-
title: "Updated Title",
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
264
|
-
|
|
265
|
-
await waitFor(() => {
|
|
266
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Select the roadmap first
|
|
270
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
271
|
-
fireEvent.click(roadmapItem);
|
|
272
|
-
|
|
273
|
-
await waitFor(() => {
|
|
274
|
-
expect(screen.getByTestId("edit-roadmap-btn")).toBeInTheDocument();
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// Click edit button
|
|
278
|
-
const editBtn = screen.getByTestId("edit-roadmap-btn");
|
|
279
|
-
fireEvent.click(editBtn);
|
|
280
|
-
|
|
281
|
-
// Should show input field
|
|
282
|
-
const titleInput = screen.getByTestId("roadmap-title-input");
|
|
283
|
-
expect(titleInput).toBeInTheDocument();
|
|
284
|
-
|
|
285
|
-
// Type new title
|
|
286
|
-
await userEvent.clear(titleInput);
|
|
287
|
-
await userEvent.type(titleInput, "Updated Title");
|
|
288
|
-
|
|
289
|
-
// Save
|
|
290
|
-
fireEvent.keyDown(titleInput, { key: "Enter" });
|
|
291
|
-
|
|
292
|
-
await waitFor(() => {
|
|
293
|
-
expect(api.updateRoadmap).toHaveBeenCalledWith("RM-001", { title: "Updated Title" }, undefined);
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it("deletes a roadmap after confirmation", async () => {
|
|
298
|
-
(api.deleteRoadmap as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
|
299
|
-
|
|
300
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
301
|
-
|
|
302
|
-
await waitFor(() => {
|
|
303
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// Select the roadmap first
|
|
307
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
308
|
-
fireEvent.click(roadmapItem);
|
|
309
|
-
|
|
310
|
-
await waitFor(() => {
|
|
311
|
-
expect(screen.getByTestId("delete-roadmap-btn")).toBeInTheDocument();
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
// Click delete button
|
|
315
|
-
const deleteBtn = screen.getByTestId("delete-roadmap-btn");
|
|
316
|
-
fireEvent.click(deleteBtn);
|
|
317
|
-
|
|
318
|
-
await waitFor(() => {
|
|
319
|
-
expect(mockConfirm).toHaveBeenCalledWith({
|
|
320
|
-
title: "Delete Roadmap",
|
|
321
|
-
message: "Delete this roadmap? This cannot be undone.",
|
|
322
|
-
danger: true,
|
|
323
|
-
});
|
|
324
|
-
expect(api.deleteRoadmap).toHaveBeenCalledWith("RM-001", undefined);
|
|
325
|
-
expect(mockAddToast).toHaveBeenCalledWith("Roadmap deleted", "success");
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it("cancels roadmap deletion when not confirmed", async () => {
|
|
330
|
-
mockConfirm.mockResolvedValueOnce(false);
|
|
331
|
-
|
|
332
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
333
|
-
|
|
334
|
-
await waitFor(() => {
|
|
335
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
339
|
-
fireEvent.click(roadmapItem);
|
|
340
|
-
|
|
341
|
-
await waitFor(() => {
|
|
342
|
-
expect(screen.getByTestId("delete-roadmap-btn")).toBeInTheDocument();
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
const deleteBtn = screen.getByTestId("delete-roadmap-btn");
|
|
346
|
-
fireEvent.click(deleteBtn);
|
|
347
|
-
|
|
348
|
-
expect(api.deleteRoadmap).not.toHaveBeenCalled();
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it("shows empty milestones state", async () => {
|
|
352
|
-
const emptyHierarchy: RoadmapWithHierarchy = {
|
|
353
|
-
...mockRoadmapHierarchy,
|
|
354
|
-
milestones: [],
|
|
355
|
-
};
|
|
356
|
-
(api.fetchRoadmap as ReturnType<typeof vi.fn>).mockResolvedValue(emptyHierarchy);
|
|
357
|
-
|
|
358
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
359
|
-
|
|
360
|
-
await waitFor(() => {
|
|
361
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
365
|
-
fireEvent.click(roadmapItem);
|
|
366
|
-
|
|
367
|
-
await waitFor(() => {
|
|
368
|
-
expect(screen.getByText("This roadmap has no milestones.")).toBeInTheDocument();
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
it("shows empty features state for a milestone", async () => {
|
|
373
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
374
|
-
|
|
375
|
-
await waitFor(() => {
|
|
376
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
380
|
-
fireEvent.click(roadmapItem);
|
|
381
|
-
|
|
382
|
-
await waitFor(() => {
|
|
383
|
-
expect(screen.getByText("Milestone 2")).toBeInTheDocument();
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
// Milestone 2 has no features
|
|
387
|
-
const milestone2 = screen.getByTestId("add-feature-RMS-002").closest(".roadmaps-view__milestone");
|
|
388
|
-
expect(milestone2?.textContent).toContain("No features yet.");
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it("creates a milestone", async () => {
|
|
392
|
-
const newMilestone: RoadmapMilestone = {
|
|
393
|
-
id: "RMS-003",
|
|
394
|
-
roadmapId: "RM-001",
|
|
395
|
-
title: "New Milestone",
|
|
396
|
-
orderIndex: 2,
|
|
397
|
-
createdAt: "2026-01-03T00:00:00.000Z",
|
|
398
|
-
updatedAt: "2026-01-03T00:00:00.000Z",
|
|
399
|
-
};
|
|
400
|
-
(api.createRoadmapMilestone as ReturnType<typeof vi.fn>).mockResolvedValue(newMilestone);
|
|
401
|
-
|
|
402
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
403
|
-
|
|
404
|
-
await waitFor(() => {
|
|
405
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
409
|
-
fireEvent.click(roadmapItem);
|
|
410
|
-
|
|
411
|
-
await waitFor(() => {
|
|
412
|
-
expect(screen.getByTestId("add-milestone-btn")).toBeInTheDocument();
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// Click add milestone button
|
|
416
|
-
const addBtn = screen.getByTestId("add-milestone-btn");
|
|
417
|
-
fireEvent.click(addBtn);
|
|
418
|
-
|
|
419
|
-
// Fill in the form
|
|
420
|
-
const titleInput = screen.getByTestId("create-milestone-title");
|
|
421
|
-
await userEvent.type(titleInput, "New Milestone");
|
|
422
|
-
|
|
423
|
-
// Submit
|
|
424
|
-
const submitBtn = screen.getByTestId("create-milestone-submit");
|
|
425
|
-
fireEvent.click(submitBtn);
|
|
426
|
-
|
|
427
|
-
await waitFor(() => {
|
|
428
|
-
expect(api.createRoadmapMilestone).toHaveBeenCalledWith("RM-001", { title: "New Milestone" }, undefined);
|
|
429
|
-
expect(mockAddToast).toHaveBeenCalledWith("Milestone created", "success");
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
it("deletes a milestone after confirmation", async () => {
|
|
434
|
-
(api.deleteRoadmapMilestone as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
|
435
|
-
|
|
436
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
437
|
-
|
|
438
|
-
await waitFor(() => {
|
|
439
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
443
|
-
fireEvent.click(roadmapItem);
|
|
444
|
-
|
|
445
|
-
await waitFor(() => {
|
|
446
|
-
expect(screen.getByTestId("milestone-delete-RMS-001")).toBeInTheDocument();
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
const deleteBtn = screen.getByTestId("milestone-delete-RMS-001");
|
|
450
|
-
fireEvent.click(deleteBtn);
|
|
451
|
-
|
|
452
|
-
await waitFor(() => {
|
|
453
|
-
expect(mockConfirm).toHaveBeenCalledWith({
|
|
454
|
-
title: "Delete Milestone",
|
|
455
|
-
message: "Delete this milestone and all its features?",
|
|
456
|
-
danger: true,
|
|
457
|
-
});
|
|
458
|
-
expect(api.deleteRoadmapMilestone).toHaveBeenCalledWith("RMS-001", undefined);
|
|
459
|
-
expect(mockAddToast).toHaveBeenCalledWith("Milestone deleted", "success");
|
|
460
|
-
});
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it("creates a feature in a milestone", async () => {
|
|
464
|
-
const newFeature: RoadmapFeature = {
|
|
465
|
-
id: "RF-002",
|
|
466
|
-
milestoneId: "RMS-001",
|
|
467
|
-
title: "New Feature",
|
|
468
|
-
orderIndex: 1,
|
|
469
|
-
createdAt: "2026-01-03T00:00:00.000Z",
|
|
470
|
-
updatedAt: "2026-01-03T00:00:00.000Z",
|
|
471
|
-
};
|
|
472
|
-
(api.createRoadmapFeature as ReturnType<typeof vi.fn>).mockResolvedValue(newFeature);
|
|
473
|
-
|
|
474
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
475
|
-
|
|
476
|
-
await waitFor(() => {
|
|
477
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
481
|
-
fireEvent.click(roadmapItem);
|
|
482
|
-
|
|
483
|
-
await waitFor(() => {
|
|
484
|
-
expect(screen.getByTestId("add-feature-RMS-001")).toBeInTheDocument();
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
// Click add feature button
|
|
488
|
-
const addBtn = screen.getByTestId("add-feature-RMS-001");
|
|
489
|
-
fireEvent.click(addBtn);
|
|
490
|
-
|
|
491
|
-
// Fill in the form
|
|
492
|
-
const titleInput = screen.getByTestId("create-feature-title");
|
|
493
|
-
await userEvent.type(titleInput, "New Feature");
|
|
494
|
-
|
|
495
|
-
// Submit
|
|
496
|
-
const submitBtn = screen.getByTestId("create-feature-submit");
|
|
497
|
-
fireEvent.click(submitBtn);
|
|
498
|
-
|
|
499
|
-
await waitFor(() => {
|
|
500
|
-
expect(api.createRoadmapFeature).toHaveBeenCalledWith("RMS-001", { title: "New Feature" }, undefined);
|
|
501
|
-
expect(mockAddToast).toHaveBeenCalledWith("Feature created", "success");
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
it("deletes a feature after confirmation", async () => {
|
|
506
|
-
(api.deleteRoadmapFeature as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
|
507
|
-
|
|
508
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
509
|
-
|
|
510
|
-
await waitFor(() => {
|
|
511
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
515
|
-
fireEvent.click(roadmapItem);
|
|
516
|
-
|
|
517
|
-
await waitFor(() => {
|
|
518
|
-
expect(screen.getByTestId("feature-delete-RF-001")).toBeInTheDocument();
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
// Hover over feature to show actions
|
|
522
|
-
const featureItem = screen.getByTestId("feature-delete-RF-001").closest(".roadmaps-view__feature-item");
|
|
523
|
-
fireEvent.mouseEnter(featureItem!);
|
|
524
|
-
|
|
525
|
-
const deleteBtn = screen.getByTestId("feature-delete-RF-001");
|
|
526
|
-
fireEvent.click(deleteBtn);
|
|
527
|
-
|
|
528
|
-
await waitFor(() => {
|
|
529
|
-
expect(mockConfirm).toHaveBeenCalledWith({
|
|
530
|
-
title: "Delete Feature",
|
|
531
|
-
message: "Delete this feature?",
|
|
532
|
-
danger: true,
|
|
533
|
-
});
|
|
534
|
-
expect(api.deleteRoadmapFeature).toHaveBeenCalledWith("RF-001", undefined);
|
|
535
|
-
expect(mockAddToast).toHaveBeenCalledWith("Feature deleted", "success");
|
|
536
|
-
});
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
it("cancels inline edit on Escape", async () => {
|
|
540
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
541
|
-
|
|
542
|
-
await waitFor(() => {
|
|
543
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
547
|
-
fireEvent.click(roadmapItem);
|
|
548
|
-
|
|
549
|
-
await waitFor(() => {
|
|
550
|
-
expect(screen.getByTestId("edit-roadmap-btn")).toBeInTheDocument();
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// Start editing
|
|
554
|
-
const editBtn = screen.getByTestId("edit-roadmap-btn");
|
|
555
|
-
fireEvent.click(editBtn);
|
|
556
|
-
|
|
557
|
-
const titleInput = screen.getByTestId("roadmap-title-input");
|
|
558
|
-
expect(titleInput).toBeInTheDocument();
|
|
559
|
-
|
|
560
|
-
// Type something
|
|
561
|
-
await userEvent.type(titleInput, "Changed");
|
|
562
|
-
|
|
563
|
-
// Cancel with Escape
|
|
564
|
-
fireEvent.keyDown(titleInput, { key: "Escape" });
|
|
565
|
-
|
|
566
|
-
// Edit should be cancelled
|
|
567
|
-
await waitFor(() => {
|
|
568
|
-
expect(screen.queryByTestId("roadmap-title-input")).not.toBeInTheDocument();
|
|
569
|
-
});
|
|
570
|
-
expect(api.updateRoadmap).not.toHaveBeenCalled();
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
it("shows empty main state when no roadmap selected", async () => {
|
|
574
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
575
|
-
|
|
576
|
-
await waitFor(() => {
|
|
577
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
expect(screen.getByText("Select a roadmap from the sidebar to view its milestones.")).toBeInTheDocument();
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
describe("Feature suggestions", () => {
|
|
584
|
-
it("shows AI Suggestions button when roadmap is selected", async () => {
|
|
585
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
586
|
-
|
|
587
|
-
// Wait for roadmap to load
|
|
588
|
-
await waitFor(() => {
|
|
589
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
// Select roadmap
|
|
593
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
594
|
-
fireEvent.click(roadmapItem);
|
|
595
|
-
|
|
596
|
-
// Wait for milestone to load and button to appear
|
|
597
|
-
await waitFor(() => {
|
|
598
|
-
expect(screen.getByTestId("generate-features-RMS-001")).toBeInTheDocument();
|
|
599
|
-
}, { timeout: 3000 });
|
|
600
|
-
});
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
describe("Suggestion editing", () => {
|
|
604
|
-
it("can edit milestone suggestion before accepting", async () => {
|
|
605
|
-
// Mock milestone suggestion generation
|
|
606
|
-
(api.generateMilestoneSuggestions as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
607
|
-
suggestions: [
|
|
608
|
-
{ title: "Original Title", description: "Original description" },
|
|
609
|
-
],
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
613
|
-
|
|
614
|
-
await waitFor(() => {
|
|
615
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
// Select roadmap
|
|
619
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
620
|
-
fireEvent.click(roadmapItem);
|
|
621
|
-
|
|
622
|
-
// Wait for roadmap to load
|
|
623
|
-
await waitFor(() => {
|
|
624
|
-
expect(screen.getByText("Generate Milestone Ideas")).toBeInTheDocument();
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
// Generate suggestions
|
|
628
|
-
const goalInput = screen.getByTestId("goal-prompt-input");
|
|
629
|
-
await userEvent.type(goalInput, "Build an app");
|
|
630
|
-
|
|
631
|
-
const generateBtn = screen.getByTestId("generate-suggestions-btn");
|
|
632
|
-
fireEvent.click(generateBtn);
|
|
633
|
-
|
|
634
|
-
// Wait for suggestion to appear
|
|
635
|
-
await waitFor(() => {
|
|
636
|
-
expect(screen.getByText("Original Title")).toBeInTheDocument();
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
// This test verifies the suggestion appears after generation
|
|
640
|
-
expect(screen.getByText("Original Title")).toBeInTheDocument();
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
it("can edit feature suggestion before accepting", async () => {
|
|
644
|
-
// Mock feature suggestion generation
|
|
645
|
-
(api.generateFeatureSuggestions as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
646
|
-
suggestions: [
|
|
647
|
-
{ title: "Feature Suggestion", description: "Feature description" },
|
|
648
|
-
],
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
652
|
-
|
|
653
|
-
await waitFor(() => {
|
|
654
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
// Select roadmap
|
|
658
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
659
|
-
fireEvent.click(roadmapItem);
|
|
660
|
-
|
|
661
|
-
// Wait for milestone to load
|
|
662
|
-
await waitFor(() => {
|
|
663
|
-
expect(screen.getByTestId("generate-features-RMS-001")).toBeInTheDocument();
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
// Generate feature suggestions
|
|
667
|
-
const suggestBtn = screen.getByTestId("generate-features-RMS-001");
|
|
668
|
-
fireEvent.click(suggestBtn);
|
|
669
|
-
|
|
670
|
-
// Wait for suggestion to appear
|
|
671
|
-
await waitFor(() => {
|
|
672
|
-
expect(screen.getByText("Feature Suggestion")).toBeInTheDocument();
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
// Verify the suggestion card is rendered
|
|
676
|
-
expect(screen.getByText("Feature Suggestion")).toBeInTheDocument();
|
|
677
|
-
expect(screen.getByText("Feature description")).toBeInTheDocument();
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
it("shows edit button on suggestion cards", async () => {
|
|
681
|
-
// Mock milestone suggestion generation
|
|
682
|
-
(api.generateMilestoneSuggestions as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
683
|
-
suggestions: [
|
|
684
|
-
{ title: "Test Milestone" },
|
|
685
|
-
],
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
689
|
-
|
|
690
|
-
await waitFor(() => {
|
|
691
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
// Select roadmap
|
|
695
|
-
const roadmapItem = screen.getByTestId("roadmap-item-RM-001");
|
|
696
|
-
fireEvent.click(roadmapItem);
|
|
697
|
-
|
|
698
|
-
// Generate suggestions
|
|
699
|
-
const goalInput = screen.getByTestId("goal-prompt-input");
|
|
700
|
-
await userEvent.type(goalInput, "Build something");
|
|
701
|
-
|
|
702
|
-
const generateBtn = screen.getByTestId("generate-suggestions-btn");
|
|
703
|
-
fireEvent.click(generateBtn);
|
|
704
|
-
|
|
705
|
-
// Wait for suggestion to appear
|
|
706
|
-
await waitFor(() => {
|
|
707
|
-
expect(screen.getByText("Test Milestone")).toBeInTheDocument();
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
// Look for edit button - it should have data-testid
|
|
711
|
-
// The edit button is a pencil icon with testId like "suggestion-{id}-edit"
|
|
712
|
-
const editButtons = screen.queryAllByRole("button", { name: /edit/i });
|
|
713
|
-
expect(editButtons.length).toBeGreaterThan(0);
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
it("sidebar edit pencil selects the roadmap and shows inline edit", async () => {
|
|
717
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
718
|
-
|
|
719
|
-
await waitFor(() => {
|
|
720
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// No roadmap selected yet — main content shows empty state
|
|
724
|
-
expect(screen.getByText("Select a roadmap from the sidebar to view its milestones.")).toBeInTheDocument();
|
|
725
|
-
|
|
726
|
-
// Click the edit pencil on RM-001 in the sidebar
|
|
727
|
-
const editBtn = screen.getByTestId("roadmap-edit-RM-001");
|
|
728
|
-
fireEvent.click(editBtn);
|
|
729
|
-
|
|
730
|
-
// Roadmap should be selected and inline edit should appear
|
|
731
|
-
await waitFor(() => {
|
|
732
|
-
expect(screen.getByTestId("roadmap-title-input")).toBeInTheDocument();
|
|
733
|
-
});
|
|
734
|
-
});
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
describe("Mobile roadmap controls", () => {
|
|
738
|
-
beforeEach(() => {
|
|
739
|
-
mockViewport("mobile");
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
afterEach(() => {
|
|
743
|
-
mockViewport("desktop");
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
it("shows mobile roadmap list when no roadmap selected on mobile", async () => {
|
|
747
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
748
|
-
|
|
749
|
-
await waitFor(() => {
|
|
750
|
-
expect(screen.getByTestId("roadmaps-view__mobile-list")).toBeInTheDocument();
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
expect(screen.getByText("Q2 Roadmap")).toBeInTheDocument();
|
|
754
|
-
expect(screen.getByText("Q3 Roadmap")).toBeInTheDocument();
|
|
755
|
-
expect(screen.getByTestId("mobile-create-roadmap-btn")).toBeInTheDocument();
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
it("shows mobile roadmap items when roadmaps exist on mobile", async () => {
|
|
759
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
760
|
-
|
|
761
|
-
await waitFor(() => {
|
|
762
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-002")).toBeInTheDocument();
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
it("can select a roadmap from mobile list", async () => {
|
|
769
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
770
|
-
|
|
771
|
-
await waitFor(() => {
|
|
772
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
// Click on a roadmap item
|
|
776
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-001"));
|
|
777
|
-
|
|
778
|
-
// Should show the mobile header with the roadmap title
|
|
779
|
-
await waitFor(() => {
|
|
780
|
-
expect(screen.getByTestId("roadmaps-view__mobile-header")).toBeInTheDocument();
|
|
781
|
-
expect(screen.getByText("Q2 Roadmap", { selector: ".roadmaps-view__mobile-header-title" })).toBeInTheDocument();
|
|
782
|
-
});
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
it("mobile back button deselects roadmap", async () => {
|
|
786
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
787
|
-
|
|
788
|
-
await waitFor(() => {
|
|
789
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
// Select a roadmap
|
|
793
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-001"));
|
|
794
|
-
|
|
795
|
-
await waitFor(() => {
|
|
796
|
-
expect(screen.getByTestId("roadmaps-view__mobile-header")).toBeInTheDocument();
|
|
797
|
-
});
|
|
798
|
-
|
|
799
|
-
// Click back button
|
|
800
|
-
fireEvent.click(screen.getByTestId("mobile-back-btn"));
|
|
801
|
-
|
|
802
|
-
// Should show mobile list again
|
|
803
|
-
await waitFor(() => {
|
|
804
|
-
expect(screen.getByTestId("roadmaps-view__mobile-list")).toBeInTheDocument();
|
|
805
|
-
});
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
it("mobile create button shows create form", async () => {
|
|
809
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
810
|
-
|
|
811
|
-
await waitFor(() => {
|
|
812
|
-
expect(screen.getByTestId("mobile-create-roadmap-btn")).toBeInTheDocument();
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
// Click create button
|
|
816
|
-
fireEvent.click(screen.getByTestId("mobile-create-roadmap-btn"));
|
|
817
|
-
|
|
818
|
-
// Should show create form
|
|
819
|
-
await waitFor(() => {
|
|
820
|
-
expect(screen.getByTestId("create-roadmap-form")).toBeInTheDocument();
|
|
821
|
-
expect(screen.getByTestId("create-roadmap-title")).toBeInTheDocument();
|
|
822
|
-
});
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
it("mobile can create roadmap via form", async () => {
|
|
826
|
-
const newRoadmap = {
|
|
827
|
-
id: "RM-003",
|
|
828
|
-
title: "New Roadmap",
|
|
829
|
-
createdAt: "2026-01-03T00:00:00.000Z",
|
|
830
|
-
updatedAt: "2026-01-03T00:00:00.000Z",
|
|
831
|
-
};
|
|
832
|
-
(api.createRoadmap as ReturnType<typeof vi.fn>).mockResolvedValue(newRoadmap);
|
|
833
|
-
|
|
834
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
835
|
-
|
|
836
|
-
await waitFor(() => {
|
|
837
|
-
expect(screen.getByTestId("mobile-create-roadmap-btn")).toBeInTheDocument();
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
// Click create button
|
|
841
|
-
fireEvent.click(screen.getByTestId("mobile-create-roadmap-btn"));
|
|
842
|
-
|
|
843
|
-
// Fill in the form using userEvent for better React integration
|
|
844
|
-
await waitFor(() => {
|
|
845
|
-
const titleInput = screen.getByTestId("create-roadmap-title");
|
|
846
|
-
expect(titleInput).toBeInTheDocument();
|
|
847
|
-
});
|
|
848
|
-
|
|
849
|
-
const titleInput = screen.getByTestId("create-roadmap-title");
|
|
850
|
-
await userEvent.type(titleInput, "New Roadmap");
|
|
851
|
-
|
|
852
|
-
// Submit the form using fireEvent.submit
|
|
853
|
-
const form = screen.getByTestId("create-roadmap-form").querySelector("form");
|
|
854
|
-
expect(form).toBeTruthy();
|
|
855
|
-
fireEvent.submit(form!);
|
|
856
|
-
|
|
857
|
-
await waitFor(() => {
|
|
858
|
-
expect(api.createRoadmap).toHaveBeenCalledWith(
|
|
859
|
-
{ title: "New Roadmap" },
|
|
860
|
-
undefined
|
|
861
|
-
);
|
|
862
|
-
expect(mockAddToast).toHaveBeenCalledWith("Roadmap created", "success");
|
|
863
|
-
});
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
it("mobile edit and delete buttons are visible on roadmap items", async () => {
|
|
867
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
868
|
-
|
|
869
|
-
await waitFor(() => {
|
|
870
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
// Edit and delete buttons should be visible (not hidden behind hover on mobile)
|
|
874
|
-
expect(screen.getByTestId("mobile-roadmap-edit-RM-001")).toBeInTheDocument();
|
|
875
|
-
expect(screen.getByTestId("mobile-roadmap-delete-RM-001")).toBeInTheDocument();
|
|
876
|
-
expect(screen.getByTestId("mobile-roadmap-export-RM-001")).toBeInTheDocument();
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
it("shows empty state on mobile when no roadmaps", async () => {
|
|
880
|
-
(api.fetchRoadmaps as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
881
|
-
|
|
882
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
883
|
-
|
|
884
|
-
await waitFor(() => {
|
|
885
|
-
expect(screen.getByTestId("roadmaps-view__mobile-list")).toBeInTheDocument();
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
expect(screen.getByText("No roadmaps yet.")).toBeInTheDocument();
|
|
889
|
-
expect(screen.getByTestId("roadmaps-view__mobile-list").textContent).toContain("Create Roadmap");
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
it("mobile header shows action buttons when roadmap selected", async () => {
|
|
893
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
894
|
-
|
|
895
|
-
await waitFor(() => {
|
|
896
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
// Select a roadmap
|
|
900
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-001"));
|
|
901
|
-
|
|
902
|
-
await waitFor(() => {
|
|
903
|
-
expect(screen.getByTestId("roadmaps-view__mobile-header")).toBeInTheDocument();
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
// Action buttons should be visible in header
|
|
907
|
-
expect(screen.getByTestId("mobile-header-create-btn")).toBeInTheDocument();
|
|
908
|
-
expect(screen.getByTestId("mobile-header-edit-btn")).toBeInTheDocument();
|
|
909
|
-
expect(screen.getByTestId("mobile-header-delete-btn")).toBeInTheDocument();
|
|
910
|
-
expect(screen.getByTestId("mobile-back-btn")).toBeInTheDocument();
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
it("mobile edit pencil selects the roadmap and shows inline edit", async () => {
|
|
914
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
915
|
-
|
|
916
|
-
await waitFor(() => {
|
|
917
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
// Click the edit pencil on RM-001 in mobile list
|
|
921
|
-
const editBtn = screen.getByTestId("mobile-roadmap-edit-RM-001");
|
|
922
|
-
fireEvent.click(editBtn);
|
|
923
|
-
|
|
924
|
-
// Should show the mobile header (roadmap selected) and inline edit
|
|
925
|
-
await waitFor(() => {
|
|
926
|
-
expect(screen.getByTestId("roadmaps-view__mobile-header")).toBeInTheDocument();
|
|
927
|
-
expect(screen.getByTestId("roadmap-title-input")).toBeInTheDocument();
|
|
928
|
-
});
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
it("mobile roadmap list has scrollable container structure", async () => {
|
|
932
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
933
|
-
|
|
934
|
-
await waitFor(() => {
|
|
935
|
-
expect(screen.getByTestId("roadmaps-view__mobile-list")).toBeInTheDocument();
|
|
936
|
-
});
|
|
937
|
-
|
|
938
|
-
// The mobile list should have the scrollable container
|
|
939
|
-
const mobileList = screen.getByTestId("roadmaps-view__mobile-list");
|
|
940
|
-
expect(mobileList).toBeInTheDocument();
|
|
941
|
-
|
|
942
|
-
// The list items container should exist and be scrollable
|
|
943
|
-
const listItems = mobileList.querySelector(".roadmaps-view__mobile-list-items");
|
|
944
|
-
expect(listItems).toBeInTheDocument();
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
it("selecting roadmap on mobile shows detail view with header and milestone content", async () => {
|
|
948
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
949
|
-
|
|
950
|
-
await waitFor(() => {
|
|
951
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
// Click on a roadmap item
|
|
955
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-001"));
|
|
956
|
-
|
|
957
|
-
// Should show the mobile header with back button
|
|
958
|
-
await waitFor(() => {
|
|
959
|
-
expect(screen.getByTestId("roadmaps-view__mobile-header")).toBeInTheDocument();
|
|
960
|
-
expect(screen.getByTestId("mobile-back-btn")).toBeInTheDocument();
|
|
961
|
-
});
|
|
962
|
-
|
|
963
|
-
// Should show milestone content
|
|
964
|
-
expect(screen.getByText("Milestone 1")).toBeInTheDocument();
|
|
965
|
-
expect(screen.getByText("Milestone 2")).toBeInTheDocument();
|
|
966
|
-
});
|
|
967
|
-
});
|
|
968
|
-
|
|
969
|
-
describe("Mobile suggestion panel collapse", () => {
|
|
970
|
-
beforeEach(() => {
|
|
971
|
-
mockViewport("mobile");
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
afterEach(() => {
|
|
975
|
-
vi.restoreAllMocks();
|
|
976
|
-
});
|
|
977
|
-
|
|
978
|
-
it("shows expand button instead of suggestion section on mobile", async () => {
|
|
979
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
980
|
-
|
|
981
|
-
await waitFor(() => {
|
|
982
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
// Select roadmap
|
|
986
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-001"));
|
|
987
|
-
|
|
988
|
-
// On mobile, should show the expand button instead of the goal prompt input
|
|
989
|
-
await waitFor(() => {
|
|
990
|
-
expect(screen.getByTestId("expand-suggestion-panel-btn")).toBeInTheDocument();
|
|
991
|
-
});
|
|
992
|
-
expect(screen.queryByTestId("goal-prompt-input")).not.toBeInTheDocument();
|
|
993
|
-
});
|
|
994
|
-
|
|
995
|
-
it("expands suggestion panel on mobile when button is clicked", async () => {
|
|
996
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
997
|
-
|
|
998
|
-
await waitFor(() => {
|
|
999
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
// Select roadmap
|
|
1003
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-001"));
|
|
1004
|
-
|
|
1005
|
-
// Expand the panel
|
|
1006
|
-
await waitFor(() => {
|
|
1007
|
-
expect(screen.getByTestId("expand-suggestion-panel-btn")).toBeInTheDocument();
|
|
1008
|
-
});
|
|
1009
|
-
fireEvent.click(screen.getByTestId("expand-suggestion-panel-btn"));
|
|
1010
|
-
|
|
1011
|
-
// Panel should now be visible
|
|
1012
|
-
await waitFor(() => {
|
|
1013
|
-
expect(screen.getByTestId("goal-prompt-input")).toBeInTheDocument();
|
|
1014
|
-
});
|
|
1015
|
-
expect(screen.queryByTestId("expand-suggestion-panel-btn")).not.toBeInTheDocument();
|
|
1016
|
-
});
|
|
1017
|
-
|
|
1018
|
-
it("can collapse suggestion panel on mobile", async () => {
|
|
1019
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
1020
|
-
|
|
1021
|
-
await waitFor(() => {
|
|
1022
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
// Select roadmap
|
|
1026
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-001"));
|
|
1027
|
-
|
|
1028
|
-
// Expand the panel
|
|
1029
|
-
await waitFor(() => {
|
|
1030
|
-
expect(screen.getByTestId("expand-suggestion-panel-btn")).toBeInTheDocument();
|
|
1031
|
-
});
|
|
1032
|
-
fireEvent.click(screen.getByTestId("expand-suggestion-panel-btn"));
|
|
1033
|
-
|
|
1034
|
-
// Wait for panel to expand
|
|
1035
|
-
await waitFor(() => {
|
|
1036
|
-
expect(screen.getByTestId("goal-prompt-input")).toBeInTheDocument();
|
|
1037
|
-
});
|
|
1038
|
-
|
|
1039
|
-
// Collapse the panel
|
|
1040
|
-
fireEvent.click(screen.getByTestId("collapse-suggestion-panel-btn"));
|
|
1041
|
-
|
|
1042
|
-
// Panel should be hidden, expand button should be back
|
|
1043
|
-
await waitFor(() => {
|
|
1044
|
-
expect(screen.getByTestId("expand-suggestion-panel-btn")).toBeInTheDocument();
|
|
1045
|
-
});
|
|
1046
|
-
expect(screen.queryByTestId("goal-prompt-input")).not.toBeInTheDocument();
|
|
1047
|
-
});
|
|
1048
|
-
|
|
1049
|
-
it("persists goal prompt and suggestions across collapse/expand on mobile", async () => {
|
|
1050
|
-
// Mock milestone suggestion generation
|
|
1051
|
-
(api.generateMilestoneSuggestions as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
1052
|
-
suggestions: [
|
|
1053
|
-
{ title: "Persisted Milestone", description: "Persisted description" },
|
|
1054
|
-
],
|
|
1055
|
-
});
|
|
1056
|
-
|
|
1057
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
1058
|
-
|
|
1059
|
-
await waitFor(() => {
|
|
1060
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
|
-
// Select roadmap
|
|
1064
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-001"));
|
|
1065
|
-
|
|
1066
|
-
// Expand the panel
|
|
1067
|
-
await waitFor(() => {
|
|
1068
|
-
expect(screen.getByTestId("expand-suggestion-panel-btn")).toBeInTheDocument();
|
|
1069
|
-
});
|
|
1070
|
-
fireEvent.click(screen.getByTestId("expand-suggestion-panel-btn"));
|
|
1071
|
-
|
|
1072
|
-
// Type into goal prompt
|
|
1073
|
-
await waitFor(() => {
|
|
1074
|
-
expect(screen.getByTestId("goal-prompt-input")).toBeInTheDocument();
|
|
1075
|
-
});
|
|
1076
|
-
const goalInput = screen.getByTestId("goal-prompt-input");
|
|
1077
|
-
await userEvent.type(goalInput, "Build an app");
|
|
1078
|
-
|
|
1079
|
-
// Generate suggestions
|
|
1080
|
-
fireEvent.click(screen.getByTestId("generate-suggestions-btn"));
|
|
1081
|
-
|
|
1082
|
-
// Wait for suggestions to appear
|
|
1083
|
-
await waitFor(() => {
|
|
1084
|
-
expect(screen.getByText("Persisted Milestone")).toBeInTheDocument();
|
|
1085
|
-
});
|
|
1086
|
-
|
|
1087
|
-
// Collapse the panel
|
|
1088
|
-
fireEvent.click(screen.getByTestId("collapse-suggestion-panel-btn"));
|
|
1089
|
-
|
|
1090
|
-
// Wait for expand button to appear
|
|
1091
|
-
await waitFor(() => {
|
|
1092
|
-
expect(screen.getByTestId("expand-suggestion-panel-btn")).toBeInTheDocument();
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
// Re-expand the panel
|
|
1096
|
-
fireEvent.click(screen.getByTestId("expand-suggestion-panel-btn"));
|
|
1097
|
-
|
|
1098
|
-
// Goal prompt and suggestions should persist
|
|
1099
|
-
await waitFor(() => {
|
|
1100
|
-
expect(screen.getByTestId("goal-prompt-input")).toBeInTheDocument();
|
|
1101
|
-
});
|
|
1102
|
-
expect(screen.getByTestId("goal-prompt-input")).toHaveValue("Build an app");
|
|
1103
|
-
expect(screen.getByText("Persisted Milestone")).toBeInTheDocument();
|
|
1104
|
-
});
|
|
1105
|
-
|
|
1106
|
-
it("resets suggestion panel when switching roadmaps on mobile", async () => {
|
|
1107
|
-
render(<RoadmapsView addToast={mockAddToast} />);
|
|
1108
|
-
|
|
1109
|
-
await waitFor(() => {
|
|
1110
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-001")).toBeInTheDocument();
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
// Select RM-001
|
|
1114
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-001"));
|
|
1115
|
-
|
|
1116
|
-
// Expand the panel
|
|
1117
|
-
await waitFor(() => {
|
|
1118
|
-
expect(screen.getByTestId("expand-suggestion-panel-btn")).toBeInTheDocument();
|
|
1119
|
-
});
|
|
1120
|
-
fireEvent.click(screen.getByTestId("expand-suggestion-panel-btn"));
|
|
1121
|
-
|
|
1122
|
-
await waitFor(() => {
|
|
1123
|
-
expect(screen.getByTestId("goal-prompt-input")).toBeInTheDocument();
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
// Go back to roadmap list
|
|
1127
|
-
fireEvent.click(screen.getByTestId("mobile-back-btn"));
|
|
1128
|
-
|
|
1129
|
-
// Wait for list to appear
|
|
1130
|
-
await waitFor(() => {
|
|
1131
|
-
expect(screen.getByTestId("mobile-roadmap-item-RM-002")).toBeInTheDocument();
|
|
1132
|
-
});
|
|
1133
|
-
|
|
1134
|
-
// Switch to RM-002
|
|
1135
|
-
fireEvent.click(screen.getByTestId("mobile-roadmap-item-RM-002"));
|
|
1136
|
-
|
|
1137
|
-
// Panel should be collapsed (expand button visible)
|
|
1138
|
-
await waitFor(() => {
|
|
1139
|
-
expect(screen.getByTestId("expand-suggestion-panel-btn")).toBeInTheDocument();
|
|
1140
|
-
});
|
|
1141
|
-
expect(screen.queryByTestId("goal-prompt-input")).not.toBeInTheDocument();
|
|
1142
|
-
});
|
|
1143
|
-
});
|
|
1144
|
-
});
|