Skip to content

Commit

Permalink
Merge pull request #1088 from votingworks/fix/bmd/allow-setting-1am-1pm
Browse files Browse the repository at this point in the history
fix(bmd): allow setting 1am or 1pm
  • Loading branch information
eventualbuddha authored Oct 19, 2021
2 parents 73964df + 31adbcf commit b4d9f87
Show file tree
Hide file tree
Showing 8 changed files with 1,991 additions and 10 deletions.
3 changes: 2 additions & 1 deletion apps/bmd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-vx": "workspace:*",
"fast-check": "^2.18.0",
"is-ci-cli": "^2.0.0",
"jest-date-mock": "^1.0.8",
"jest-environment-jsdom-sixteen": "^1.0.3",
Expand All @@ -178,4 +179,4 @@
"../module-smartcards"
]
}
}
}
66 changes: 64 additions & 2 deletions apps/bmd/src/components/PickDateTimeModal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { DateTime } from 'luxon'
import React from 'react'
import PickDateTimeModal from './PickDateTimeModal'
import fc from 'fast-check'
import { arbitraryDateTime } from '@votingworks/test-utils'
import PickDateTimeModal, { MAX_YEAR, MIN_YEAR } from './PickDateTimeModal'

function getSelect(testId: string): HTMLSelectElement {
return screen.getByTestId(testId) as HTMLSelectElement
Expand Down Expand Up @@ -152,3 +154,63 @@ test('calls back on cancel', () => {
fireEvent.click(screen.getByText('Cancel'))
expect(onCancel).toHaveBeenCalledTimes(1)
})

test('can set any valid date & time', () => {
fc.assert(
fc.property(
arbitraryDateTime({
minYear: MIN_YEAR,
maxYear: MAX_YEAR,
zoneName: aDate.zoneName,
}).map((dateTime) => dateTime.set({ second: 0 })),
(dateTime) => {
cleanup()

const onCancel = jest.fn()
const onSave = jest.fn()
render(
<PickDateTimeModal
onCancel={onCancel}
onSave={onSave}
saveLabel="Save"
value={aDate}
/>
)

expect(onSave).not.toHaveBeenCalled()

// Make a change & save
fireEvent.change(getSelect('selectYear'), {
target: { value: dateTime.year.toString() },
})
fireEvent.change(getSelect('selectMonth'), {
target: { value: dateTime.month.toString() },
})
fireEvent.change(getSelect('selectDay'), {
target: { value: dateTime.day.toString() },
})
fireEvent.change(getSelect('selectHour'), {
target: {
value: (dateTime.hour > 12
? dateTime.hour % 12
: dateTime.hour === 0
? 12
: dateTime.hour
).toString(),
},
})
fireEvent.change(getSelect('selectMinute'), {
target: { value: dateTime.minute.toString() },
})
fireEvent.change(getSelect('selectMeridian'), {
target: { value: dateTime.hour < 12 ? 'AM' : 'PM' },
})
fireEvent.click(screen.getByText('Save'))

// Expect a changed date
expect(onSave).toHaveBeenCalledWith(aDate.set(dateTime.toObject()))
}
),
{ numRuns: 50 }
)
})
19 changes: 12 additions & 7 deletions apps/bmd/src/components/PickDateTimeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import Modal from './Modal'
import Prose from './Prose'
import Select from './Select'

export const MIN_YEAR = 2020
export const MAX_YEAR = 2030

export interface Props {
disabled?: boolean
onCancel(): void
Expand Down Expand Up @@ -108,11 +111,13 @@ const PickDateTimeModal = ({
<option value="" disabled>
Year
</option>
{[...integers({ from: 2020, through: 2030 })].map((year) => (
<option key={year} value={year}>
{year}
</option>
))}
{[...integers({ from: MIN_YEAR, through: MAX_YEAR })].map(
(year) => (
<option key={year} value={year}>
{year}
</option>
)
)}
</Select>
<Select
data-testid="selectMonth"
Expand Down Expand Up @@ -175,8 +180,8 @@ const PickDateTimeModal = ({
Hour
</option>
{[...integers({ from: 1, through: 12 })].map((hour) => (
<option key={hour} value={hour + 1}>
{hour + 1}
<option key={hour} value={hour}>
{hour}
</option>
))}
</Select>
Expand Down
2 changes: 2 additions & 0 deletions libs/test-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@
"@types/node": "^14.14.35",
"@votingworks/types": "workspace:*",
"fast-check": "^2.18.0",
"luxon": "1.26.0",
"rxjs": "^6.6.6",
"zip-stream": "^4.1.0"
},
"devDependencies": {
"@types/kiosk-browser": "workspace:*",
"@types/luxon": "^1.26.5",
"@types/react": "^17.0.4",
"@types/zip-stream": "workspace:*",
"@typescript-eslint/eslint-plugin": "^4.28.4",
Expand Down
14 changes: 14 additions & 0 deletions libs/test-utils/src/arbitraries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '@votingworks/types'
import { strict as assert } from 'assert'
import fc from 'fast-check'
import { arbitraryDateTime } from '.'
import {
arbitraryCastVoteRecord,
arbitraryCastVoteRecords,
Expand All @@ -24,6 +25,19 @@ test('arbitraryId', () => {
)
})

test('arbitraryDateTime', () => {
fc.assert(
fc.property(
arbitraryDateTime({ minYear: 2020, maxYear: 2022, zoneName: 'UTC' }),
(dateTime) => {
expect(dateTime.year).toBeGreaterThanOrEqual(2020)
expect(dateTime.year).toBeLessThanOrEqual(2022)
expect(dateTime.zoneName).toEqual('UTC')
}
)
)
})

test('arbitraryYesNoOption', () => {
fc.assert(
fc.property(arbitraryYesNoOption(), (option) => {
Expand Down
40 changes: 40 additions & 0 deletions libs/test-utils/src/arbitraries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import fc from 'fast-check'
import { DateTime } from 'luxon'
import {
BallotLayout,
BallotPaperSize,
Expand Down Expand Up @@ -57,6 +58,45 @@ export function arbitraryId(): fc.Arbitrary<z.TypeOf<typeof Id>> {
)
}

export function arbitraryDateTime({
minYear,
maxYear,
zoneName,
}: {
minYear?: number
maxYear?: number
zoneName?: DateTime['zoneName']
} = {}): fc.Arbitrary<DateTime> {
return fc
.record({
year: fc.integer({ min: minYear, max: maxYear }),
month: fc.integer({ min: 1, max: 12 }),
day: fc.integer({ min: 1, max: 31 }),
hour: fc.integer({ min: 0, max: 23 }),
minute: fc.integer({ min: 0, max: 59 }),
second: fc.integer({ min: 0, max: 59 }),
})
.map((parts) => {
try {
const result = DateTime.fromObject({ ...parts, zone: zoneName })
if (
result.year === parts.year &&
result.month === parts.month &&
result.day === parts.day &&
result.hour === parts.hour &&
result.minute === parts.minute &&
result.second === parts.second
) {
return result
}
} catch {
// ignore invalid dates
}
return undefined
})
.filter((dateTime): dateTime is DateTime => !!dateTime)
}

/**
* Builds values for use as "yes" or "no" options on ballots.
*/
Expand Down
Loading

0 comments on commit b4d9f87

Please sign in to comment.