@aiready/components 0.13.18 → 0.13.19
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/dist/charts/ForceDirectedGraph.d.ts +7 -13
- package/dist/charts/ForceDirectedGraph.js +451 -337
- package/dist/charts/ForceDirectedGraph.js.map +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +724 -582
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/charts/ForceDirectedGraph.tsx +12 -509
- package/src/charts/LinkItem.tsx +1 -1
- package/src/charts/NodeItem.tsx +1 -1
- package/src/charts/force-directed/ControlButton.tsx +39 -0
- package/src/charts/force-directed/ForceDirectedGraph.tsx +250 -0
- package/src/charts/force-directed/GraphCanvas.tsx +129 -0
- package/src/charts/{GraphControls.tsx → force-directed/GraphControls.tsx} +3 -110
- package/src/charts/force-directed/index.ts +31 -0
- package/src/charts/force-directed/types.ts +102 -0
- package/src/charts/{hooks.ts → force-directed/useGraphInteractions.ts} +64 -1
- package/src/charts/force-directed/useGraphLayout.ts +54 -0
- package/src/charts/force-directed/useImperativeHandle.ts +131 -0
- package/src/charts/layout-utils.ts +1 -1
- package/src/components/feedback/__tests__/badge.test.tsx +92 -0
- package/src/components/ui/__tests__/button.test.tsx +203 -0
- package/src/data-display/__tests__/ScoreBar.test.tsx +215 -0
- package/src/index.ts +4 -1
- package/src/utils/__tests__/score.test.ts +1 -1
- package/src/utils/score.ts +48 -23
- package/src/charts/types.ts +0 -24
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { ScoreBar, ScoreCard } from '../ScoreBar';
|
|
4
|
+
|
|
5
|
+
// Mock the @aiready/core/client module
|
|
6
|
+
vi.mock('@aiready/core/client', async (importOriginal) => {
|
|
7
|
+
const actual = (await importOriginal()) as any;
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
getRating: vi.fn((score: number) => {
|
|
11
|
+
if (score >= 80) return 'Excellent';
|
|
12
|
+
if (score >= 60) return 'Good';
|
|
13
|
+
if (score >= 40) return 'Fair';
|
|
14
|
+
if (score >= 20) return 'Needs Work';
|
|
15
|
+
return 'Critical';
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('ScoreBar', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.clearAllMocks();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('rendering', () => {
|
|
26
|
+
it('should render with default props', () => {
|
|
27
|
+
render(<ScoreBar score={75} label="Test Score" />);
|
|
28
|
+
|
|
29
|
+
expect(screen.getByText('Test Score')).toBeInTheDocument();
|
|
30
|
+
expect(screen.getByText('75/100')).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should render without score when showScore is false', () => {
|
|
34
|
+
render(<ScoreBar score={75} label="Test Score" showScore={false} />);
|
|
35
|
+
|
|
36
|
+
expect(screen.getByText('Test Score')).toBeInTheDocument();
|
|
37
|
+
expect(screen.queryByText('75/100')).not.toBeInTheDocument();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should apply custom className', () => {
|
|
41
|
+
const { container } = render(
|
|
42
|
+
<ScoreBar score={75} label="Test Score" className="custom-class" />
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(container.firstChild).toHaveClass('custom-class');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should render with different sizes', () => {
|
|
49
|
+
const { container, rerender } = render(
|
|
50
|
+
<ScoreBar score={75} label="Test Score" size="sm" />
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Small size should have h-1.5 for the bar
|
|
54
|
+
expect(container.querySelector('.bg-slate-200')).toHaveClass('h-1.5');
|
|
55
|
+
|
|
56
|
+
rerender(<ScoreBar score={75} label="Test Score" size="lg" />);
|
|
57
|
+
expect(container.querySelector('.bg-slate-200')).toHaveClass('h-3');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('score prop', () => {
|
|
62
|
+
it('should handle score of 0', () => {
|
|
63
|
+
const { container } = render(<ScoreBar score={0} label="Zero Score" />);
|
|
64
|
+
|
|
65
|
+
expect(screen.getByText('0/100')).toBeInTheDocument();
|
|
66
|
+
// Should show critical rating (red)
|
|
67
|
+
expect(container.querySelector('.bg-red-500')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle perfect score of 100', () => {
|
|
71
|
+
const { container } = render(
|
|
72
|
+
<ScoreBar score={100} label="Perfect Score" />
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(screen.getByText('100/100')).toBeInTheDocument();
|
|
76
|
+
// Should show excellent rating (green)
|
|
77
|
+
expect(container.querySelector('.bg-green-500')).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should handle custom maxScore', () => {
|
|
81
|
+
const { container } = render(
|
|
82
|
+
<ScoreBar score={150} maxScore={200} label="Custom Max" />
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
expect(screen.getByText('150/200')).toBeInTheDocument();
|
|
86
|
+
// 150/200 = 75% which should be good (emerald)
|
|
87
|
+
expect(container.querySelector('.bg-emerald-500')).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should clamp bar width to 0-100% range', () => {
|
|
91
|
+
const { container, rerender } = render(
|
|
92
|
+
<ScoreBar score={-10} label="Negative Score" />
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// The bar width should be clamped to 0%
|
|
96
|
+
const barElement = container.querySelector('[style*="width"]');
|
|
97
|
+
expect(barElement).toHaveStyle('width: 0%');
|
|
98
|
+
|
|
99
|
+
rerender(<ScoreBar score={150} label="Over Max" />);
|
|
100
|
+
// The bar width should be clamped to 100%
|
|
101
|
+
expect(barElement).toHaveStyle('width: 100%');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('label display', () => {
|
|
106
|
+
it('should render the provided label', () => {
|
|
107
|
+
render(<ScoreBar score={75} label="Custom Label" />);
|
|
108
|
+
|
|
109
|
+
expect(screen.getByText('Custom Label')).toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should display label in correct size based on size prop', () => {
|
|
113
|
+
const { rerender } = render(
|
|
114
|
+
<ScoreBar score={75} label="Test" size="sm" />
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(screen.getByText('Test')).toHaveClass('text-xs');
|
|
118
|
+
|
|
119
|
+
rerender(<ScoreBar score={75} label="Test" size="lg" />);
|
|
120
|
+
expect(screen.getByText('Test')).toHaveClass('text-base');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('color based on score', () => {
|
|
125
|
+
it('should show green color for excellent scores (>=80)', () => {
|
|
126
|
+
const { container } = render(<ScoreBar score={85} label="Excellent" />);
|
|
127
|
+
|
|
128
|
+
// Check for green-500 background class
|
|
129
|
+
expect(container.querySelector('.bg-green-500')).toBeInTheDocument();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should show emerald color for good scores (>=60)', () => {
|
|
133
|
+
const { container } = render(<ScoreBar score={70} label="Good" />);
|
|
134
|
+
|
|
135
|
+
// Check for emerald-500 background class
|
|
136
|
+
expect(container.querySelector('.bg-emerald-500')).toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should show amber color for fair scores (>=40)', () => {
|
|
140
|
+
const { container } = render(<ScoreBar score={50} label="Fair" />);
|
|
141
|
+
|
|
142
|
+
// Check for amber-500 background class
|
|
143
|
+
expect(container.querySelector('.bg-amber-500')).toBeInTheDocument();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should show orange color for needs-work scores (>=20)', () => {
|
|
147
|
+
const { container } = render(<ScoreBar score={30} label="Needs Work" />);
|
|
148
|
+
|
|
149
|
+
// Check for orange-500 background class
|
|
150
|
+
expect(container.querySelector('.bg-orange-500')).toBeInTheDocument();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should show red color for critical scores (<20)', () => {
|
|
154
|
+
const { container } = render(<ScoreBar score={10} label="Critical" />);
|
|
155
|
+
|
|
156
|
+
// Check for red-500 background class
|
|
157
|
+
expect(container.querySelector('.bg-red-500')).toBeInTheDocument();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('accessibility', () => {
|
|
162
|
+
it('should have proper ARIA structure', () => {
|
|
163
|
+
render(<ScoreBar score={75} label="Accessibility Test" />);
|
|
164
|
+
|
|
165
|
+
// The component should have proper div structure for screen readers
|
|
166
|
+
// The label and score should be visible to screen readers
|
|
167
|
+
expect(screen.getByText('Accessibility Test')).toBeInTheDocument();
|
|
168
|
+
expect(screen.getByText('75/100')).toBeInTheDocument();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('ScoreCard', () => {
|
|
174
|
+
it('should render with score and rating', () => {
|
|
175
|
+
render(<ScoreCard score={85} title="Test Card" />);
|
|
176
|
+
|
|
177
|
+
expect(screen.getByText('85/100')).toBeInTheDocument();
|
|
178
|
+
expect(screen.getByText('Excellent Rating')).toBeInTheDocument();
|
|
179
|
+
expect(screen.getByText('Test Card')).toBeInTheDocument();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should render breakdown when provided', () => {
|
|
183
|
+
const breakdown = [
|
|
184
|
+
{ label: 'Security', score: 90 },
|
|
185
|
+
{ label: 'Performance', score: 80 },
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
render(<ScoreCard score={85} breakdown={breakdown} />);
|
|
189
|
+
|
|
190
|
+
expect(screen.getByText('Security')).toBeInTheDocument();
|
|
191
|
+
expect(screen.getByText('Performance')).toBeInTheDocument();
|
|
192
|
+
expect(screen.getByText('90/100')).toBeInTheDocument();
|
|
193
|
+
expect(screen.getByText('80/100')).toBeInTheDocument();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should calculate and display formula when breakdown has weights', () => {
|
|
197
|
+
const breakdown = [
|
|
198
|
+
{ label: 'Security', score: 90, weight: 2 },
|
|
199
|
+
{ label: 'Performance', score: 80, weight: 1 },
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
render(<ScoreCard score={87} breakdown={breakdown} />);
|
|
203
|
+
|
|
204
|
+
// Formula: 90×2 + 80×1 / 100 = 87
|
|
205
|
+
expect(screen.getByText('90×2 + 80×1 / 100 = 87')).toBeInTheDocument();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should apply custom className', () => {
|
|
209
|
+
const { container } = render(
|
|
210
|
+
<ScoreCard score={75} className="custom-class" />
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(container.firstChild).toHaveClass('custom-class');
|
|
214
|
+
});
|
|
215
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -114,4 +114,7 @@ export {
|
|
|
114
114
|
type ForceDirectedGraphHandle,
|
|
115
115
|
type LayoutType,
|
|
116
116
|
} from './charts/ForceDirectedGraph';
|
|
117
|
-
export {
|
|
117
|
+
export {
|
|
118
|
+
GraphControls,
|
|
119
|
+
type GraphControlsProps,
|
|
120
|
+
} from './charts/force-directed/GraphControls';
|
|
@@ -18,7 +18,7 @@ describe('Score Utilities', () => {
|
|
|
18
18
|
|
|
19
19
|
it('should return correct labels', () => {
|
|
20
20
|
expect(scoreLabel(80)).toBe('AI-Ready');
|
|
21
|
-
expect(scoreLabel(60)).toBe('
|
|
21
|
+
expect(scoreLabel(60)).toBe('Fair');
|
|
22
22
|
expect(scoreLabel(30)).toBe('Critical Issues');
|
|
23
23
|
expect(scoreLabel(null)).toBe('Not analyzed');
|
|
24
24
|
});
|
package/src/utils/score.ts
CHANGED
|
@@ -1,50 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
* Score utility functions for AI readiness scoring
|
|
3
|
-
* Provides color, background, glow, and label helpers for score display
|
|
4
|
-
*/
|
|
1
|
+
import { getRatingSlug } from '@aiready/core/client';
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
|
-
* Get the Tailwind color class for a score
|
|
4
|
+
* Get the Tailwind color class for a score using core rating system
|
|
8
5
|
*/
|
|
9
6
|
export function scoreColor(score: number | null | undefined): string {
|
|
10
7
|
if (score == null) return 'text-slate-400';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const rating = getRatingSlug(score);
|
|
9
|
+
switch (rating) {
|
|
10
|
+
case 'excellent':
|
|
11
|
+
case 'good':
|
|
12
|
+
return 'text-emerald-400';
|
|
13
|
+
case 'fair':
|
|
14
|
+
return 'text-amber-400';
|
|
15
|
+
default:
|
|
16
|
+
return 'text-red-400';
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
|
-
* Get the Tailwind background/border class for a score
|
|
21
|
+
* Get the Tailwind background/border class for a score using core rating system
|
|
18
22
|
*/
|
|
19
23
|
export function scoreBg(score: number | null | undefined): string {
|
|
20
24
|
if (score == null) return 'bg-slate-800/50 border-slate-700';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
const rating = getRatingSlug(score);
|
|
26
|
+
switch (rating) {
|
|
27
|
+
case 'excellent':
|
|
28
|
+
case 'good':
|
|
29
|
+
return 'bg-emerald-900/30 border-emerald-500/30';
|
|
30
|
+
case 'fair':
|
|
31
|
+
return 'bg-amber-900/30 border-amber-500/30';
|
|
32
|
+
default:
|
|
33
|
+
return 'bg-red-900/30 border-red-500/30';
|
|
34
|
+
}
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
/**
|
|
27
|
-
* Get the display label for a score
|
|
38
|
+
* Get the display label for a score using core rating system
|
|
28
39
|
*/
|
|
29
40
|
export function scoreLabel(score: number | null | undefined): string {
|
|
30
41
|
if (score == null) return 'Not analyzed';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
const rating = getRatingSlug(score);
|
|
43
|
+
switch (rating) {
|
|
44
|
+
case 'excellent':
|
|
45
|
+
return 'Excellent';
|
|
46
|
+
case 'good':
|
|
47
|
+
return 'AI-Ready';
|
|
48
|
+
case 'fair':
|
|
49
|
+
return 'Fair';
|
|
50
|
+
case 'needs-work':
|
|
51
|
+
return 'Needs Improvement';
|
|
52
|
+
default:
|
|
53
|
+
return 'Critical Issues';
|
|
54
|
+
}
|
|
34
55
|
}
|
|
35
56
|
|
|
36
57
|
/**
|
|
37
|
-
* Get the Tailwind shadow glow class for a score
|
|
58
|
+
* Get the Tailwind shadow glow class for a score using core rating system
|
|
38
59
|
*/
|
|
39
60
|
export function scoreGlow(score: number | null | undefined): string {
|
|
40
61
|
if (score == null) return '';
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
const rating = getRatingSlug(score);
|
|
63
|
+
switch (rating) {
|
|
64
|
+
case 'excellent':
|
|
65
|
+
case 'good':
|
|
66
|
+
return 'shadow-emerald-500/20';
|
|
67
|
+
case 'fair':
|
|
68
|
+
return 'shadow-amber-500/20';
|
|
69
|
+
default:
|
|
70
|
+
return 'shadow-red-500/20';
|
|
71
|
+
}
|
|
44
72
|
}
|
|
45
73
|
|
|
46
|
-
import { getRatingSlug } from '@aiready/core/client';
|
|
47
|
-
|
|
48
74
|
/**
|
|
49
75
|
* Get rating from score (for use with ScoreBar component)
|
|
50
76
|
*/
|
|
@@ -52,6 +78,5 @@ export function getScoreRating(
|
|
|
52
78
|
score: number | null | undefined
|
|
53
79
|
): 'excellent' | 'good' | 'fair' | 'needs-work' | 'critical' {
|
|
54
80
|
if (score == null) return 'critical';
|
|
55
|
-
// Use core implementation to resolve duplication
|
|
56
81
|
return getRatingSlug(score) as any;
|
|
57
82
|
}
|
package/src/charts/types.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface GraphNode {
|
|
2
|
-
id: string;
|
|
3
|
-
label?: string;
|
|
4
|
-
color?: string;
|
|
5
|
-
size?: number;
|
|
6
|
-
group?: string;
|
|
7
|
-
kind?: 'file' | 'package';
|
|
8
|
-
packageGroup?: string;
|
|
9
|
-
x?: number;
|
|
10
|
-
y?: number;
|
|
11
|
-
fx?: number | null;
|
|
12
|
-
fy?: number | null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface GraphLink {
|
|
16
|
-
source: string | GraphNode;
|
|
17
|
-
target: string | GraphNode;
|
|
18
|
-
color?: string;
|
|
19
|
-
width?: number;
|
|
20
|
-
label?: string;
|
|
21
|
-
type?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type LayoutType = 'force' | 'hierarchical' | 'circular';
|