Skip to content

Commit

Permalink
load election from smart card when it has one.
Browse files Browse the repository at this point in the history
  • Loading branch information
benadida committed May 20, 2019
1 parent 9465bb5 commit 3cf7c41
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 21 deletions.
57 changes: 41 additions & 16 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ interface State {
cardData?: CardData
contests: Contests
election: OptionalElection
loadingElection: boolean
precinctId: string
userSettings: UserSettings
votes: VotesDict
Expand All @@ -62,6 +63,7 @@ const initialState = {
ballotStyleId: '',
contests: [],
election: undefined,
loadingElection: false,
precinctId: '',
userSettings: { textSize: GLOBALS.TEXT_SIZE as TextSizeSetting },
votes: {},
Expand All @@ -72,7 +74,7 @@ let checkCardInterval = 0
export class App extends React.Component<RouteComponentProps, State> {
public state: State = initialState

public processCardData = (cardData: CardData) => {
public processCardData = (cardData: CardData, longValueExists: boolean) => {
if (cardData.t === 'voter') {
if (!this.state.election) {
return
Expand All @@ -94,6 +96,42 @@ export class App extends React.Component<RouteComponentProps, State> {
this.activateBallot(activationData)
}
}

if (cardData.t === 'admin') {
if (!this.state.election) {
if (longValueExists && !this.state.loadingElection) {
this.setState({ loadingElection: true })
this.fetchElection().then(election => {
this.setElection(JSON.parse(election.longValue))
})
}
}
}
}

public fetchElection = async () => {
return fetch('/card/read_long').then(result => result.json())
}

public startPolling = () => {
checkCardInterval = window.setInterval(() => {
fetch('/card/read')
.then(result => result.json())
.then(resultJSON => {
if (resultJSON.shortValue) {
const cardData = JSON.parse(resultJSON.shortValue) as CardData
this.processCardData(cardData, resultJSON.longValueExists)
}
})
.catch(() => {
// if it's an error, aggressively assume there's no backend and stop hammering
this.stopPolling()
})
}, 1000)
}

public stopPolling = () => {
window.clearInterval(checkCardInterval)
}

public componentDidMount = () => {
Expand Down Expand Up @@ -124,20 +162,7 @@ export class App extends React.Component<RouteComponentProps, State> {
document.documentElement.setAttribute('data-useragent', navigator.userAgent)
this.setDocumentFontSize()

checkCardInterval = window.setInterval(() => {
fetch('/card/read')
.then(result => result.json())
.then(resultJSON => {
if (resultJSON.shortValue) {
const cardData = JSON.parse(resultJSON.shortValue) as CardData
this.processCardData(cardData)
}
})
.catch(() => {
// if it's an error, aggressively assume there's no backend and stop hammering
window.clearInterval(checkCardInterval)
})
}, 1000)
this.startPolling()
}

public componentWillUnount = /* istanbul ignore next - triggering keystrokes issue - https://github.com/votingworks/bmd/issues/62 */ () => {
Expand All @@ -153,7 +178,7 @@ export class App extends React.Component<RouteComponentProps, State> {

public setElection = (electionConfigFile: Election) => {
const election = mergeWithDefaults(electionConfigFile)
this.setState({ election })
this.setState({ election, loadingElection: false })
window.localStorage.setItem(electionKey, JSON.stringify(election))
}

Expand Down
108 changes: 108 additions & 0 deletions src/AppAdminCard.test.tsx~
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { render } from 'react-testing-library'

import electionSample from './data/electionSample.json'

import Root, { App } from './App'
import { AdminCardData, Election, VoterCardData } from './config/types'

const election = electionSample as Election

beforeEach(() => {
window.localStorage.clear()
window.location.href = '/'
})


it(`App fetches the card data every 1 second`, () => {
fetchMock.resetMocks()
jest.useFakeTimers()

fetchMock.mockResponses(
[JSON.stringify({}), { status: 200 }],
// This response covers the App functionality for processing card data.
[
JSON.stringify({
present: true,
shortValue: JSON.stringify({
t: 'voter',
pr: election.precincts[0].id,
bs: election.ballotStyles[0].id,
}),
}),
{ status: 200 },
],
['', { status: 500 }]
)

// load the sample election
window.location.href = '/#sample'
render(<Root />)

expect(window.setInterval).toHaveBeenCalledTimes(1)

jest.advanceTimersByTime(3000)

expect(fetchMock.mock.calls.length).toEqual(3)
expect(fetchMock.mock.calls).toEqual([
['/card/read'],
['/card/read'],
['/card/read'],
])

jest.useRealTimers()
})

it(`CardData processing processes card data properly`, () => {
const div = document.createElement('div')
// @ts-ignore - App expects ReactRouter props, but are unnecessary for this test.
const app = (ReactDOM.render(<App />, div) as unknown) as App

app.activateBallot = jest.fn()
app.fetchElection = jest.fn().mockResolvedValue(election)

const adminCardData: AdminCardData = {
h: 'abcdef',
t: 'admin',
}

// for now just for code coverage of the else, we don't do anything useful yet
app.processCardData(adminCardData, false)
expect(app.fetchElection).not.toHaveBeenCalled()

app.processCardData(adminCardData, true)
expect(app.fetchElection).toHaveBeenCalled()

const voterCardData: VoterCardData = {
bs: election.ballotStyles[0].id,
pr: election.precincts[0].id,
t: 'voter',
}

app.processCardData(voterCardData, false)
expect(app.activateBallot).not.toHaveBeenCalled()

app.state.election = election
app.processCardData(voterCardData, false)

// also bad ballot style and precinct, for coverage.
const badVoterCardData: VoterCardData = {
bs: 'foobar',
pr: 'barbaz',
t: 'voter',
}
app.processCardData(badVoterCardData, false)

expect(app.activateBallot).toBeCalled()
})

it(`Admin card fetches election`, async (done) => {
const div = document.createElement('div')
// @ts-ignore - App expects ReactRouter props, but are unnecessary for this test.
const app = (ReactDOM.render(<App />, div) as unknown) as App

app.fetchElection = jest.fn().mockResolvedValue(election)
app.setElection = jest.fn()

})
38 changes: 33 additions & 5 deletions src/AppCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,34 +59,62 @@ it(`CardData processing processes card data properly`, () => {
const app = (ReactDOM.render(<App />, div) as unknown) as App

app.activateBallot = jest.fn()
app.fetchElection = jest.fn().mockResolvedValue(election)

const adminCardData: AdminCardData = {
h: 'abcdef',
t: 'admin',
}

// for now just for code coverage of the else, we don't do anything useful yet
app.processCardData(adminCardData)
app.processCardData(adminCardData, false)
expect(app.fetchElection).not.toHaveBeenCalled()

app.state.election = election
app.processCardData(adminCardData, true)
expect(app.fetchElection).not.toHaveBeenCalled()

app.state.election = undefined
app.state.loadingElection = true
app.processCardData(adminCardData, true)
expect(app.fetchElection).not.toHaveBeenCalled()

app.state.loadingElection = false
app.processCardData(adminCardData, true)
expect(app.fetchElection).toHaveBeenCalled()

const voterCardData: VoterCardData = {
bs: election.ballotStyles[0].id,
pr: election.precincts[0].id,
t: 'voter',
}

app.processCardData(voterCardData)
app.processCardData(voterCardData, false)
expect(app.activateBallot).not.toHaveBeenCalled()

app.state.election = election
app.processCardData(voterCardData)
app.processCardData(voterCardData, false)

// also bad ballot style and precinct, for coverage.
const badVoterCardData: VoterCardData = {
bs: 'foobar',
pr: 'barbaz',
t: 'voter',
}
app.processCardData(badVoterCardData)
app.processCardData(badVoterCardData, false)

expect(app.activateBallot).toBeCalled()
})

it(`Calls fetch on fetchElection`, () => {
fetchMock.resetMocks()

fetchMock.mockResponse(JSON.stringify(election))

const div = document.createElement('div')
// @ts-ignore - App expects ReactRouter props, but are unnecessary for this test.
const app = (ReactDOM.render(<App />, div) as unknown) as App

app.fetchElection()

expect(fetchMock).toHaveBeenCalled()
})

0 comments on commit 3cf7c41

Please sign in to comment.