Skip to content

Commit 2425bda

Browse files
authored
libs/ui: Add coverage for BallotsPrintedReport (#7709)
* libs/ui: Add coverage for BallotsPrintedReport * libs/ui: PR feedback – declare election definitions outside of test, share between tests
1 parent b787806 commit 2425bda

File tree

3 files changed

+347
-12
lines changed

3 files changed

+347
-12
lines changed
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
import { expect, test } from 'vitest';
2+
import {
3+
electionPrimaryPrecinctSplitsFixtures,
4+
electionSimpleSinglePrecinctFixtures,
5+
} from '@votingworks/fixtures';
6+
import {
7+
BallotPrintCount,
8+
hasSplits,
9+
LanguageCode,
10+
PrecinctWithSplits,
11+
PrecinctWithoutSplits,
12+
} from '@votingworks/types';
13+
import { format } from '@votingworks/utils';
14+
import { assertDefined } from '@votingworks/basics';
15+
import { render, screen } from '../../test/react_testing_library';
16+
import { BallotsPrintedReport } from './ballots_printed_report';
17+
18+
const electionDefinitionPrimary =
19+
electionPrimaryPrecinctSplitsFixtures.readElectionDefinition();
20+
const electionDefinitionSimple =
21+
electionSimpleSinglePrecinctFixtures.readElectionDefinition();
22+
23+
test('renders report with test mode banner', () => {
24+
render(
25+
<BallotsPrintedReport
26+
electionDefinition={electionDefinitionPrimary}
27+
electionPackageHash="test-election-package-hash"
28+
generatedAtTime={new Date()}
29+
printCounts={[]}
30+
isTestMode
31+
/>
32+
);
33+
34+
// Check for report header
35+
screen.getByText('Ballots Printed Report');
36+
screen.getByText(new RegExp(electionDefinitionPrimary.election.title));
37+
screen.getByText(/Report Generated/i);
38+
39+
// Check for test mode banner
40+
screen.getByText('Test Report');
41+
});
42+
43+
test('renders report without test mode banner', () => {
44+
render(
45+
<BallotsPrintedReport
46+
electionDefinition={electionDefinitionPrimary}
47+
electionPackageHash="test-election-package-hash"
48+
generatedAtTime={new Date()}
49+
printCounts={[]}
50+
isTestMode={false}
51+
/>
52+
);
53+
54+
expect(screen.queryByText('Test Report')).not.toBeInTheDocument();
55+
});
56+
57+
test('renders report for Primary Election with precinct splits, parties, multiple languages', () => {
58+
const { election } = electionDefinitionPrimary;
59+
60+
const splitPrecinct = assertDefined(
61+
election.precincts.find((p): p is PrecinctWithSplits => hasSplits(p))
62+
);
63+
const nonSplitPrecinct = assertDefined(
64+
election.precincts.find((p): p is PrecinctWithoutSplits => !hasSplits(p))
65+
);
66+
67+
const split = assertDefined(splitPrecinct.splits[0]);
68+
69+
const mammalParty = assertDefined(
70+
election.parties.find((p) => p.name === 'Mammal')
71+
);
72+
const fishParty = assertDefined(
73+
election.parties.find((p) => p.name === 'Fish')
74+
);
75+
76+
const splitMammalEnglishBallotStyleId = assertDefined(
77+
election.ballotStyles.find(
78+
(ballotStyle) =>
79+
ballotStyle.precincts.includes(splitPrecinct.id) &&
80+
ballotStyle.partyId === mammalParty.id &&
81+
ballotStyle.languages?.includes(LanguageCode.ENGLISH) &&
82+
split.districtIds.every((d) => ballotStyle.districts.includes(d))
83+
)?.id
84+
);
85+
86+
const nonSplitFishSpanishBallotStyleId = assertDefined(
87+
election.ballotStyles.find(
88+
(ballotStyle) =>
89+
ballotStyle.precincts.includes(nonSplitPrecinct.id) &&
90+
ballotStyle.partyId === fishParty.id &&
91+
ballotStyle.languages?.includes(LanguageCode.SPANISH) &&
92+
nonSplitPrecinct.districtIds.every((d) =>
93+
ballotStyle.districts.includes(d)
94+
)
95+
)?.id
96+
);
97+
98+
const ballotPrintCounts: BallotPrintCount[] = [
99+
{
100+
// From a precinct split
101+
precinctId: splitPrecinct.id,
102+
precinctOrSplitName: split.name,
103+
ballotStyleId: splitMammalEnglishBallotStyleId,
104+
languageCode: LanguageCode.ENGLISH,
105+
absenteeCount: 2,
106+
precinctCount: 8,
107+
totalCount: 10,
108+
partyName: mammalParty.name,
109+
},
110+
{
111+
// From a precinct without splits
112+
precinctId: nonSplitPrecinct.id,
113+
precinctOrSplitName: nonSplitPrecinct.name,
114+
ballotStyleId: nonSplitFishSpanishBallotStyleId,
115+
absenteeCount: 2,
116+
precinctCount: 8,
117+
totalCount: 10,
118+
languageCode: LanguageCode.SPANISH,
119+
partyName: fishParty.name,
120+
},
121+
];
122+
123+
render(
124+
<BallotsPrintedReport
125+
electionDefinition={electionDefinitionPrimary}
126+
electionPackageHash="test-election-package-hash"
127+
generatedAtTime={new Date()}
128+
printCounts={ballotPrintCounts}
129+
isTestMode
130+
/>
131+
);
132+
133+
// Table headers
134+
screen.getByText('Precinct / Split Name');
135+
screen.getByText('Party');
136+
screen.getByText('Language');
137+
screen.getByText('Total');
138+
screen.getByText('Precinct');
139+
screen.getByText('Absentee');
140+
141+
// Split row + non-split row
142+
screen.getByText(split.name);
143+
screen.getByText(nonSplitPrecinct.name);
144+
screen.getByText(mammalParty.name);
145+
screen.getByText(fishParty.name);
146+
screen.getByText(
147+
format.languageDisplayName({
148+
languageCode: LanguageCode.ENGLISH,
149+
displayLanguageCode: 'en',
150+
})
151+
);
152+
screen.getByText(
153+
format.languageDisplayName({
154+
languageCode: LanguageCode.SPANISH,
155+
displayLanguageCode: 'en',
156+
})
157+
);
158+
159+
screen.getByText('Sum Totals');
160+
screen.getByText('20');
161+
screen.getByText('16');
162+
screen.getByText('4');
163+
});
164+
165+
test('renders report for General Election with no parties, precincts, single language', () => {
166+
const { election } = electionDefinitionSimple;
167+
168+
const precinct = election.precincts[0];
169+
const ballotStyleId = election.ballotStyles[0].id;
170+
171+
const ballotPrintCounts: BallotPrintCount[] = [
172+
{
173+
precinctId: precinct.id,
174+
precinctOrSplitName: precinct.name,
175+
ballotStyleId,
176+
languageCode: LanguageCode.ENGLISH,
177+
absenteeCount: 0,
178+
precinctCount: 42,
179+
totalCount: 42,
180+
},
181+
];
182+
183+
render(
184+
<BallotsPrintedReport
185+
electionDefinition={electionDefinitionSimple}
186+
electionPackageHash="test-election-package-hash"
187+
generatedAtTime={new Date()}
188+
printCounts={ballotPrintCounts}
189+
isTestMode
190+
/>
191+
);
192+
193+
screen.getByText(new RegExp(election.title));
194+
// Does not include "Split" in the name column header
195+
screen.getByText('Precinct Name');
196+
screen.getByText(precinct.name);
197+
// 42 appears in Precinct Count, Total Count, and in sum totals for both columns
198+
expect(screen.getAllByText('42').length).toEqual(4);
199+
screen.getByText('Sum Totals');
200+
// Language and Party columns are not rendered
201+
expect(screen.queryByText('Language')).not.toBeInTheDocument();
202+
expect(
203+
screen.queryByText(
204+
format.languageDisplayName({
205+
languageCode: LanguageCode.ENGLISH,
206+
displayLanguageCode: 'en',
207+
})
208+
)
209+
).not.toBeInTheDocument();
210+
expect(screen.queryByText('Party')).not.toBeInTheDocument();
211+
});
212+
213+
test('sums totals across multiple rows', () => {
214+
const { election } = electionDefinitionPrimary;
215+
216+
const splitPrecinct = assertDefined(
217+
election.precincts.find((p): p is PrecinctWithSplits => hasSplits(p))
218+
);
219+
const split = assertDefined(splitPrecinct.splits[0]);
220+
const nonSplitPrecinct = assertDefined(
221+
election.precincts.find((p): p is PrecinctWithoutSplits => !hasSplits(p))
222+
);
223+
224+
const mammalParty = assertDefined(
225+
election.parties.find((p) => p.name === 'Mammal')
226+
);
227+
228+
const splitMammalEnglishBallotStyleId = assertDefined(
229+
election.ballotStyles.find(
230+
(ballotStyle) =>
231+
ballotStyle.precincts.includes(splitPrecinct.id) &&
232+
ballotStyle.partyId === mammalParty.id &&
233+
ballotStyle.languages?.includes(LanguageCode.ENGLISH) &&
234+
split.districtIds.every((d) => ballotStyle.districts.includes(d))
235+
)?.id
236+
);
237+
238+
const nonSplitMammalEnglishBallotStyleId = assertDefined(
239+
election.ballotStyles.find(
240+
(ballotStyle) =>
241+
ballotStyle.precincts.includes(nonSplitPrecinct.id) &&
242+
ballotStyle.partyId === mammalParty.id &&
243+
ballotStyle.languages?.includes(LanguageCode.ENGLISH) &&
244+
nonSplitPrecinct.districtIds.every((d) =>
245+
ballotStyle.districts.includes(d)
246+
)
247+
)?.id
248+
);
249+
250+
const ballotPrintCounts: BallotPrintCount[] = [
251+
{
252+
precinctId: splitPrecinct.id,
253+
precinctOrSplitName: split.name,
254+
ballotStyleId: splitMammalEnglishBallotStyleId,
255+
languageCode: LanguageCode.ENGLISH,
256+
absenteeCount: 1,
257+
precinctCount: 2,
258+
totalCount: 3,
259+
partyName: mammalParty.name,
260+
},
261+
{
262+
precinctId: nonSplitPrecinct.id,
263+
precinctOrSplitName: nonSplitPrecinct.name,
264+
ballotStyleId: nonSplitMammalEnglishBallotStyleId,
265+
languageCode: LanguageCode.ENGLISH,
266+
absenteeCount: 4,
267+
precinctCount: 5,
268+
totalCount: 9,
269+
partyName: mammalParty.name,
270+
},
271+
];
272+
273+
render(
274+
<BallotsPrintedReport
275+
electionDefinition={electionDefinitionPrimary}
276+
electionPackageHash="test-election-package-hash"
277+
generatedAtTime={new Date()}
278+
printCounts={ballotPrintCounts}
279+
isTestMode
280+
/>
281+
);
282+
283+
screen.getByText('Sum Totals');
284+
screen.getByText('12');
285+
screen.getByText('7');
286+
expect(screen.getAllByText('5')).toHaveLength(2);
287+
});
288+
289+
test('renders count columns', () => {
290+
const { election } = electionDefinitionSimple;
291+
const precinct = election.precincts[0];
292+
const ballotStyleId = election.ballotStyles[0].id;
293+
294+
const ballotPrintCounts: BallotPrintCount[] = [
295+
{
296+
precinctId: precinct.id,
297+
precinctOrSplitName: precinct.name,
298+
ballotStyleId,
299+
languageCode: LanguageCode.ENGLISH,
300+
absenteeCount: 3,
301+
precinctCount: 5,
302+
totalCount: 8,
303+
},
304+
];
305+
306+
render(
307+
<BallotsPrintedReport
308+
electionDefinition={electionDefinitionSimple}
309+
electionPackageHash="test-election-package-hash"
310+
generatedAtTime={new Date()}
311+
printCounts={ballotPrintCounts}
312+
isTestMode
313+
/>
314+
);
315+
316+
screen.getByText('Total');
317+
screen.getByText('Precinct');
318+
screen.getByText('Absentee');
319+
320+
// Row values
321+
expect(screen.getAllByText('8')).toHaveLength(2);
322+
expect(screen.getAllByText('5')).toHaveLength(2);
323+
expect(screen.getAllByText('3')).toHaveLength(2);
324+
});

0 commit comments

Comments
 (0)