@bernierllc/email-campaign-management 1.0.1
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/.eslintrc.cjs +45 -0
- package/README.md +316 -0
- package/__tests__/api/campaigns.test.ts +217 -0
- package/__tests__/api/client.test.ts +330 -0
- package/__tests__/components/CampaignBuilder.test.tsx +103 -0
- package/__tests__/components/CampaignDashboard.test.tsx +89 -0
- package/__tests__/components/CampaignList.test.tsx +144 -0
- package/__tests__/components/MetricsOverview.test.tsx +200 -0
- package/__tests__/components/PerformanceChart.test.tsx +206 -0
- package/__tests__/hooks/useCampaignStore.test.ts +450 -0
- package/__tests__/hooks/useWorkflowValidation.test.ts +176 -0
- package/__tests__/utils/formatting.test.ts +48 -0
- package/__tests__/utils/validation.test.ts +199 -0
- package/__tests__/utils/workflow-helpers.test.ts +134 -0
- package/coverage/clover.xml +314 -0
- package/coverage/coverage-final.json +16 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +221 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/api/campaigns.ts.html +199 -0
- package/coverage/lcov-report/src/api/client.ts.html +478 -0
- package/coverage/lcov-report/src/api/index.html +131 -0
- package/coverage/lcov-report/src/components/CampaignBuilder/index.html +116 -0
- package/coverage/lcov-report/src/components/CampaignBuilder/index.tsx.html +454 -0
- package/coverage/lcov-report/src/components/CampaignDashboard/MetricsOverview.tsx.html +208 -0
- package/coverage/lcov-report/src/components/CampaignDashboard/PerformanceChart.tsx.html +232 -0
- package/coverage/lcov-report/src/components/CampaignDashboard/index.html +146 -0
- package/coverage/lcov-report/src/components/CampaignDashboard/index.tsx.html +241 -0
- package/coverage/lcov-report/src/components/CampaignList/index.html +116 -0
- package/coverage/lcov-report/src/components/CampaignList/index.tsx.html +244 -0
- package/coverage/lcov-report/src/config.ts.html +202 -0
- package/coverage/lcov-report/src/hooks/index.html +146 -0
- package/coverage/lcov-report/src/hooks/useCampaignMetrics.ts.html +208 -0
- package/coverage/lcov-report/src/hooks/useCampaignStore.ts.html +343 -0
- package/coverage/lcov-report/src/hooks/useWorkflowValidation.ts.html +136 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/types/index.html +116 -0
- package/coverage/lcov-report/src/types/index.ts.html +127 -0
- package/coverage/lcov-report/src/utils/formatting.ts.html +163 -0
- package/coverage/lcov-report/src/utils/index.html +146 -0
- package/coverage/lcov-report/src/utils/validation.ts.html +394 -0
- package/coverage/lcov-report/src/utils/workflow-helpers.ts.html +277 -0
- package/coverage/lcov.info +657 -0
- package/dist/api/campaigns.d.ts +9 -0
- package/dist/api/campaigns.js +38 -0
- package/dist/api/client.d.ts +14 -0
- package/dist/api/client.js +116 -0
- package/dist/components/CampaignBuilder/index.d.ts +8 -0
- package/dist/components/CampaignBuilder/index.js +88 -0
- package/dist/components/CampaignDashboard/MetricsOverview.d.ts +6 -0
- package/dist/components/CampaignDashboard/MetricsOverview.js +34 -0
- package/dist/components/CampaignDashboard/PerformanceChart.d.ts +7 -0
- package/dist/components/CampaignDashboard/PerformanceChart.js +45 -0
- package/dist/components/CampaignDashboard/index.d.ts +5 -0
- package/dist/components/CampaignDashboard/index.js +44 -0
- package/dist/components/CampaignList/index.d.ts +6 -0
- package/dist/components/CampaignList/index.js +68 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +31 -0
- package/dist/hooks/useCampaignMetrics.d.ts +2 -0
- package/dist/hooks/useCampaignMetrics.js +42 -0
- package/dist/hooks/useCampaignStore.d.ts +14 -0
- package/dist/hooks/useCampaignStore.js +105 -0
- package/dist/hooks/useWorkflowValidation.d.ts +3 -0
- package/dist/hooks/useWorkflowValidation.js +17 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +52 -0
- package/dist/types/abtest.d.ts +15 -0
- package/dist/types/abtest.js +9 -0
- package/dist/types/audience.d.ts +18 -0
- package/dist/types/audience.js +9 -0
- package/dist/types/campaign.d.ts +31 -0
- package/dist/types/campaign.js +9 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +29 -0
- package/dist/types/metrics.d.ts +27 -0
- package/dist/types/metrics.js +9 -0
- package/dist/types/schedule.d.ts +15 -0
- package/dist/types/schedule.js +9 -0
- package/dist/types/workflow.d.ts +37 -0
- package/dist/types/workflow.js +9 -0
- package/dist/utils/formatting.d.ts +4 -0
- package/dist/utils/formatting.js +28 -0
- package/dist/utils/validation.d.ts +8 -0
- package/dist/utils/validation.js +81 -0
- package/dist/utils/workflow-helpers.d.ts +12 -0
- package/dist/utils/workflow-helpers.js +62 -0
- package/jest.config.cjs +33 -0
- package/jest.setup.cjs +9 -0
- package/package.json +72 -0
- package/src/api/campaigns.ts +38 -0
- package/src/api/client.ts +131 -0
- package/src/components/CampaignBuilder/index.tsx +123 -0
- package/src/components/CampaignDashboard/MetricsOverview.tsx +41 -0
- package/src/components/CampaignDashboard/PerformanceChart.tsx +49 -0
- package/src/components/CampaignDashboard/index.tsx +52 -0
- package/src/components/CampaignList/index.tsx +53 -0
- package/src/config.ts +39 -0
- package/src/hooks/useCampaignMetrics.ts +41 -0
- package/src/hooks/useCampaignStore.ts +86 -0
- package/src/hooks/useWorkflowValidation.ts +17 -0
- package/src/index.ts +32 -0
- package/src/types/abtest.ts +25 -0
- package/src/types/audience.ts +30 -0
- package/src/types/campaign.ts +44 -0
- package/src/types/index.ts +14 -0
- package/src/types/metrics.ts +36 -0
- package/src/types/schedule.ts +26 -0
- package/src/types/workflow.ts +53 -0
- package/src/utils/formatting.ts +26 -0
- package/src/utils/validation.ts +103 -0
- package/src/utils/workflow-helpers.ts +64 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { render, screen } from '@testing-library/react';
|
|
11
|
+
import { MetricsOverview } from '../../src/components/CampaignDashboard/MetricsOverview';
|
|
12
|
+
import { CampaignMetrics } from '../../src/types';
|
|
13
|
+
|
|
14
|
+
const createMockMetrics = (overrides: Partial<CampaignMetrics> = {}): CampaignMetrics => ({
|
|
15
|
+
campaignId: 'campaign-123',
|
|
16
|
+
sent: 10000,
|
|
17
|
+
delivered: 9500,
|
|
18
|
+
bounced: 500,
|
|
19
|
+
opened: 4500,
|
|
20
|
+
clicked: 1200,
|
|
21
|
+
converted: 300,
|
|
22
|
+
unsubscribed: 50,
|
|
23
|
+
openRate: 45.0,
|
|
24
|
+
clickRate: 12.0,
|
|
25
|
+
conversionRate: 3.0,
|
|
26
|
+
lastUpdated: new Date('2025-01-15'),
|
|
27
|
+
...overrides
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('MetricsOverview', () => {
|
|
31
|
+
describe('rendering', () => {
|
|
32
|
+
it('should render without crashing', () => {
|
|
33
|
+
const metrics = createMockMetrics();
|
|
34
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
35
|
+
expect(screen.getByText('Sent')).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should render with metrics-overview class', () => {
|
|
39
|
+
const metrics = createMockMetrics();
|
|
40
|
+
const { container } = render(<MetricsOverview metrics={metrics} />);
|
|
41
|
+
expect(container.querySelector('.metrics-overview')).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should render all four metric cards', () => {
|
|
45
|
+
const metrics = createMockMetrics();
|
|
46
|
+
const { container } = render(<MetricsOverview metrics={metrics} />);
|
|
47
|
+
const cards = container.querySelectorAll('.metric-card');
|
|
48
|
+
expect(cards).toHaveLength(4);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('sent metrics', () => {
|
|
53
|
+
it('should display sent count', () => {
|
|
54
|
+
const metrics = createMockMetrics({ sent: 10000 });
|
|
55
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
56
|
+
expect(screen.getByText('10,000')).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should display "Sent" label', () => {
|
|
60
|
+
const metrics = createMockMetrics();
|
|
61
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
62
|
+
expect(screen.getByText('Sent')).toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle zero sent', () => {
|
|
66
|
+
const metrics = createMockMetrics({ sent: 0 });
|
|
67
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
68
|
+
expect(screen.getByText('0')).toBeInTheDocument();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('opened metrics', () => {
|
|
73
|
+
it('should display opened count', () => {
|
|
74
|
+
const metrics = createMockMetrics({ opened: 4500 });
|
|
75
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
76
|
+
expect(screen.getByText('4,500')).toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should display "Opened" label', () => {
|
|
80
|
+
const metrics = createMockMetrics();
|
|
81
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
82
|
+
expect(screen.getByText('Opened')).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should display open rate percentage', () => {
|
|
86
|
+
const metrics = createMockMetrics({ openRate: 45.0 });
|
|
87
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
88
|
+
expect(screen.getByText('45.00%')).toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('clicked metrics', () => {
|
|
93
|
+
it('should display clicked count', () => {
|
|
94
|
+
const metrics = createMockMetrics({ clicked: 1200 });
|
|
95
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
96
|
+
expect(screen.getByText('1,200')).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should display "Clicked" label', () => {
|
|
100
|
+
const metrics = createMockMetrics();
|
|
101
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
102
|
+
expect(screen.getByText('Clicked')).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should display click rate percentage', () => {
|
|
106
|
+
const metrics = createMockMetrics({ clickRate: 12.0 });
|
|
107
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
108
|
+
expect(screen.getByText('12.00%')).toBeInTheDocument();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('converted metrics', () => {
|
|
113
|
+
it('should display converted count', () => {
|
|
114
|
+
const metrics = createMockMetrics({ converted: 300 });
|
|
115
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
116
|
+
expect(screen.getByText('300')).toBeInTheDocument();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should display "Converted" label', () => {
|
|
120
|
+
const metrics = createMockMetrics();
|
|
121
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
122
|
+
expect(screen.getByText('Converted')).toBeInTheDocument();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should display conversion rate percentage', () => {
|
|
126
|
+
const metrics = createMockMetrics({ conversionRate: 3.0 });
|
|
127
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
128
|
+
expect(screen.getByText('3.00%')).toBeInTheDocument();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('edge cases', () => {
|
|
133
|
+
it('should handle very large numbers', () => {
|
|
134
|
+
const metrics = createMockMetrics({
|
|
135
|
+
sent: 1000000,
|
|
136
|
+
opened: 500000,
|
|
137
|
+
clicked: 250000,
|
|
138
|
+
converted: 50000
|
|
139
|
+
});
|
|
140
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
141
|
+
expect(screen.getByText('1,000,000')).toBeInTheDocument();
|
|
142
|
+
expect(screen.getByText('500,000')).toBeInTheDocument();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should handle decimal rates', () => {
|
|
146
|
+
const metrics = createMockMetrics({
|
|
147
|
+
openRate: 45.5,
|
|
148
|
+
clickRate: 12.3,
|
|
149
|
+
conversionRate: 3.7
|
|
150
|
+
});
|
|
151
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
152
|
+
// formatPercentage likely rounds or truncates
|
|
153
|
+
expect(screen.getByText(/45/)).toBeInTheDocument();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should handle zero rates', () => {
|
|
157
|
+
const metrics = createMockMetrics({
|
|
158
|
+
openRate: 0,
|
|
159
|
+
clickRate: 0,
|
|
160
|
+
conversionRate: 0
|
|
161
|
+
});
|
|
162
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
163
|
+
const zeroPercents = screen.getAllByText('0.00%');
|
|
164
|
+
expect(zeroPercents.length).toBeGreaterThanOrEqual(3);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should handle 100% rates', () => {
|
|
168
|
+
const metrics = createMockMetrics({
|
|
169
|
+
openRate: 100,
|
|
170
|
+
clickRate: 100,
|
|
171
|
+
conversionRate: 100
|
|
172
|
+
});
|
|
173
|
+
render(<MetricsOverview metrics={metrics} />);
|
|
174
|
+
const fullPercents = screen.getAllByText('100.00%');
|
|
175
|
+
expect(fullPercents.length).toBeGreaterThanOrEqual(3);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('re-rendering', () => {
|
|
180
|
+
it('should update when metrics change', () => {
|
|
181
|
+
const initialMetrics = createMockMetrics({ sent: 1000 });
|
|
182
|
+
const { rerender } = render(<MetricsOverview metrics={initialMetrics} />);
|
|
183
|
+
expect(screen.getByText('1,000')).toBeInTheDocument();
|
|
184
|
+
|
|
185
|
+
const updatedMetrics = createMockMetrics({ sent: 2000 });
|
|
186
|
+
rerender(<MetricsOverview metrics={updatedMetrics} />);
|
|
187
|
+
expect(screen.getByText('2,000')).toBeInTheDocument();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should update rates when they change', () => {
|
|
191
|
+
const initialMetrics = createMockMetrics({ openRate: 10 });
|
|
192
|
+
const { rerender } = render(<MetricsOverview metrics={initialMetrics} />);
|
|
193
|
+
expect(screen.getByText('10.00%')).toBeInTheDocument();
|
|
194
|
+
|
|
195
|
+
const updatedMetrics = createMockMetrics({ openRate: 50 });
|
|
196
|
+
rerender(<MetricsOverview metrics={updatedMetrics} />);
|
|
197
|
+
expect(screen.getByText('50.00%')).toBeInTheDocument();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { render, screen } from '@testing-library/react';
|
|
11
|
+
import { PerformanceChart, PerformanceChartProps } from '../../src/components/CampaignDashboard/PerformanceChart';
|
|
12
|
+
import { CampaignMetrics } from '../../src/types';
|
|
13
|
+
|
|
14
|
+
const createMockMetrics = (overrides: Partial<CampaignMetrics> = {}): CampaignMetrics => ({
|
|
15
|
+
campaignId: 'campaign-123',
|
|
16
|
+
sent: 10000,
|
|
17
|
+
delivered: 9500,
|
|
18
|
+
bounced: 500,
|
|
19
|
+
opened: 4500,
|
|
20
|
+
clicked: 1200,
|
|
21
|
+
converted: 300,
|
|
22
|
+
unsubscribed: 50,
|
|
23
|
+
openRate: 45.0,
|
|
24
|
+
clickRate: 12.0,
|
|
25
|
+
conversionRate: 3.0,
|
|
26
|
+
lastUpdated: new Date('2025-01-15'),
|
|
27
|
+
...overrides
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('PerformanceChart', () => {
|
|
31
|
+
describe('rendering', () => {
|
|
32
|
+
it('should render without crashing', () => {
|
|
33
|
+
const metrics = createMockMetrics();
|
|
34
|
+
render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
35
|
+
expect(screen.getByText('Open Rate')).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should render with performance-chart class', () => {
|
|
39
|
+
const metrics = createMockMetrics();
|
|
40
|
+
const { container } = render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
41
|
+
expect(container.querySelector('.performance-chart')).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should render chart placeholder', () => {
|
|
45
|
+
const metrics = createMockMetrics();
|
|
46
|
+
const { container } = render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
47
|
+
expect(container.querySelector('.chart-placeholder')).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('open_rate type', () => {
|
|
52
|
+
it('should display "Open Rate" title', () => {
|
|
53
|
+
const metrics = createMockMetrics({ openRate: 45.5 });
|
|
54
|
+
render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
55
|
+
expect(screen.getByRole('heading', { name: 'Open Rate' })).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should display the open rate value', () => {
|
|
59
|
+
const metrics = createMockMetrics({ openRate: 45.5 });
|
|
60
|
+
render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
61
|
+
expect(screen.getByText('Chart: 45.50%')).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should format open rate with two decimal places', () => {
|
|
65
|
+
const metrics = createMockMetrics({ openRate: 33.333 });
|
|
66
|
+
render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
67
|
+
expect(screen.getByText('Chart: 33.33%')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle zero open rate', () => {
|
|
71
|
+
const metrics = createMockMetrics({ openRate: 0 });
|
|
72
|
+
render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
73
|
+
expect(screen.getByText('Chart: 0.00%')).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle 100% open rate', () => {
|
|
77
|
+
const metrics = createMockMetrics({ openRate: 100 });
|
|
78
|
+
render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
79
|
+
expect(screen.getByText('Chart: 100.00%')).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('click_rate type', () => {
|
|
84
|
+
it('should display "Click Rate" title', () => {
|
|
85
|
+
const metrics = createMockMetrics({ clickRate: 12.3 });
|
|
86
|
+
render(<PerformanceChart metrics={metrics} type="click_rate" />);
|
|
87
|
+
expect(screen.getByRole('heading', { name: 'Click Rate' })).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should display the click rate value', () => {
|
|
91
|
+
const metrics = createMockMetrics({ clickRate: 12.3 });
|
|
92
|
+
render(<PerformanceChart metrics={metrics} type="click_rate" />);
|
|
93
|
+
expect(screen.getByText('Chart: 12.30%')).toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should format click rate with two decimal places', () => {
|
|
97
|
+
const metrics = createMockMetrics({ clickRate: 8.765 });
|
|
98
|
+
render(<PerformanceChart metrics={metrics} type="click_rate" />);
|
|
99
|
+
expect(screen.getByText('Chart: 8.77%')).toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should handle zero click rate', () => {
|
|
103
|
+
const metrics = createMockMetrics({ clickRate: 0 });
|
|
104
|
+
render(<PerformanceChart metrics={metrics} type="click_rate" />);
|
|
105
|
+
expect(screen.getByText('Chart: 0.00%')).toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should handle high click rate', () => {
|
|
109
|
+
const metrics = createMockMetrics({ clickRate: 85.5 });
|
|
110
|
+
render(<PerformanceChart metrics={metrics} type="click_rate" />);
|
|
111
|
+
expect(screen.getByText('Chart: 85.50%')).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('conversion_rate type', () => {
|
|
116
|
+
it('should display "Conversion Rate" title', () => {
|
|
117
|
+
const metrics = createMockMetrics({ conversionRate: 3.5 });
|
|
118
|
+
render(<PerformanceChart metrics={metrics} type="conversion_rate" />);
|
|
119
|
+
expect(screen.getByRole('heading', { name: 'Conversion Rate' })).toBeInTheDocument();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should display the conversion rate value', () => {
|
|
123
|
+
const metrics = createMockMetrics({ conversionRate: 3.5 });
|
|
124
|
+
render(<PerformanceChart metrics={metrics} type="conversion_rate" />);
|
|
125
|
+
expect(screen.getByText('Chart: 3.50%')).toBeInTheDocument();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should format conversion rate with two decimal places', () => {
|
|
129
|
+
const metrics = createMockMetrics({ conversionRate: 2.125 });
|
|
130
|
+
render(<PerformanceChart metrics={metrics} type="conversion_rate" />);
|
|
131
|
+
expect(screen.getByText('Chart: 2.13%')).toBeInTheDocument();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle zero conversion rate', () => {
|
|
135
|
+
const metrics = createMockMetrics({ conversionRate: 0 });
|
|
136
|
+
render(<PerformanceChart metrics={metrics} type="conversion_rate" />);
|
|
137
|
+
expect(screen.getByText('Chart: 0.00%')).toBeInTheDocument();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle very small conversion rate', () => {
|
|
141
|
+
const metrics = createMockMetrics({ conversionRate: 0.01 });
|
|
142
|
+
render(<PerformanceChart metrics={metrics} type="conversion_rate" />);
|
|
143
|
+
expect(screen.getByText('Chart: 0.01%')).toBeInTheDocument();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('placeholder text', () => {
|
|
148
|
+
it('should show integration note in placeholder', () => {
|
|
149
|
+
const metrics = createMockMetrics();
|
|
150
|
+
render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
151
|
+
expect(screen.getByText(/Integration with recharts or similar library needed/)).toBeInTheDocument();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('edge cases', () => {
|
|
156
|
+
it('should handle metrics with very large values', () => {
|
|
157
|
+
const metrics = createMockMetrics({
|
|
158
|
+
openRate: 99.99,
|
|
159
|
+
clickRate: 88.88,
|
|
160
|
+
conversionRate: 77.77
|
|
161
|
+
});
|
|
162
|
+
render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
163
|
+
expect(screen.getByText('Chart: 99.99%')).toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle metrics with decimal precision', () => {
|
|
167
|
+
const metrics = createMockMetrics({
|
|
168
|
+
openRate: 45.555555
|
|
169
|
+
});
|
|
170
|
+
render(<PerformanceChart metrics={metrics} type="open_rate" />);
|
|
171
|
+
expect(screen.getByText('Chart: 45.56%')).toBeInTheDocument();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should render correctly with different campaign IDs', () => {
|
|
175
|
+
const metrics1 = createMockMetrics({ campaignId: 'campaign-1', openRate: 50 });
|
|
176
|
+
const metrics2 = createMockMetrics({ campaignId: 'campaign-2', openRate: 60 });
|
|
177
|
+
|
|
178
|
+
const { rerender } = render(<PerformanceChart metrics={metrics1} type="open_rate" />);
|
|
179
|
+
expect(screen.getByText('Chart: 50.00%')).toBeInTheDocument();
|
|
180
|
+
|
|
181
|
+
rerender(<PerformanceChart metrics={metrics2} type="open_rate" />);
|
|
182
|
+
expect(screen.getByText('Chart: 60.00%')).toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('prop type variations', () => {
|
|
187
|
+
it('should handle each rate type correctly', () => {
|
|
188
|
+
const metrics = createMockMetrics({
|
|
189
|
+
openRate: 10,
|
|
190
|
+
clickRate: 20,
|
|
191
|
+
conversionRate: 30
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const types: PerformanceChartProps['type'][] = ['open_rate', 'click_rate', 'conversion_rate'];
|
|
195
|
+
const expectedTitles = ['Open Rate', 'Click Rate', 'Conversion Rate'];
|
|
196
|
+
const expectedValues = ['Chart: 10.00%', 'Chart: 20.00%', 'Chart: 30.00%'];
|
|
197
|
+
|
|
198
|
+
types.forEach((type, index) => {
|
|
199
|
+
const { unmount } = render(<PerformanceChart metrics={metrics} type={type} />);
|
|
200
|
+
expect(screen.getByRole('heading', { name: expectedTitles[index] })).toBeInTheDocument();
|
|
201
|
+
expect(screen.getByText(expectedValues[index])).toBeInTheDocument();
|
|
202
|
+
unmount();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|