Skip to content

Commit 5279b9c

Browse files
author
Madelyn Kasula
authored
Merge pull request #345 from code-dot-org/more-unit-tests
Unit test <Pond/>
2 parents 48012c2 + 31a7908 commit 5279b9c

File tree

3 files changed

+210
-35
lines changed

3 files changed

+210
-35
lines changed

src/oceans/helpers.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export const finishMovement = (t, pause = true) => {
190190
});
191191
};
192192

193-
export const resetTraining = state => {
193+
const resetTraining = state => {
194194
state.trainer.clearAll();
195195
setState({
196196
yesCount: 0,
@@ -222,3 +222,7 @@ export const reportPageView = page => {
222222
window.ga('set', 'page', syntheticPagePath);
223223
window.ga('send', 'pageview');
224224
};
225+
226+
export default {
227+
resetTraining
228+
};

src/oceans/ui.jsx

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import _ from 'lodash';
55
import {getState, setState} from './state';
66
import constants, {AppMode, Modes} from './constants';
77
import modeHelpers from './modeHelpers';
8-
import {
8+
import helpers, {
99
$time,
1010
currentRunTime,
1111
finishMovement,
12-
resetTraining,
1312
friendlyNameForFishPart
1413
} from './helpers';
1514
import train from './models/train';
@@ -887,7 +886,7 @@ let UnwrappedTrain = class Train extends React.Component {
887886
const noButtonText =
888887
state.appMode === AppMode.CreaturesVTrash ? 'No' : `Not ${state.word}`;
889888
const resetTrainingFunction = () => {
890-
resetTraining(state);
889+
helpers.resetTraining(state);
891890
setState({showConfirmationDialog: false});
892891
};
893892

@@ -1113,10 +1112,10 @@ let UnwrappedPredict = class Predict extends React.Component {
11131112
export const Predict = Radium(UnwrappedPredict); // Exported for unit tests.
11141113

11151114
class PondPanel extends React.Component {
1116-
onPondPanelClick(e) {
1115+
onPondPanelClick = e => {
11171116
setState({pondPanelShowing: false});
11181117
e.stopPropagation();
1119-
}
1118+
};
11201119

11211120
render() {
11221121
const state = getState();
@@ -1128,10 +1127,7 @@ class PondPanel extends React.Component {
11281127
return (
11291128
<div>
11301129
{!state.pondClickedFish && (
1131-
<div
1132-
style={styles.pondPanelLeft}
1133-
onClick={e => this.onPondPanelClick(e)}
1134-
>
1130+
<div style={styles.pondPanelLeft} onClick={this.onPondPanelClick}>
11351131
{state.pondExplainGeneralSummary && (
11361132
<div>
11371133
<div style={styles.pondPanelPreText}>
@@ -1242,7 +1238,7 @@ class PondPanel extends React.Component {
12421238
}
12431239
}
12441240

1245-
let Pond = class Pond extends React.Component {
1241+
let UnwrappedPond = class Pond extends React.Component {
12461242
constructor(props) {
12471243
super(props);
12481244
}
@@ -1278,7 +1274,9 @@ let Pond = class Pond extends React.Component {
12781274
setState({pondFishTransitionStartTime: $time(), pondClickedFish: null});
12791275
}
12801276

1281-
e.stopPropagation();
1277+
if (e) {
1278+
e.stopPropagation();
1279+
}
12821280
};
12831281

12841282
onPondClick = e => {
@@ -1364,13 +1362,10 @@ let Pond = class Pond extends React.Component {
13641362
}
13651363
};
13661364

1367-
onPondPanelButtonClick(e) {
1365+
onPondPanelButtonClick = e => {
13681366
const state = getState();
13691367

1370-
if (
1371-
state.appMode === AppMode.FishShort ||
1372-
state.appMode === AppMode.FishLong
1373-
) {
1368+
if ([AppMode.FishShort, AppMode.FishLong].includes(state.appMode)) {
13741369
setState({
13751370
pondPanelShowing: !state.pondPanelShowing
13761371
});
@@ -1382,15 +1377,16 @@ let Pond = class Pond extends React.Component {
13821377
}
13831378
}
13841379

1385-
e.stopPropagation();
1386-
}
1380+
if (e) {
1381+
e.stopPropagation();
1382+
}
1383+
};
13871384

13881385
render() {
13891386
const state = getState();
13901387

13911388
const showInfoButton =
1392-
(state.appMode === AppMode.FishShort ||
1393-
state.appMode === AppMode.FishLong) &&
1389+
[AppMode.FishShort, AppMode.FishLong].includes(state.appMode) &&
13941390
state.pondFish.length > 0 &&
13951391
state.recallFish.length > 0;
13961392
const recallIconsStyle = showInfoButton
@@ -1399,14 +1395,14 @@ let Pond = class Pond extends React.Component {
13991395

14001396
return (
14011397
<Body>
1402-
<div onClick={e => this.onPondClick(e)} style={styles.pondSurface} />
1398+
<div onClick={this.onPondClick} style={styles.pondSurface} />
14031399
<div style={recallIconsStyle}>
14041400
<FontAwesomeIcon
14051401
icon={faCheck}
14061402
style={{
14071403
...styles.recallIcon,
14081404
...{borderTopLeftRadius: 8, borderBottomLeftRadius: 8},
1409-
...(!state.showRecallFish ? styles.bgGreen : {})
1405+
...(state.showRecallFish ? {} : styles.bgGreen)
14101406
}}
14111407
onClick={this.toggleRecall}
14121408
/>
@@ -1427,37 +1423,32 @@ let Pond = class Pond extends React.Component {
14271423
...(!state.pondPanelShowing ? {} : styles.bgTeal)
14281424
}}
14291425
onClick={this.onPondPanelButtonClick}
1426+
id="uitest-info-btn"
14301427
>
14311428
<FontAwesomeIcon icon={faInfo} style={styles.infoIcon} />
14321429
</div>
14331430
)}
14341431
<img style={styles.pondBot} src={aiBotClosed} />
14351432
{state.canSkipPond && (
1436-
<div>
1433+
<div id="uitest-nav-btns">
14371434
{state.appMode === AppMode.FishLong ? (
14381435
<div>
14391436
<Button
14401437
style={styles.playAgainButton}
14411438
onClick={() => {
14421439
setState({pondClickedFish: null, pondPanelShowing: false});
1443-
resetTraining(state);
1440+
helpers.resetTraining(state);
14441441
modeHelpers.toMode(Modes.Words);
14451442
}}
14461443
>
14471444
New Word
14481445
</Button>
1449-
<Button
1450-
style={styles.finishButton}
1451-
onClick={() => state.onContinue()}
1452-
>
1446+
<Button style={styles.finishButton} onClick={state.onContinue}>
14531447
Finish
14541448
</Button>
14551449
</div>
14561450
) : (
1457-
<Button
1458-
style={styles.continueButton}
1459-
onClick={() => state.onContinue()}
1460-
>
1451+
<Button style={styles.continueButton} onClick={state.onContinue}>
14611452
Continue
14621453
</Button>
14631454
)}
@@ -1479,7 +1470,7 @@ let Pond = class Pond extends React.Component {
14791470
);
14801471
}
14811472
};
1482-
Pond = Radium(Pond);
1473+
export const Pond = Radium(UnwrappedPond); // Exported for unit tests.
14831474

14841475
let Guide = class Guide extends React.Component {
14851476
onShowing() {

test/unit/oceans/ui.test.js

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import {
88
Words,
99
wordSet,
1010
Train,
11-
Predict
11+
Predict,
12+
Pond
1213
} from '@ml/oceans/ui';
1314
import guide from '@ml/oceans/models/guide';
1415
import soundLibrary from '@ml/oceans/models/soundLibrary';
1516
import train from '@ml/oceans/models/train';
1617
import modeHelpers from '@ml/oceans/modeHelpers';
18+
import helpers from '@ml/oceans/helpers';
1719
import {setState, getState, resetState} from '@ml/oceans/state';
1820
import {AppMode, Modes} from '@ml/oceans/constants';
1921
import colors from '@ml/oceans/colors';
@@ -326,6 +328,8 @@ describe('Train', () => {
326328
continueButton.simulate('click');
327329

328330
expect(toModeStub.withArgs(Modes.Predicting).callCount).toEqual(1);
331+
332+
modeHelpers.toMode.restore();
329333
});
330334
});
331335

@@ -425,3 +429,179 @@ describe('Predict', () => {
425429
expect(wrapper.exists('#uitest-continue-btn')).toBeTruthy();
426430
});
427431
});
432+
433+
describe('Pond', () => {
434+
let playSoundStub;
435+
436+
beforeEach(() => {
437+
playSoundStub = sinon.stub(soundLibrary, 'playSound');
438+
});
439+
440+
afterEach(() => {
441+
soundLibrary.playSound.restore();
442+
resetState();
443+
});
444+
445+
it('recall icons toggle fish set on click', () => {
446+
let wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
447+
let checkIcon = wrapper.find('FontAwesomeIcon').at(0);
448+
let banIcon = wrapper.find('FontAwesomeIcon').at(1);
449+
450+
expect(checkIcon.prop('style').backgroundColor).toEqual(colors.green);
451+
expect(banIcon.prop('style').backgroundColor).toBeFalsy();
452+
expect(playSoundStub.callCount).toEqual(0);
453+
454+
banIcon.simulate('click');
455+
wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
456+
checkIcon = wrapper.find('FontAwesomeIcon').at(0);
457+
banIcon = wrapper.find('FontAwesomeIcon').at(1);
458+
459+
expect(checkIcon.prop('style').backgroundColor).toBeFalsy();
460+
expect(banIcon.prop('style').backgroundColor).toEqual(colors.red);
461+
expect(playSoundStub.withArgs('no').callCount).toEqual(1);
462+
});
463+
464+
describe('info button', () => {
465+
it('displays based on state', () => {
466+
let wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
467+
expect(wrapper.exists('#uitest-info-btn')).toBeFalsy();
468+
469+
setState({
470+
appMode: AppMode.FishShort,
471+
pondFish: [{}],
472+
recallFish: [{}]
473+
});
474+
wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
475+
expect(wrapper.exists('#uitest-info-btn')).toBeTruthy();
476+
});
477+
478+
it('toggles pond panel on click', () => {
479+
setState({
480+
appMode: AppMode.FishShort,
481+
pondFish: [{}],
482+
recallFish: [{}],
483+
pondPanelShowing: false
484+
});
485+
let wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
486+
487+
wrapper.find('#uitest-info-btn').simulate('click');
488+
expect(getState().pondPanelShowing).toBeTruthy();
489+
expect(playSoundStub.withArgs('sortyes').callCount).toEqual(1);
490+
491+
wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
492+
expect(wrapper.exists('PondPanel')).toBeTruthy();
493+
494+
wrapper.find('#uitest-info-btn').simulate('click');
495+
expect(getState().pondPanelShowing).toBeFalsy();
496+
expect(playSoundStub.withArgs('sortno').callCount).toEqual(1);
497+
498+
wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
499+
expect(wrapper.exists('PondPanel')).toBeFalsy();
500+
});
501+
});
502+
503+
describe('navigation', () => {
504+
let toModeStub;
505+
506+
beforeEach(() => {
507+
toModeStub = sinon.stub(modeHelpers, 'toMode');
508+
});
509+
510+
afterEach(() => {
511+
modeHelpers.toMode.restore();
512+
});
513+
514+
it('displays buttons based on canSkipPond state', () => {
515+
let wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
516+
expect(getState().canSkipPond).toBeFalsy();
517+
expect(wrapper.exists('#uitest-nav-btns')).toBeFalsy();
518+
519+
setState({canSkipPond: true});
520+
wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
521+
expect(wrapper.exists('#uitest-nav-btns')).toBeTruthy();
522+
});
523+
524+
it('displays different buttons based on appMode state', () => {
525+
const getBtnText = (btns, i) =>
526+
btns
527+
.at(i)
528+
.render()
529+
.text();
530+
531+
setState({canSkipPond: true, appMode: AppMode.FishLong});
532+
let wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
533+
534+
let buttons = wrapper.find('#uitest-nav-btns').find('Button');
535+
expect(buttons.length).toEqual(3);
536+
expect(getBtnText(buttons, 0)).toEqual('New Word');
537+
expect(getBtnText(buttons, 1)).toEqual('Finish');
538+
expect(getBtnText(buttons, 2)).toEqual('Train More');
539+
540+
setState({appMode: 'not-fish-long'});
541+
wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
542+
543+
buttons = wrapper.find('#uitest-nav-btns').find('Button');
544+
expect(buttons.length).toEqual(2);
545+
expect(getBtnText(buttons, 0)).toEqual('Continue');
546+
expect(getBtnText(buttons, 1)).toEqual('Train More');
547+
});
548+
549+
it('"new word" button resets training and transitions to Modes.Words', () => {
550+
const resetTrainingStub = sinon.stub(helpers, 'resetTraining');
551+
552+
setState({canSkipPond: true, appMode: AppMode.FishLong});
553+
let wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
554+
const newWordBtn = wrapper.find('#uitest-nav-btns Button').at(0);
555+
556+
newWordBtn.simulate('click');
557+
558+
const newState = getState();
559+
expect(newState.pondClickedFish).toBeNull();
560+
expect(newState.pondPanelShowing).toBeFalsy();
561+
expect(resetTrainingStub.callCount).toEqual(1);
562+
expect(toModeStub.withArgs(Modes.Words).callCount).toEqual(1);
563+
564+
helpers.resetTraining.restore();
565+
});
566+
567+
it('"finish" button calls onContinue', () => {
568+
const onContinueSpy = sinon.spy();
569+
setState({
570+
canSkipPond: true,
571+
appMode: AppMode.FishLong,
572+
onContinue: onContinueSpy
573+
});
574+
let wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
575+
const finishBtn = wrapper.find('#uitest-nav-btns Button').at(1);
576+
577+
finishBtn.simulate('click');
578+
expect(onContinueSpy.callCount).toEqual(1);
579+
});
580+
581+
it('"continue" button calls onContinue', () => {
582+
const onContinueSpy = sinon.spy();
583+
setState({
584+
canSkipPond: true,
585+
onContinue: onContinueSpy
586+
});
587+
let wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
588+
const continueBtn = wrapper.find('#uitest-nav-btns Button').at(0);
589+
590+
continueBtn.simulate('click');
591+
expect(onContinueSpy.callCount).toEqual(1);
592+
});
593+
594+
it('"train more" button transitions to Modes.Training', () => {
595+
setState({canSkipPond: true});
596+
let wrapper = shallow(<Pond {...DEFAULT_PROPS} />);
597+
const trainMoreBtn = wrapper.find('#uitest-nav-btns Button').at(1);
598+
599+
trainMoreBtn.simulate('click');
600+
601+
const newState = getState();
602+
expect(newState.pondClickedFish).toBeNull();
603+
expect(newState.pondPanelShowing).toBeFalsy();
604+
expect(toModeStub.withArgs(Modes.Training).callCount).toEqual(1);
605+
});
606+
});
607+
});

0 commit comments

Comments
 (0)