diff --git a/src/__snapshots__/player.test.js.snap b/src/__snapshots__/player.test.js.snap index 778556f..f8ebba3 100644 --- a/src/__snapshots__/player.test.js.snap +++ b/src/__snapshots__/player.test.js.snap @@ -224,7 +224,10 @@ exports[`Player Render with no question 1`] = ` className="popup help" >
-

+

Secret Spreadsheet - How it works:

@@ -248,6 +251,7 @@ exports[`Player Render with no question 1`] = `
diff --git a/src/components/player/question.js b/src/components/player/question.js index 5389414..f5a50c1 100644 --- a/src/components/player/question.js +++ b/src/components/player/question.js @@ -1,17 +1,41 @@ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; + +const ariaWarningText = 'After closing this dialog focus will be automatically moved to the first empty cell in the spreadsheet.' // the popup containing the question and description given by the widget creator // at a minimum shows the question, description is optional const Question = props => { + const questionEl = useRef(null) + + // bit of a hack to make sure the question is given focus when it comes up + useEffect(() => { + if (questionEl.current) questionEl.current.focus() + }, []) + return (
-

{props.question}

+

+ {props.question} +

- {props.description !== `` ?

{props.description}

:null} + { + props.description !== `` + ? +

{props.description}

+ : + null + } -
diff --git a/src/components/player/question.test.js b/src/components/player/question.test.js index 5050189..b21150f 100644 --- a/src/components/player/question.test.js +++ b/src/components/player/question.test.js @@ -1,35 +1,77 @@ +jest.mock('react', () => { + const ActualReact = jest.requireActual('react') + return { + ...ActualReact, + useRef: jest.fn() + } +}) + import React from 'react'; import Question from './question'; -import renderer from 'react-test-renderer'; +import { create, act } from 'react-test-renderer'; describe(`Question component`, () => { beforeEach(() => { jest.resetModules(); + React.useRef.mockClear(); }); test(`Is rendered with question body`, () => { + const mockFocus = jest.fn(); + React.useRef.mockReturnValue({ + current: { + focus: mockFocus + } + }); + const props = { handleQuestionToggle: jest.fn(), question: `Test entry question`, description: `Test question body` }; - const component = renderer.create(); + let component + act(() => { + component = create(, { + createNodeMock: () => ({ + focus: mockFocus + }) + }); + }) const tree = component.toJSON(); expect(tree).toMatchSnapshot(); + + // bonus test - make sure the ref works and auto-focuses + expect(mockFocus).toHaveBeenCalled(); }); test(`Is rendered without question body`, () => { + const mockFocus = jest.fn(); + React.useRef.mockReturnValue({ + current: { + focus: mockFocus + } + }); + const props = { handleQuestionToggle: jest.fn(), question: `Test entry question`, description: `` }; - const component = renderer.create(); + // not sure if this is even possible, but... + // update the ref to null to make sure nothing calls 'focus'? + let component + act(() => { + component = create(, { + createNodeMock: () => null + }); + }) const tree = component.toJSON(); expect(tree).toMatchSnapshot(); + + expect(mockFocus).not.toHaveBeenCalled(); }); }); diff --git a/src/player.js b/src/player.js index 805bd22..ae02628 100644 --- a/src/player.js +++ b/src/player.js @@ -22,6 +22,7 @@ class PlayerApp extends React.Component { this.handlePopupToggle = this.handlePopupToggle.bind(this); this.randomize = this.randomize.bind(this); this.countBlanks = this.countBlanks.bind(this); + this.handleQuestionKeyUp = this.handleQuestionKeyUp.bind(this); this.handleQuestionToggle = this.handleQuestionToggle.bind(this); } @@ -108,6 +109,11 @@ class PlayerApp extends React.Component { } } + // because the 'question' link isn't a button we have to handle space/enter presses on it ourselves + handleQuestionKeyUp(e) { + if (e.key === ' ' || e.key === 'Enter') this.handleQuestionToggle() + } + // decides if it should show the question popup or not handleQuestionToggle() { if (this.state.showQuestion) { @@ -158,7 +164,18 @@ class PlayerApp extends React.Component {

{this.props.title}

- {this.state.popup ? null:} + { + this.state.popup + ? + null + : + + }
@@ -174,7 +191,8 @@ class PlayerApp extends React.Component {

Input the missing data to complete the spreadsheet:

- +
You've filled out {this.state.answered} of {this.blankPositions.size} missing cells

- {this.state.question ?

View Question

:null} + { + this.state.question + ? +

+ View Question +

+ : + null + } - diff --git a/src/player.scss b/src/player.scss index b3fa4d0..e81d531 100644 --- a/src/player.scss +++ b/src/player.scss @@ -170,6 +170,12 @@ header { p:not(.mainQuestion) { margin-bottom: 20px; } + + &:focus { + .mainQuestion { + text-decoration: underline; + } + } } .help { diff --git a/src/player.test.js b/src/player.test.js index 908e9f6..139cf99 100644 --- a/src/player.test.js +++ b/src/player.test.js @@ -444,6 +444,36 @@ describe(`Player`, () => { mockPlayer.unmount(); }); + test('handleKeyUp does nothing when handling an irrelevant key', () => { + const mockPlayer = makeNewPlayer(); + + const mockHandleQuestionToggle = jest.fn(); + + mockPlayer.instance().handleQuestionToggle = mockHandleQuestionToggle; + + const mockKeyupEvent = { key: 'Tab' }; + + mockPlayer.instance().handleQuestionKeyUp(mockKeyupEvent); + expect(mockHandleQuestionToggle).not.toHaveBeenCalled(); + }); + test('handleKeyUp handles expected keys', () => { + const mockPlayer = makeNewPlayer(); + + const mockHandleQuestionToggle = jest.fn(); + + mockPlayer.instance().handleQuestionToggle = mockHandleQuestionToggle; + + let mockKeyupEvent; + + mockKeyupEvent = { key: ' ' }; + mockPlayer.instance().handleQuestionKeyUp(mockKeyupEvent); + expect(mockHandleQuestionToggle).toHaveBeenCalledTimes(1); + + mockKeyupEvent = { key: 'Enter' }; + mockPlayer.instance().handleQuestionKeyUp(mockKeyupEvent); + expect(mockHandleQuestionToggle).toHaveBeenCalledTimes(2); + }); + test(`handleQuestionToggle is toggled off`, () => { const mockPlayer = makeNewPlayer(); diff --git a/yarn.lock b/yarn.lock index a5865df..bbc860a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5897,7 +5897,7 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= -"ngmodal@github:ucfcdl/ngModal#v1.2.2": +ngmodal@ucfcdl/ngModal#v1.2.2: version "1.2.2" resolved "https://codeload.github.com/ucfcdl/ngModal/tar.gz/6abad982bdb8f258ffcdc20316a907c2292399d2"