CarouselForge Developer commited on
Commit
e1e95a0
·
1 Parent(s): 0f4e866

feat: add InsightsBadge component to display carousel guidance

Browse files
jest.config.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const nextJest = require('next/jest')
2
+
3
+ const createJestConfig = nextJest({
4
+ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
5
+ dir: './',
6
+ })
7
+
8
+ // Add any custom config to be passed to Jest
9
+ const customJestConfig = {
10
+ setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
11
+ testEnvironment: 'jsdom',
12
+ moduleNameMapper: {
13
+ '^@/(.*)$': '<rootDir>/src/$1',
14
+ },
15
+ testMatch: ['**/*.test.ts', '**/*.test.tsx'],
16
+ }
17
+
18
+ // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
19
+ module.exports = createJestConfig(customJestConfig)
jest.setup.js ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ // jest.setup.js is a place to add global test setup/teardown
2
+ import '@testing-library/jest-dom';
src/components/carousel/InsightsBadge.test.tsx ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import InsightsBadge from './InsightsBadge';
4
+ import type { GeneratedInsight } from '@/lib/analytics/insights';
5
+
6
+ describe('InsightsBadge', () => {
7
+ it('should render null when no insights provided', () => {
8
+ const { container } = render(<InsightsBadge insights={[]} />);
9
+ expect(container.firstChild).toBeNull();
10
+ });
11
+
12
+ it('should display insight count when insights provided', () => {
13
+ const insights: GeneratedInsight[] = [
14
+ {
15
+ id: 'test-1',
16
+ observation: 'Bold statement template performed well',
17
+ confidence_level: 'high',
18
+ applies_to: 'template',
19
+ recommendation: 'Use bold-statement',
20
+ created_at: new Date().toISOString(),
21
+ },
22
+ ];
23
+
24
+ render(<InsightsBadge insights={insights} />);
25
+ expect(screen.getByText(/1 insight/i)).toBeInTheDocument();
26
+ });
27
+
28
+ it('should show high confidence indicator', () => {
29
+ const insights: GeneratedInsight[] = [
30
+ {
31
+ id: 'test-1',
32
+ observation: 'Test observation',
33
+ confidence_level: 'high',
34
+ applies_to: 'template',
35
+ recommendation: 'Test recommendation',
36
+ created_at: new Date().toISOString(),
37
+ },
38
+ ];
39
+
40
+ render(<InsightsBadge insights={insights} />);
41
+ expect(screen.getByText(/Based on your past performance/i)).toBeInTheDocument();
42
+ });
43
+
44
+ it('should display recommendation text in user-friendly language', () => {
45
+ const insights: GeneratedInsight[] = [
46
+ {
47
+ id: 'test-1',
48
+ observation: 'Bold statement template got 45% engagement',
49
+ confidence_level: 'high',
50
+ applies_to: 'template',
51
+ recommendation: 'Use bold-statement for maximum impact',
52
+ created_at: new Date().toISOString(),
53
+ },
54
+ ];
55
+
56
+ render(<InsightsBadge insights={insights} />);
57
+ expect(screen.getByText(/Use bold-statement/i)).toBeInTheDocument();
58
+ });
59
+
60
+ it('should display multiple insights in a list', () => {
61
+ const insights: GeneratedInsight[] = [
62
+ {
63
+ id: 'test-1',
64
+ observation: 'Bold statement template performed best',
65
+ confidence_level: 'high',
66
+ applies_to: 'template',
67
+ recommendation: 'Use bold-statement',
68
+ created_at: new Date().toISOString(),
69
+ },
70
+ {
71
+ id: 'test-2',
72
+ observation: 'Blue palette drove engagement',
73
+ confidence_level: 'medium',
74
+ applies_to: 'palette',
75
+ recommendation: 'Try blue palette',
76
+ created_at: new Date().toISOString(),
77
+ },
78
+ ];
79
+
80
+ render(<InsightsBadge insights={insights} />);
81
+ expect(screen.getByText(/2 insights/i)).toBeInTheDocument();
82
+ expect(screen.getByText(/Use bold-statement/i)).toBeInTheDocument();
83
+ expect(screen.getByText(/Try blue palette/i)).toBeInTheDocument();
84
+ });
85
+ });
src/components/carousel/InsightsBadge.tsx ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import type { GeneratedInsight } from '@/lib/analytics/insights';
5
+
6
+ interface InsightsBadgeProps {
7
+ insights: GeneratedInsight[];
8
+ }
9
+
10
+ /**
11
+ * Display insights that guided carousel creation.
12
+ * Shows in the UI during carousel review before publishing.
13
+ */
14
+ export default function InsightsBadge({ insights }: InsightsBadgeProps) {
15
+ if (!insights || insights.length === 0) {
16
+ return null;
17
+ }
18
+
19
+ const count = insights.length;
20
+ const highConfCount = insights.filter((i) => i.confidence_level === 'high').length;
21
+
22
+ return (
23
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
24
+ <div className="flex items-start gap-3">
25
+ <div className="text-xl">💡</div>
26
+ <div className="flex-1">
27
+ <h3 className="font-semibold text-gray-900 mb-2">
28
+ Based on your past performance
29
+ </h3>
30
+ <p className="text-sm text-gray-600 mb-3">
31
+ {count} {count === 1 ? 'insight' : 'insights'} from your recent carousels are guiding this one{highConfCount > 0 ? ' (high confidence)' : ''}:
32
+ </p>
33
+ <ul className="space-y-2 text-sm">
34
+ {insights.map((insight) => (
35
+ <li key={insight.id} className="flex items-start gap-2">
36
+ <span className="text-gray-400 mt-1">•</span>
37
+ <span className="text-gray-700">{insight.recommendation}</span>
38
+ </li>
39
+ ))}
40
+ </ul>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ );
45
+ }