@reqord/web 0.1.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/LICENSE +661 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +7 -0
- package/package.json +59 -0
- package/postcss.config.mjs +7 -0
- package/src/__tests__/components/dashboard/critical-path-display.test.tsx +129 -0
- package/src/__tests__/components/dashboard/progress-bar.test.tsx +87 -0
- package/src/__tests__/components/dashboard/project-health.test.tsx +57 -0
- package/src/__tests__/components/dashboard/warning-alert.test.tsx +75 -0
- package/src/__tests__/components/feedback/feedback-client-view.test.tsx +84 -0
- package/src/__tests__/components/feedback/feedback-filters.test.tsx +51 -0
- package/src/__tests__/components/feedback/feedback-linked-items.test.tsx +131 -0
- package/src/__tests__/components/feedback/feedback-list.test.tsx +49 -0
- package/src/__tests__/components/feedback/feedback-table.test.tsx +165 -0
- package/src/__tests__/components/flags/flag-badge.test.tsx +41 -0
- package/src/__tests__/components/flags/flag-list.test.tsx +51 -0
- package/src/__tests__/components/gantt/gantt-bar.test.tsx +190 -0
- package/src/__tests__/components/gantt/gantt-chart.test.tsx +141 -0
- package/src/__tests__/components/gantt/gantt-header.test.tsx +84 -0
- package/src/__tests__/components/gantt/gantt-legend.test.tsx +52 -0
- package/src/__tests__/components/graph/dag-layout.test.ts +129 -0
- package/src/__tests__/components/graph/dependency-graph.test.tsx +94 -0
- package/src/__tests__/components/graph/drilldown-breadcrumb.test.tsx +70 -0
- package/src/__tests__/components/graph/drilldown-graph.test.tsx +108 -0
- package/src/__tests__/components/graph/edge-styles.test.ts +27 -0
- package/src/__tests__/components/graph/graph-page-client.test.tsx +124 -0
- package/src/__tests__/components/graph/issue-node.test.tsx +173 -0
- package/src/__tests__/components/graph/requirement-node.test.tsx +151 -0
- package/src/__tests__/components/graph/specification-node.test.tsx +140 -0
- package/src/__tests__/components/specification/spec-tabs.test.tsx +153 -0
- package/src/__tests__/components/specification/tab-coverage.test.tsx +70 -0
- package/src/__tests__/components/specification/tab-design.test.tsx +42 -0
- package/src/__tests__/components/specification/tab-history.test.tsx +118 -0
- package/src/__tests__/components/specification/tab-issues.test.tsx +126 -0
- package/src/__tests__/components/specification/tab-research.test.tsx +42 -0
- package/src/__tests__/lib/dashboard-data.test.ts +334 -0
- package/src/__tests__/lib/drilldown-graph-data.test.ts +267 -0
- package/src/__tests__/lib/gantt-data.test.ts +299 -0
- package/src/__tests__/lib/graph-data.test.ts +309 -0
- package/src/__tests__/lib/local-feedback-repository.test.ts +74 -0
- package/src/__tests__/lib/local-specification-repository.test.ts +194 -0
- package/src/__tests__/lib/reqord-root.test.ts +31 -0
- package/src/__tests__/lib/specification-file.test.ts +63 -0
- package/src/__tests__/lib/tasks-data.test.ts +104 -0
- package/src/app/dashboard/loading.tsx +21 -0
- package/src/app/dashboard/page.tsx +50 -0
- package/src/app/error.tsx +22 -0
- package/src/app/feedback/loading.tsx +13 -0
- package/src/app/feedback/page.tsx +48 -0
- package/src/app/globals.css +2 -0
- package/src/app/graph/page.tsx +32 -0
- package/src/app/layout.tsx +25 -0
- package/src/app/page.tsx +5 -0
- package/src/app/requirements/[id]/edit/page.tsx +40 -0
- package/src/app/requirements/[id]/loading.tsx +14 -0
- package/src/app/requirements/[id]/not-found.tsx +18 -0
- package/src/app/requirements/[id]/page.tsx +43 -0
- package/src/app/requirements/loading.tsx +13 -0
- package/src/app/requirements/new/page.tsx +14 -0
- package/src/app/requirements/page.tsx +35 -0
- package/src/app/specifications/[id]/loading.tsx +14 -0
- package/src/app/specifications/[id]/not-found.tsx +18 -0
- package/src/app/specifications/[id]/page.tsx +52 -0
- package/src/app/specifications/loading.tsx +13 -0
- package/src/app/specifications/page.tsx +42 -0
- package/src/components/dashboard/critical-path-display.tsx +76 -0
- package/src/components/dashboard/progress-bar.tsx +45 -0
- package/src/components/dashboard/progress-section.tsx +57 -0
- package/src/components/dashboard/project-health.tsx +35 -0
- package/src/components/dashboard/status-card.tsx +27 -0
- package/src/components/dashboard/status-cards.tsx +28 -0
- package/src/components/dashboard/warning-alert.tsx +33 -0
- package/src/components/dashboard/warning-alerts.tsx +24 -0
- package/src/components/feedback/feedback-badge.tsx +48 -0
- package/src/components/feedback/feedback-client-view.tsx +38 -0
- package/src/components/feedback/feedback-filters.tsx +86 -0
- package/src/components/feedback/feedback-linked-items.tsx +93 -0
- package/src/components/feedback/feedback-list.tsx +40 -0
- package/src/components/feedback/feedback-table.tsx +115 -0
- package/src/components/gantt/gantt-bar.tsx +65 -0
- package/src/components/gantt/gantt-chart.tsx +88 -0
- package/src/components/gantt/gantt-constants.ts +15 -0
- package/src/components/gantt/gantt-critical-path.tsx +38 -0
- package/src/components/gantt/gantt-group.tsx +25 -0
- package/src/components/gantt/gantt-header.tsx +47 -0
- package/src/components/gantt/gantt-legend.tsx +26 -0
- package/src/components/graph/dag-layout.ts +131 -0
- package/src/components/graph/dependency-graph.tsx +88 -0
- package/src/components/graph/drilldown-breadcrumb.tsx +35 -0
- package/src/components/graph/drilldown-graph.tsx +45 -0
- package/src/components/graph/edge-styles.ts +16 -0
- package/src/components/graph/graph-loader.tsx +25 -0
- package/src/components/graph/graph-page-client.tsx +98 -0
- package/src/components/graph/issue-node.tsx +46 -0
- package/src/components/graph/multi-level-graph.tsx +91 -0
- package/src/components/graph/requirement-node.tsx +69 -0
- package/src/components/graph/specification-node.tsx +39 -0
- package/src/components/requirement/delete-button.tsx +46 -0
- package/src/components/requirement/dependency-editor.tsx +79 -0
- package/src/components/requirement/markdown-editor.tsx +47 -0
- package/src/components/requirement/markdown-renderer.tsx +12 -0
- package/src/components/requirement/requirement-detail.tsx +228 -0
- package/src/components/requirement/requirement-form.tsx +390 -0
- package/src/components/requirement/requirement-table.tsx +203 -0
- package/src/components/requirement/requirement-tabs.tsx +65 -0
- package/src/components/requirement/success-criteria-editor.tsx +53 -0
- package/src/components/specification/spec-detail.tsx +103 -0
- package/src/components/specification/spec-tabs.tsx +66 -0
- package/src/components/specification/specification-table.tsx +193 -0
- package/src/components/specification/tab-coverage.tsx +52 -0
- package/src/components/specification/tab-design.tsx +16 -0
- package/src/components/specification/tab-history.tsx +61 -0
- package/src/components/specification/tab-issues.tsx +111 -0
- package/src/components/specification/tab-research.tsx +16 -0
- package/src/components/ui/badge.tsx +64 -0
- package/src/components/ui/nav.tsx +49 -0
- package/src/components/ui/tabs.tsx +39 -0
- package/src/lib/actions.ts +222 -0
- package/src/lib/dashboard-data.ts +224 -0
- package/src/lib/data.ts +21 -0
- package/src/lib/drilldown-graph-data.ts +98 -0
- package/src/lib/feedback-data.ts +33 -0
- package/src/lib/feedback-repository.ts +6 -0
- package/src/lib/file-system.ts +167 -0
- package/src/lib/gantt-data.ts +168 -0
- package/src/lib/get-repository.ts +43 -0
- package/src/lib/graph-data.ts +161 -0
- package/src/lib/id-generator.ts +23 -0
- package/src/lib/local-feedback-repository.ts +36 -0
- package/src/lib/local-repository.ts +78 -0
- package/src/lib/local-specification-repository.ts +61 -0
- package/src/lib/repository.ts +11 -0
- package/src/lib/reqord-root.ts +33 -0
- package/src/lib/specification-data.ts +28 -0
- package/src/lib/specification-file.ts +12 -0
- package/src/lib/specification-repository.ts +8 -0
- package/src/lib/tasks-data.ts +32 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
4
|
+
import { render, screen, cleanup } from "@testing-library/react";
|
|
5
|
+
import "@testing-library/jest-dom/vitest";
|
|
6
|
+
import type { FeedbackEntry } from "@reqord/shared";
|
|
7
|
+
import { FeedbackList } from "../../../components/feedback/feedback-list";
|
|
8
|
+
|
|
9
|
+
const baseFeedback: FeedbackEntry = {
|
|
10
|
+
githubIssue: 42,
|
|
11
|
+
type: "bug",
|
|
12
|
+
severity: "high",
|
|
13
|
+
status: "open",
|
|
14
|
+
linkedTo: {
|
|
15
|
+
requirements: [],
|
|
16
|
+
createdRequirements: [],
|
|
17
|
+
specifications: [],
|
|
18
|
+
createdSpecifications: [],
|
|
19
|
+
},
|
|
20
|
+
syncedAt: "2026-01-15T10:00:00Z",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe("FeedbackList", () => {
|
|
24
|
+
afterEach(() => cleanup());
|
|
25
|
+
|
|
26
|
+
it("feedbacks空: 何も表示されない", () => {
|
|
27
|
+
const { container } = render(<FeedbackList feedbacks={[]} />);
|
|
28
|
+
expect(container.innerHTML).toBe("");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("titleがある場合はtitleが表示される", () => {
|
|
32
|
+
const fb: FeedbackEntry = { ...baseFeedback, title: "Login fails on Safari" };
|
|
33
|
+
render(<FeedbackList feedbacks={[fb]} />);
|
|
34
|
+
|
|
35
|
+
expect(screen.getByTestId("feedback-title")).toHaveTextContent("Login fails on Safari");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("titleがundefinedの場合はtitleが表示されない", () => {
|
|
39
|
+
render(<FeedbackList feedbacks={[baseFeedback]} />);
|
|
40
|
+
|
|
41
|
+
expect(screen.queryByTestId("feedback-title")).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("Issue番号が表示される", () => {
|
|
45
|
+
render(<FeedbackList feedbacks={[baseFeedback]} />);
|
|
46
|
+
|
|
47
|
+
expect(screen.getByTestId("feedback-issue")).toHaveTextContent("#42");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
4
|
+
import { render, screen, cleanup } from "@testing-library/react";
|
|
5
|
+
import "@testing-library/jest-dom/vitest";
|
|
6
|
+
import type { FeedbackEntry } from "@reqord/shared";
|
|
7
|
+
import { FeedbackTable } from "../../../components/feedback/feedback-table";
|
|
8
|
+
|
|
9
|
+
const baseFeedback: FeedbackEntry = {
|
|
10
|
+
githubIssue: 42,
|
|
11
|
+
type: "bug",
|
|
12
|
+
severity: "high",
|
|
13
|
+
status: "open",
|
|
14
|
+
linkedTo: {
|
|
15
|
+
requirements: ["req-000001"],
|
|
16
|
+
createdRequirements: [],
|
|
17
|
+
specifications: [],
|
|
18
|
+
createdSpecifications: [],
|
|
19
|
+
},
|
|
20
|
+
syncedAt: "2026-01-15T10:00:00Z",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const reqTitles: Record<string, string> = { "req-000001": "認証" };
|
|
24
|
+
const specTitles: Record<string, string> = {};
|
|
25
|
+
|
|
26
|
+
describe("FeedbackTable", () => {
|
|
27
|
+
afterEach(() => cleanup());
|
|
28
|
+
|
|
29
|
+
it("feedbacks空: empty メッセージが表示される", () => {
|
|
30
|
+
render(
|
|
31
|
+
<FeedbackTable
|
|
32
|
+
feedbacks={[]}
|
|
33
|
+
requirementTitles={reqTitles}
|
|
34
|
+
specificationTitles={specTitles}
|
|
35
|
+
/>,
|
|
36
|
+
);
|
|
37
|
+
expect(screen.getByTestId("feedback-empty")).toBeInTheDocument();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("feedbacks配列に基づく行数の正確性", () => {
|
|
41
|
+
const feedbacks: FeedbackEntry[] = [
|
|
42
|
+
baseFeedback,
|
|
43
|
+
{ ...baseFeedback, githubIssue: 43, type: "improvement", severity: "medium" },
|
|
44
|
+
{ ...baseFeedback, githubIssue: 44, type: "security", severity: "critical" },
|
|
45
|
+
];
|
|
46
|
+
render(
|
|
47
|
+
<FeedbackTable
|
|
48
|
+
feedbacks={feedbacks}
|
|
49
|
+
requirementTitles={reqTitles}
|
|
50
|
+
specificationTitles={specTitles}
|
|
51
|
+
/>,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(screen.getAllByTestId("feedback-row")).toHaveLength(3);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("Issue番号が表示される", () => {
|
|
58
|
+
render(
|
|
59
|
+
<FeedbackTable
|
|
60
|
+
feedbacks={[baseFeedback]}
|
|
61
|
+
requirementTitles={reqTitles}
|
|
62
|
+
specificationTitles={specTitles}
|
|
63
|
+
/>,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(screen.getByTestId("feedback-issue")).toHaveTextContent("#42");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("タイプバッジにbug用の色クラスが適用される", () => {
|
|
70
|
+
render(
|
|
71
|
+
<FeedbackTable
|
|
72
|
+
feedbacks={[baseFeedback]}
|
|
73
|
+
requirementTitles={reqTitles}
|
|
74
|
+
specificationTitles={specTitles}
|
|
75
|
+
/>,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const badge = screen.getByTestId("feedback-type-badge");
|
|
79
|
+
expect(badge).toHaveTextContent("bug");
|
|
80
|
+
expect(badge).toHaveClass("bg-red-100");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("severityバッジが表示される", () => {
|
|
84
|
+
render(
|
|
85
|
+
<FeedbackTable
|
|
86
|
+
feedbacks={[baseFeedback]}
|
|
87
|
+
requirementTitles={reqTitles}
|
|
88
|
+
specificationTitles={specTitles}
|
|
89
|
+
/>,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const badge = screen.getByTestId("feedback-severity-badge");
|
|
93
|
+
expect(badge).toHaveTextContent("high");
|
|
94
|
+
expect(badge).toHaveClass("bg-orange-500");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("statusバッジが表示される", () => {
|
|
98
|
+
render(
|
|
99
|
+
<FeedbackTable
|
|
100
|
+
feedbacks={[baseFeedback]}
|
|
101
|
+
requirementTitles={reqTitles}
|
|
102
|
+
specificationTitles={specTitles}
|
|
103
|
+
/>,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const badge = screen.getByTestId("feedback-status-badge");
|
|
107
|
+
expect(badge).toHaveTextContent("open");
|
|
108
|
+
expect(badge).toHaveClass("bg-green-100");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("type/severityがundefinedの場合はバッジが表示されない", () => {
|
|
112
|
+
const fb: FeedbackEntry = {
|
|
113
|
+
...baseFeedback,
|
|
114
|
+
type: undefined,
|
|
115
|
+
severity: undefined,
|
|
116
|
+
};
|
|
117
|
+
render(
|
|
118
|
+
<FeedbackTable
|
|
119
|
+
feedbacks={[fb]}
|
|
120
|
+
requirementTitles={reqTitles}
|
|
121
|
+
specificationTitles={specTitles}
|
|
122
|
+
/>,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
expect(screen.queryByTestId("feedback-type-badge")).toBeNull();
|
|
126
|
+
expect(screen.queryByTestId("feedback-severity-badge")).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("titleがある場合はtitleが表示される", () => {
|
|
130
|
+
const fb: FeedbackEntry = { ...baseFeedback, title: "Login button broken" };
|
|
131
|
+
render(
|
|
132
|
+
<FeedbackTable
|
|
133
|
+
feedbacks={[fb]}
|
|
134
|
+
requirementTitles={reqTitles}
|
|
135
|
+
specificationTitles={specTitles}
|
|
136
|
+
/>,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(screen.getByTestId("feedback-title")).toHaveTextContent("Login button broken");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("titleがundefinedの場合は'-'が表示される", () => {
|
|
143
|
+
render(
|
|
144
|
+
<FeedbackTable
|
|
145
|
+
feedbacks={[baseFeedback]}
|
|
146
|
+
requirementTitles={reqTitles}
|
|
147
|
+
specificationTitles={specTitles}
|
|
148
|
+
/>,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(screen.getByTestId("feedback-title")).toHaveTextContent("-");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("関連Req/Specリンクが表示される", () => {
|
|
155
|
+
render(
|
|
156
|
+
<FeedbackTable
|
|
157
|
+
feedbacks={[baseFeedback]}
|
|
158
|
+
requirementTitles={reqTitles}
|
|
159
|
+
specificationTitles={specTitles}
|
|
160
|
+
/>,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(screen.getByTestId("linked-requirement")).toHaveTextContent("認証");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
4
|
+
import { render, screen, cleanup } from "@testing-library/react";
|
|
5
|
+
import "@testing-library/jest-dom/vitest";
|
|
6
|
+
import { FeedbackBadge } from "../../../components/feedback/feedback-badge";
|
|
7
|
+
|
|
8
|
+
describe("FeedbackBadge", () => {
|
|
9
|
+
afterEach(() => cleanup());
|
|
10
|
+
|
|
11
|
+
it("bugタイプにred色クラスが適用される", () => {
|
|
12
|
+
render(<FeedbackBadge type="bug" />);
|
|
13
|
+
const badge = screen.getByTestId("feedback-badge-bug");
|
|
14
|
+
expect(badge).toHaveClass("bg-red-100");
|
|
15
|
+
expect(badge).toHaveTextContent("Bug");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("improvementタイプにblue色クラスが適用される", () => {
|
|
19
|
+
render(<FeedbackBadge type="improvement" />);
|
|
20
|
+
const badge = screen.getByTestId("feedback-badge-improvement");
|
|
21
|
+
expect(badge).toHaveClass("bg-blue-100");
|
|
22
|
+
expect(badge).toHaveTextContent("Improvement");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("spec-mismatchタイプにpurple色クラスが適用される", () => {
|
|
26
|
+
render(<FeedbackBadge type="spec-mismatch" />);
|
|
27
|
+
const badge = screen.getByTestId("feedback-badge-spec-mismatch");
|
|
28
|
+
expect(badge).toHaveClass("bg-purple-100");
|
|
29
|
+
expect(badge).toHaveTextContent("Spec Mismatch");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("severity propsがある場合のみseverityバッジが表示される", () => {
|
|
33
|
+
render(<FeedbackBadge type="bug" severity="high" />);
|
|
34
|
+
expect(screen.getByTestId("feedback-severity")).toHaveTextContent("high");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("severity propsがない場合はseverityバッジが表示されない", () => {
|
|
38
|
+
render(<FeedbackBadge type="bug" />);
|
|
39
|
+
expect(screen.queryByTestId("feedback-severity")).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
4
|
+
import { render, screen, cleanup } from "@testing-library/react";
|
|
5
|
+
import "@testing-library/jest-dom/vitest";
|
|
6
|
+
import type { FeedbackEntry } from "@reqord/shared";
|
|
7
|
+
import { FeedbackList } from "../../../components/feedback/feedback-list";
|
|
8
|
+
|
|
9
|
+
const makeFeedback = (overrides: Partial<FeedbackEntry> = {}): FeedbackEntry => ({
|
|
10
|
+
githubIssue: 1,
|
|
11
|
+
type: "bug",
|
|
12
|
+
severity: "high",
|
|
13
|
+
linkedTo: {
|
|
14
|
+
requirements: [],
|
|
15
|
+
createdRequirements: [],
|
|
16
|
+
specifications: ["spec-000001"],
|
|
17
|
+
createdSpecifications: [],
|
|
18
|
+
},
|
|
19
|
+
syncedAt: "2026-01-01T00:00:00Z",
|
|
20
|
+
status: "open",
|
|
21
|
+
...overrides,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("FeedbackList", () => {
|
|
25
|
+
afterEach(() => cleanup());
|
|
26
|
+
|
|
27
|
+
it("feedbacks空配列: 何も表示されない", () => {
|
|
28
|
+
const { container } = render(<FeedbackList feedbacks={[]} />);
|
|
29
|
+
expect(container.firstChild).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("feedbackエントリ: githubIssueとseverityバッジが表示される", () => {
|
|
33
|
+
render(<FeedbackList feedbacks={[makeFeedback({ githubIssue: 42, severity: "high" })]} />);
|
|
34
|
+
|
|
35
|
+
expect(screen.getByTestId("feedback-list")).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByTestId("feedback-severity")).toHaveTextContent("high");
|
|
37
|
+
expect(screen.getByTestId("feedback-issue")).toHaveTextContent("#42");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("複数feedback: 全件表示される", () => {
|
|
41
|
+
const feedbacks: FeedbackEntry[] = [
|
|
42
|
+
makeFeedback({ githubIssue: 1, type: "bug" }),
|
|
43
|
+
makeFeedback({ githubIssue: 2, type: "improvement" }),
|
|
44
|
+
makeFeedback({ githubIssue: 3, type: "security" }),
|
|
45
|
+
];
|
|
46
|
+
render(<FeedbackList feedbacks={feedbacks} />);
|
|
47
|
+
|
|
48
|
+
const items = screen.getAllByTestId("feedback-item");
|
|
49
|
+
expect(items).toHaveLength(3);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { render, fireEvent } from "@testing-library/react";
|
|
5
|
+
import "@testing-library/jest-dom/vitest";
|
|
6
|
+
import { GanttBar } from "@/components/gantt/gantt-bar";
|
|
7
|
+
import type { GanttTask } from "@/lib/gantt-data";
|
|
8
|
+
|
|
9
|
+
describe("GanttBar", () => {
|
|
10
|
+
const mockTask: GanttTask = {
|
|
11
|
+
id: "1",
|
|
12
|
+
title: "Test Task",
|
|
13
|
+
issueNumber: 123,
|
|
14
|
+
issueUrl: "https://github.com/test/repo/issues/123",
|
|
15
|
+
priority: "P0",
|
|
16
|
+
state: "open",
|
|
17
|
+
estimatedHours: 4,
|
|
18
|
+
startOffset: 0,
|
|
19
|
+
dependencies: [],
|
|
20
|
+
isCriticalPath: false,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
it("renders closed state with green color", () => {
|
|
24
|
+
const task = { ...mockTask, state: "closed" };
|
|
25
|
+
const { container } = render(
|
|
26
|
+
<svg>
|
|
27
|
+
<GanttBar task={task} y={0} hourWidth={60} leftLabelWidth={200} />
|
|
28
|
+
</svg>
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const rect = container.querySelector("rect");
|
|
32
|
+
expect(rect).toHaveAttribute("fill", "#22c55e");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("renders in_progress state with blue color", () => {
|
|
36
|
+
const task = { ...mockTask, state: "in_progress" };
|
|
37
|
+
const { container } = render(
|
|
38
|
+
<svg>
|
|
39
|
+
<GanttBar task={task} y={0} hourWidth={60} leftLabelWidth={200} />
|
|
40
|
+
</svg>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const rect = container.querySelector("rect");
|
|
44
|
+
expect(rect).toHaveAttribute("fill", "#3b82f6");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("renders blocked state with red color", () => {
|
|
48
|
+
const task = { ...mockTask, state: "blocked" };
|
|
49
|
+
const { container } = render(
|
|
50
|
+
<svg>
|
|
51
|
+
<GanttBar task={task} y={0} hourWidth={60} leftLabelWidth={200} />
|
|
52
|
+
</svg>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const rect = container.querySelector("rect");
|
|
56
|
+
expect(rect).toHaveAttribute("fill", "#ef4444");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("renders open state with gray color", () => {
|
|
60
|
+
const task = { ...mockTask, state: "open" };
|
|
61
|
+
const { container } = render(
|
|
62
|
+
<svg>
|
|
63
|
+
<GanttBar task={task} y={0} hourWidth={60} leftLabelWidth={200} />
|
|
64
|
+
</svg>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const rect = container.querySelector("rect");
|
|
68
|
+
expect(rect).toHaveAttribute("fill", "#9ca3af");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("renders bar with correct width based on estimatedHours", () => {
|
|
72
|
+
const task = { ...mockTask, estimatedHours: 8 };
|
|
73
|
+
const { container } = render(
|
|
74
|
+
<svg>
|
|
75
|
+
<GanttBar task={task} y={0} hourWidth={60} leftLabelWidth={200} />
|
|
76
|
+
</svg>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const rect = container.querySelector("rect");
|
|
80
|
+
expect(rect).toHaveAttribute("width", "480"); // 8 * 60
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("renders bar with correct x position based on startOffset", () => {
|
|
84
|
+
const task = { ...mockTask, startOffset: 4, estimatedHours: 4 };
|
|
85
|
+
const { container } = render(
|
|
86
|
+
<svg>
|
|
87
|
+
<GanttBar task={task} y={0} hourWidth={60} leftLabelWidth={200} />
|
|
88
|
+
</svg>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const rect = container.querySelector("rect");
|
|
92
|
+
expect(rect).toHaveAttribute("x", "440"); // 4 * 60 + 200
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("renders bar with correct y position", () => {
|
|
96
|
+
const { container } = render(
|
|
97
|
+
<svg>
|
|
98
|
+
<GanttBar task={mockTask} y={100} hourWidth={60} leftLabelWidth={200} />
|
|
99
|
+
</svg>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const rect = container.querySelector("rect");
|
|
103
|
+
expect(rect).toHaveAttribute("y", "100");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("renders bar with correct height", () => {
|
|
107
|
+
const { container } = render(
|
|
108
|
+
<svg>
|
|
109
|
+
<GanttBar task={mockTask} y={0} hourWidth={60} leftLabelWidth={200} />
|
|
110
|
+
</svg>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const rect = container.querySelector("rect");
|
|
114
|
+
expect(rect).toHaveAttribute("height", "24"); // BAR_HEIGHT constant
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("calls onHover with task on mouse enter", () => {
|
|
118
|
+
const onHover = vi.fn();
|
|
119
|
+
const { container } = render(
|
|
120
|
+
<svg>
|
|
121
|
+
<GanttBar
|
|
122
|
+
task={mockTask}
|
|
123
|
+
y={0}
|
|
124
|
+
hourWidth={60}
|
|
125
|
+
leftLabelWidth={200}
|
|
126
|
+
onHover={onHover}
|
|
127
|
+
/>
|
|
128
|
+
</svg>
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const group = container.querySelector("g");
|
|
132
|
+
if (group) {
|
|
133
|
+
fireEvent.mouseEnter(group);
|
|
134
|
+
}
|
|
135
|
+
expect(onHover).toHaveBeenCalledWith(mockTask);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("calls onHover with null on mouse leave", () => {
|
|
139
|
+
const onHover = vi.fn();
|
|
140
|
+
const { container } = render(
|
|
141
|
+
<svg>
|
|
142
|
+
<GanttBar
|
|
143
|
+
task={mockTask}
|
|
144
|
+
y={0}
|
|
145
|
+
hourWidth={60}
|
|
146
|
+
leftLabelWidth={200}
|
|
147
|
+
onHover={onHover}
|
|
148
|
+
/>
|
|
149
|
+
</svg>
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const group = container.querySelector("g");
|
|
153
|
+
if (group) {
|
|
154
|
+
fireEvent.mouseLeave(group);
|
|
155
|
+
}
|
|
156
|
+
expect(onHover).toHaveBeenCalledWith(null);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("calls onClick with task when clicked", () => {
|
|
160
|
+
const onClick = vi.fn();
|
|
161
|
+
const { container } = render(
|
|
162
|
+
<svg>
|
|
163
|
+
<GanttBar
|
|
164
|
+
task={mockTask}
|
|
165
|
+
y={0}
|
|
166
|
+
hourWidth={60}
|
|
167
|
+
leftLabelWidth={200}
|
|
168
|
+
onClick={onClick}
|
|
169
|
+
/>
|
|
170
|
+
</svg>
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const group = container.querySelector("g");
|
|
174
|
+
if (group) {
|
|
175
|
+
fireEvent.click(group);
|
|
176
|
+
}
|
|
177
|
+
expect(onClick).toHaveBeenCalledWith(mockTask);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("renders task title as text", () => {
|
|
181
|
+
const { container } = render(
|
|
182
|
+
<svg>
|
|
183
|
+
<GanttBar task={mockTask} y={0} hourWidth={60} leftLabelWidth={200} />
|
|
184
|
+
</svg>
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const text = container.querySelector("text");
|
|
188
|
+
expect(text).toHaveTextContent("Test Task");
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { describe, it, expect } from "vitest";
|
|
4
|
+
import { render, screen } from "@testing-library/react";
|
|
5
|
+
import "@testing-library/jest-dom/vitest";
|
|
6
|
+
import { GanttChart } from "@/components/gantt/gantt-chart";
|
|
7
|
+
import type { GanttData } from "@/lib/gantt-data";
|
|
8
|
+
|
|
9
|
+
describe("GanttChart", () => {
|
|
10
|
+
const mockData: GanttData = {
|
|
11
|
+
specId: "spec-000001",
|
|
12
|
+
groups: [
|
|
13
|
+
{
|
|
14
|
+
priority: "P0",
|
|
15
|
+
label: "P0: Sequential",
|
|
16
|
+
tasks: [
|
|
17
|
+
{
|
|
18
|
+
id: "1",
|
|
19
|
+
title: "Task 1",
|
|
20
|
+
issueNumber: 100,
|
|
21
|
+
issueUrl: "https://github.com/test/repo/issues/100",
|
|
22
|
+
priority: "P0",
|
|
23
|
+
state: "closed",
|
|
24
|
+
estimatedHours: 4,
|
|
25
|
+
startOffset: 0,
|
|
26
|
+
dependencies: [],
|
|
27
|
+
isCriticalPath: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "2",
|
|
31
|
+
title: "Task 2",
|
|
32
|
+
issueNumber: 101,
|
|
33
|
+
issueUrl: "https://github.com/test/repo/issues/101",
|
|
34
|
+
priority: "P0",
|
|
35
|
+
state: "in_progress",
|
|
36
|
+
estimatedHours: 4,
|
|
37
|
+
startOffset: 4,
|
|
38
|
+
dependencies: [100],
|
|
39
|
+
isCriticalPath: true,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
priority: "P1",
|
|
45
|
+
label: "P1: Parallel",
|
|
46
|
+
tasks: [
|
|
47
|
+
{
|
|
48
|
+
id: "3",
|
|
49
|
+
title: "Task 3",
|
|
50
|
+
issueNumber: 102,
|
|
51
|
+
issueUrl: "https://github.com/test/repo/issues/102",
|
|
52
|
+
priority: "P1",
|
|
53
|
+
state: "open",
|
|
54
|
+
estimatedHours: 4,
|
|
55
|
+
startOffset: 8,
|
|
56
|
+
dependencies: [],
|
|
57
|
+
isCriticalPath: false,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
totalEstimatedHours: 12,
|
|
63
|
+
timelineStart: 0,
|
|
64
|
+
timelineEnd: 12,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
it("renders SVG element", () => {
|
|
68
|
+
const { container } = render(<GanttChart data={mockData} />);
|
|
69
|
+
const svg = container.querySelector("svg");
|
|
70
|
+
expect(svg).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("renders correct number of task bars", () => {
|
|
74
|
+
const { container } = render(<GanttChart data={mockData} />);
|
|
75
|
+
const rects = container.querySelectorAll("rect");
|
|
76
|
+
// Each task has a rect, plus group headers
|
|
77
|
+
expect(rects.length).toBeGreaterThanOrEqual(3); // 3 tasks minimum
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("renders group labels", () => {
|
|
81
|
+
render(<GanttChart data={mockData} />);
|
|
82
|
+
const labels = screen.getAllByText("P0: Sequential");
|
|
83
|
+
expect(labels.length).toBeGreaterThanOrEqual(1);
|
|
84
|
+
const p1Labels = screen.getAllByText("P1: Parallel");
|
|
85
|
+
expect(p1Labels.length).toBeGreaterThanOrEqual(1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("renders legend component", () => {
|
|
89
|
+
render(<GanttChart data={mockData} />);
|
|
90
|
+
const completed = screen.getAllByText("Completed");
|
|
91
|
+
expect(completed.length).toBeGreaterThanOrEqual(1);
|
|
92
|
+
const inProgress = screen.getAllByText("In Progress");
|
|
93
|
+
expect(inProgress.length).toBeGreaterThanOrEqual(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("renders empty chart when no groups", () => {
|
|
97
|
+
const emptyData: GanttData = {
|
|
98
|
+
specId: "spec-000001",
|
|
99
|
+
groups: [],
|
|
100
|
+
totalEstimatedHours: 0,
|
|
101
|
+
timelineStart: 0,
|
|
102
|
+
timelineEnd: 0,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const { container } = render(<GanttChart data={emptyData} />);
|
|
106
|
+
const svg = container.querySelector("svg");
|
|
107
|
+
expect(svg).toBeInTheDocument();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("handles single task", () => {
|
|
111
|
+
const singleTaskData: GanttData = {
|
|
112
|
+
specId: "spec-000001",
|
|
113
|
+
groups: [
|
|
114
|
+
{
|
|
115
|
+
priority: "P0",
|
|
116
|
+
label: "P0: Sequential",
|
|
117
|
+
tasks: [
|
|
118
|
+
{
|
|
119
|
+
id: "1",
|
|
120
|
+
title: "Only Task",
|
|
121
|
+
issueNumber: 100,
|
|
122
|
+
issueUrl: "https://github.com/test/repo/issues/100",
|
|
123
|
+
priority: "P0",
|
|
124
|
+
state: "open",
|
|
125
|
+
estimatedHours: 4,
|
|
126
|
+
startOffset: 0,
|
|
127
|
+
dependencies: [],
|
|
128
|
+
isCriticalPath: true,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
totalEstimatedHours: 4,
|
|
134
|
+
timelineStart: 0,
|
|
135
|
+
timelineEnd: 4,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
render(<GanttChart data={singleTaskData} />);
|
|
139
|
+
expect(screen.getByText("Only Task")).toBeInTheDocument();
|
|
140
|
+
});
|
|
141
|
+
});
|