From 9624551f7a1c9824a1eb4e88b211512b3a9629f5 Mon Sep 17 00:00:00 2001
From: Andrew Hayes
Date: Tue, 5 Nov 2024 21:55:02 -0800
Subject: [PATCH] always allow polls transitions when continuous export is
disabled
---
.../src/screens/poll_worker_screen.test.tsx | 178 +++++++++++++++++-
.../src/screens/poll_worker_screen.tsx | 92 ++++++---
2 files changed, 237 insertions(+), 33 deletions(-)
diff --git a/apps/scan/frontend/src/screens/poll_worker_screen.test.tsx b/apps/scan/frontend/src/screens/poll_worker_screen.test.tsx
index eecd9a3edd..a6cd86a339 100644
--- a/apps/scan/frontend/src/screens/poll_worker_screen.test.tsx
+++ b/apps/scan/frontend/src/screens/poll_worker_screen.test.tsx
@@ -386,7 +386,7 @@ describe('must have printer attached to transition polls and print reports', ()
});
describe('must have usb drive attached to transition polls', () => {
- test('polls open', async () => {
+ test('opening polls', async () => {
apiMock.expectGetPollsInfo('polls_closed_initial');
apiMock.setPrinterStatusV3({ connected: true });
apiMock.expectGetUsbDriveStatus('no_drive');
@@ -405,7 +405,7 @@ describe('must have usb drive attached to transition polls', () => {
await screen.findByText('Print Additional Polls Opened Report');
});
- test('polls open from fallback screen', async () => {
+ test('opening polls from fallback screen', async () => {
apiMock.expectGetPollsInfo('polls_closed_initial');
apiMock.setPrinterStatusV3({ connected: true });
apiMock.expectGetUsbDriveStatus('no_drive');
@@ -431,7 +431,7 @@ describe('must have usb drive attached to transition polls', () => {
await screen.findByText('Print Additional Polls Opened Report');
});
- test('polls paused', async () => {
+ test('resuming voting', async () => {
apiMock.expectGetPollsInfo('polls_paused');
apiMock.setPrinterStatusV3({ connected: true });
apiMock.expectGetUsbDriveStatus('no_drive');
@@ -451,7 +451,7 @@ describe('must have usb drive attached to transition polls', () => {
await screen.findByText('Voting Resumed');
});
- test('polls paused from fallback screen', async () => {
+ test('resuming voting from fallback screen', async () => {
apiMock.expectGetPollsInfo('polls_paused');
apiMock.setPrinterStatusV3({ connected: true });
apiMock.expectGetUsbDriveStatus('no_drive');
@@ -476,7 +476,7 @@ describe('must have usb drive attached to transition polls', () => {
await screen.findByText('Print Additional Polls Closed Report');
});
- test('polls close', async () => {
+ test('closing polls', async () => {
apiMock.expectGetPollsInfo('polls_open');
apiMock.setPrinterStatusV3({ connected: true });
apiMock.expectGetUsbDriveStatus('no_drive');
@@ -497,7 +497,7 @@ describe('must have usb drive attached to transition polls', () => {
expect(startNewVoterSessionMock).toHaveBeenCalledTimes(1);
});
- test('polls close from fallback screen', async () => {
+ test('closing polls from fallback screen', async () => {
apiMock.expectGetPollsInfo('polls_open');
apiMock.setPrinterStatusV3({ connected: true });
apiMock.expectGetUsbDriveStatus('no_drive');
@@ -524,6 +524,172 @@ describe('must have usb drive attached to transition polls', () => {
});
});
+describe('does not need usb drive attached to transition polls if continuous export disabled', () => {
+ beforeEach(() => {
+ apiMock.mockApiClient.getConfig.reset();
+ apiMock.expectGetConfig({
+ isContinuousExportEnabled: false,
+ });
+ });
+
+ test('opening polls', async () => {
+ apiMock.expectGetPollsInfo('polls_closed_initial');
+ apiMock.setPrinterStatusV4();
+ apiMock.expectGetUsbDriveStatus('no_drive');
+ renderScreen({});
+
+ await screen.findButton('Open Polls');
+ expect(
+ screen.queryByText('Insert a USB drive to continue.')
+ ).not.toBeInTheDocument();
+
+ apiMock.expectOpenPolls();
+ apiMock.expectPrintReportV4().resolve();
+ apiMock.expectGetPollsInfo('polls_open');
+ userEvent.click(screen.getButton('Open Polls'));
+ await screen.findByText('Opening Polls…');
+ await screen.findByText('Polls Opened');
+ });
+
+ test('opening polls from fallback screen', async () => {
+ apiMock.expectGetPollsInfo('polls_closed_initial');
+ apiMock.setPrinterStatusV4();
+ apiMock.expectGetUsbDriveStatus('no_drive');
+ renderScreen({});
+
+ await screen.findButton('Open Polls');
+ expect(
+ screen.queryByText('Insert a USB drive to continue.')
+ ).not.toBeInTheDocument();
+
+ userEvent.click(screen.getButton('Menu'));
+ apiMock.expectOpenPolls();
+ apiMock.expectPrintReportV4().resolve();
+ apiMock.expectGetPollsInfo('polls_open');
+ userEvent.click(screen.getButton('Open Polls'));
+ await screen.findByText('Opening Polls…');
+ await screen.findByText('Polls Opened');
+ });
+
+ test('pausing voting', async () => {
+ apiMock.expectGetPollsInfo('polls_open');
+ apiMock.setPrinterStatusV4();
+ apiMock.expectGetUsbDriveStatus('no_drive');
+ renderScreen({});
+
+ await screen.findButton('Close Polls');
+ expect(
+ screen.queryByText('Insert a USB drive to continue.')
+ ).not.toBeInTheDocument();
+
+ userEvent.click(screen.getButton('Menu'));
+ apiMock.expectPauseVoting();
+ apiMock.expectPrintReportV4().resolve();
+ apiMock.expectGetPollsInfo('polls_paused');
+ userEvent.click(screen.getButton('Pause Voting'));
+ await screen.findByText('Pausing Voting…');
+ await screen.findByText('Voting Paused');
+ });
+
+ test('resuming voting', async () => {
+ apiMock.expectGetPollsInfo('polls_paused');
+ apiMock.setPrinterStatusV4();
+ apiMock.expectGetUsbDriveStatus('no_drive');
+ renderScreen({});
+
+ await screen.findButton('Resume Voting');
+ expect(
+ screen.queryByText('Insert a USB drive to continue.')
+ ).not.toBeInTheDocument();
+
+ apiMock.expectResumeVoting();
+ apiMock.expectPrintReportV4().resolve();
+ apiMock.expectGetPollsInfo('polls_open');
+ userEvent.click(screen.getButton('Resume Voting'));
+ await screen.findByText('Resuming Voting…');
+ await screen.findByText('Voting Resumed');
+ });
+
+ test('resuming voting from fallback screen', async () => {
+ apiMock.expectGetPollsInfo('polls_paused');
+ apiMock.setPrinterStatusV4();
+ apiMock.expectGetUsbDriveStatus('no_drive');
+ renderScreen({});
+
+ await screen.findButton('Resume Voting');
+ expect(
+ screen.queryByText('Insert a USB drive to continue.')
+ ).not.toBeInTheDocument();
+
+ userEvent.click(screen.getButton('Menu'));
+ apiMock.expectResumeVoting();
+ apiMock.expectPrintReportV4().resolve();
+ apiMock.expectGetPollsInfo('polls_open');
+ userEvent.click(screen.getButton('Resume Voting'));
+ await screen.findByText('Resuming Voting…');
+ await screen.findByText('Voting Resumed');
+ });
+
+ test('closing polls', async () => {
+ apiMock.expectGetPollsInfo('polls_open');
+ apiMock.setPrinterStatusV4();
+ apiMock.expectGetUsbDriveStatus('no_drive');
+ renderScreen({});
+
+ await screen.findButton('Close Polls');
+ expect(
+ screen.queryByText('Insert a USB drive to continue.')
+ ).not.toBeInTheDocument();
+
+ apiMock.expectClosePolls();
+ apiMock.expectPrintReportV4().resolve();
+ apiMock.expectGetPollsInfo('polls_closed_final');
+ userEvent.click(screen.getButton('Close Polls'));
+ await screen.findByText('Closing Polls…');
+ await screen.findByText('Polls Closed');
+ });
+
+ test('closing polls from fallback screen', async () => {
+ apiMock.expectGetPollsInfo('polls_open');
+ apiMock.setPrinterStatusV4();
+ apiMock.expectGetUsbDriveStatus('no_drive');
+ renderScreen({});
+
+ await screen.findButton('Close Polls');
+ expect(
+ screen.queryByText('Insert a USB drive to continue.')
+ ).not.toBeInTheDocument();
+
+ userEvent.click(screen.getButton('Menu'));
+ apiMock.expectClosePolls();
+ apiMock.expectPrintReportV4().resolve();
+ apiMock.expectGetPollsInfo('polls_closed_final');
+ userEvent.click(screen.getButton('Close Polls'));
+ await screen.findByText('Closing Polls…');
+ await screen.findByText('Polls Closed');
+ });
+
+ test('closing polls from voting paused', async () => {
+ apiMock.expectGetPollsInfo('polls_paused');
+ apiMock.setPrinterStatusV4();
+ apiMock.expectGetUsbDriveStatus('no_drive');
+ renderScreen({});
+
+ await screen.findButton('Resume Voting');
+ expect(
+ screen.queryByText('Insert a USB drive to continue.')
+ ).not.toBeInTheDocument();
+
+ userEvent.click(screen.getButton('Menu'));
+ apiMock.expectClosePolls();
+ apiMock.expectPrintReportV4().resolve();
+ apiMock.expectGetPollsInfo('polls_closed_final');
+ userEvent.click(screen.getButton('Close Polls'));
+ await screen.findByText('Closing Polls…');
+ await screen.findByText('Polls Closed');
+ });
+});
+
describe('hardware V4 report printing', () => {
test('single report printing happy path', async () => {
apiMock.setPrinterStatusV4();
diff --git a/apps/scan/frontend/src/screens/poll_worker_screen.tsx b/apps/scan/frontend/src/screens/poll_worker_screen.tsx
index 7a17748b90..bc73a83e31 100644
--- a/apps/scan/frontend/src/screens/poll_worker_screen.tsx
+++ b/apps/scan/frontend/src/screens/poll_worker_screen.tsx
@@ -23,7 +23,6 @@ import type {
PrecinctScannerPollsInfo,
PrintResult,
} from '@votingworks/scan-backend';
-import type { UsbDriveStatus } from '@votingworks/usb-drive';
import {
getUsbDriveStatus,
printReport,
@@ -34,6 +33,7 @@ import {
resumeVoting as resumeVotingApi,
getPollsInfo,
useApiClient,
+ getConfig,
} from '../api';
import { FullScreenPromptLayout } from '../components/full_screen_prompt_layout';
import {
@@ -106,11 +106,11 @@ function PrinterAlertText({
}
function UsbDriveAlertText({
- usbDriveStatus,
+ mustInsertUsbDriveToContinue,
}: {
- usbDriveStatus: UsbDriveStatus;
+ mustInsertUsbDriveToContinue: boolean;
}): JSX.Element | null {
- if (usbDriveStatus.status === 'mounted') {
+ if (!mustInsertUsbDriveToContinue) {
return null;
}
@@ -123,21 +123,21 @@ function UsbDriveAlertText({
function shouldAllowTogglingPolls(
printerSummary: PollsFlowPrinterSummary,
- usbDriveStatus: UsbDriveStatus
+ mustInsertUsbDriveToContinue: boolean
): boolean {
- return printerSummary.ready && usbDriveStatus.status === 'mounted';
+ return printerSummary.ready && !mustInsertUsbDriveToContinue;
}
function OpenPollsPromptScreen({
onConfirm,
onClose,
printerSummary,
- usbDriveStatus,
+ mustInsertUsbDriveToContinue,
}: {
onConfirm: () => void;
onClose: () => void;
printerSummary: PollsFlowPrinterSummary;
- usbDriveStatus: UsbDriveStatus;
+ mustInsertUsbDriveToContinue: boolean;
}): JSX.Element {
return (
@@ -148,13 +148,20 @@ function OpenPollsPromptScreen({
-
+
);
@@ -164,12 +171,12 @@ function ResumeVotingPromptScreen({
onConfirm,
onClose,
printerSummary,
- usbDriveStatus,
+ mustInsertUsbDriveToContinue,
}: {
onConfirm: () => void;
onClose: () => void;
printerSummary: PollsFlowPrinterSummary;
- usbDriveStatus: UsbDriveStatus;
+ mustInsertUsbDriveToContinue: boolean;
}): JSX.Element {
return (
@@ -180,13 +187,20 @@ function ResumeVotingPromptScreen({
-
+
);
@@ -196,12 +210,12 @@ function ClosePollsPromptScreen({
onConfirm,
onClose,
printerSummary,
- usbDriveStatus,
+ mustInsertUsbDriveToContinue,
}: {
onConfirm: () => void;
onClose: () => void;
printerSummary: PollsFlowPrinterSummary;
- usbDriveStatus: UsbDriveStatus;
+ mustInsertUsbDriveToContinue: boolean;
}): JSX.Element {
return (
@@ -212,13 +226,20 @@ function ClosePollsPromptScreen({
-
+
);
@@ -270,6 +291,7 @@ function PollWorkerScreenContents({
}): JSX.Element {
const apiClient = useApiClient();
const pollsInfoQuery = getPollsInfo.useQuery();
+ const configQuery = getConfig.useQuery();
const usbDriveStatusQuery = getUsbDriveStatus.useQuery();
const printerStatusQuery = getPrinterStatus.useQuery();
const openPollsMutation = openPollsApi.useMutation();
@@ -303,7 +325,8 @@ function PollWorkerScreenContents({
if (
!usbDriveStatusQuery.isSuccess ||
!printerStatusQuery.isSuccess ||
- !pollsInfoQuery.isSuccess
+ !pollsInfoQuery.isSuccess ||
+ !configQuery.isSuccess
) {
return (
@@ -318,6 +341,9 @@ function PollWorkerScreenContents({
const printerStatus = printerStatusQuery.data;
const printerSummary = getPollsFlowPrinterSummary(printerStatus);
const { pollsState } = pollsInfo;
+ const { isContinuousExportEnabled } = configQuery.data;
+ const mustInsertUsbDriveToContinue =
+ isContinuousExportEnabled && usbDriveStatus.status !== 'mounted';
function showAllPollWorkerActions() {
return setPollWorkerFlowState(undefined);
@@ -346,7 +372,7 @@ function PollWorkerScreenContents({
}
async function closePolls() {
- assert(usbDriveStatus.status === 'mounted');
+ assert(!mustInsertUsbDriveToContinue);
setPollWorkerFlowState({
type: 'polls-transitioning',
transitionType: 'close_polls',
@@ -427,7 +453,7 @@ function PollWorkerScreenContents({
onConfirm={openPolls}
onClose={showAllPollWorkerActions}
printerSummary={printerSummary}
- usbDriveStatus={usbDriveStatus}
+ mustInsertUsbDriveToContinue={mustInsertUsbDriveToContinue}
/>
);
case 'resume-voting-prompt':
@@ -436,7 +462,7 @@ function PollWorkerScreenContents({
onConfirm={resumeVoting}
onClose={showAllPollWorkerActions}
printerSummary={printerSummary}
- usbDriveStatus={usbDriveStatus}
+ mustInsertUsbDriveToContinue={mustInsertUsbDriveToContinue}
/>
);
case 'close-polls-prompt':
@@ -445,7 +471,7 @@ function PollWorkerScreenContents({
onConfirm={closePolls}
onClose={showAllPollWorkerActions}
printerSummary={printerSummary}
- usbDriveStatus={usbDriveStatus}
+ mustInsertUsbDriveToContinue={mustInsertUsbDriveToContinue}
/>
);
case 'polls-transitioning':
@@ -541,7 +567,10 @@ function PollWorkerScreenContents({
variant="primary"
onPress={openPolls}
disabled={
- !shouldAllowTogglingPolls(printerSummary, usbDriveStatus)
+ !shouldAllowTogglingPolls(
+ printerSummary,
+ mustInsertUsbDriveToContinue
+ )
}
>
Open Polls
@@ -565,7 +594,10 @@ function PollWorkerScreenContents({
variant="primary"
onPress={closePolls}
disabled={
- !shouldAllowTogglingPolls(printerSummary, usbDriveStatus)
+ !shouldAllowTogglingPolls(
+ printerSummary,
+ mustInsertUsbDriveToContinue
+ )
}
>
Close Polls
@@ -589,7 +621,10 @@ function PollWorkerScreenContents({
variant="primary"
onPress={resumeVoting}
disabled={
- !shouldAllowTogglingPolls(printerSummary, usbDriveStatus)
+ !shouldAllowTogglingPolls(
+ printerSummary,
+ mustInsertUsbDriveToContinue
+ )
}
>
Resume Voting
@@ -600,7 +635,10 @@ function PollWorkerScreenContents({