Skip to content

Commit 61fc747

Browse files
committed
Wire up rhythm and rhythm-edit with frequency handling
1 parent 1437a5a commit 61fc747

11 files changed

+3062
-67
lines changed

.babelrc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"presets": [
3+
"next/babel"
4+
]
5+
}

.eslintrc.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"env": {
3+
"jest": true
4+
}
5+
}

components/rhythm-edit.js

+44-23
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,47 @@
11
import { useState } from "react";
22
import styles from "../styles/RhythmEdit.module.css";
3+
import { denominatorTerm } from "../utils/denominator-term";
4+
import { numeratorTerm } from '../utils/numerator-term';
35

4-
export default function RhythmEdit({ rhythm, onClose, onUpdate }) {
6+
const generateNumeratorSelect = (selected, onChange) => {
7+
const options = [1, 2, 3, 4, 5, 6, 7].map((number) => {
8+
return <option key={number} value={number}>{ numeratorTerm(number) }</option>;
9+
});
10+
11+
return <select onChange={onChange} aria-label="Rhythm action count" value={selected}>{
12+
options
13+
}</select>;
14+
};
15+
16+
const generateDenominatorSelect = (selected, onChange) => {
17+
const options = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14].map((number) => {
18+
return <option key={number} value={number}>{ denominatorTerm(number) }</option>;
19+
});
20+
21+
return <select onChange={onChange} aria-label="Rhythm action count time span" value={selected}>
22+
{ options }
23+
</select>;
24+
};
25+
26+
export default function RhythmEdit({ rhythm, onClose, onSubmit }) {
527
const [rhythmAction, setRhythmAction] = useState(rhythm.action);
628
const [rhythmFrequency, setRhythmFrequency] = useState(rhythm.frequency);
29+
const [rhythmNumerator, rhythmDenominator] = rhythmFrequency;
730
const [rhythmReason, setRhythmReason] = useState(rhythm.reason);
831

32+
const submitHandler = (e) => {
33+
e.preventDefault();
34+
35+
onSubmit({
36+
action: rhythmAction,
37+
reason: rhythmReason,
38+
frequency: rhythmFrequency,
39+
});
40+
};
41+
942
return (
10-
<div className={styles["rhythm-edit"]}>
11-
<button onClick={() => onClose()} className={styles.close}>
43+
<form className={styles["rhythm-edit"]} onSubmit={submitHandler}>
44+
<button type="button" onClick={() => onClose()} className={styles.close}>
1245
Close
1346
</button>
1447
<div className={styles.action}>
@@ -21,25 +54,13 @@ export default function RhythmEdit({ rhythm, onClose, onUpdate }) {
2154
/>
2255
</div>
2356
<div className={styles.frequency}>
24-
<select aria-label="Rhythm action count">
25-
<option>once</option>
26-
<option>twice</option>
27-
<option>thrice</option>
28-
<option>four times</option>
29-
<option>five times</option>
30-
<option>six times</option>
31-
<option>seven times</option>
32-
</select>
57+
{generateNumeratorSelect(rhythmNumerator, (e) =>
58+
setRhythmFrequency([Number(e.target.value), rhythmDenominator])
59+
)}
3360
<span className={styles.every}>every</span>
34-
<select aria-label="Rhythm action count time span">
35-
<option>day</option>
36-
<option>two days</option>
37-
<option>three days</option>
38-
<option>four days</option>
39-
<option>five days</option>
40-
<option>six days</option>
41-
<option>seven days</option>
42-
</select>
61+
{generateDenominatorSelect(rhythmDenominator, (e) =>
62+
setRhythmFrequency([rhythmNumerator, Number(e.target.value)])
63+
)}
4364
</div>
4465
<div className="styles.reason">
4566
<div className={styles.action}>
@@ -52,7 +73,7 @@ export default function RhythmEdit({ rhythm, onClose, onUpdate }) {
5273
/>
5374
</div>
5475
</div>
55-
<button onClick={() => onUpdate({ action: rhythmAction, reason: rhythmReason, frequency: rhythm.frequency })}>Update Rhythm</button>
56-
</div>
76+
<button type="submit">Update Rhythm</button>
77+
</form>
5778
);
5879
}

components/rhythm-edit.test.js

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { render } from "@testing-library/react";
2+
import userEvent from "@testing-library/user-event";
3+
import "@testing-library/jest-dom/extend-expect";
4+
import RhythmEdit from './rhythm-edit';
5+
6+
describe('with an existing rhythm object', () => {
7+
let rendered;
8+
let rhythm;
9+
let actionInput;
10+
let numeratorSelect;
11+
let denominatorSelect;
12+
let reasonInput;
13+
let submit;
14+
let onSubmitCallback;
15+
16+
beforeEach(() => {
17+
rhythm = {
18+
action: "I want to run",
19+
frequency: [2, 3],
20+
reason: "I want to train for a marathon",
21+
};
22+
23+
onSubmitCallback = jest.fn();
24+
25+
rendered = render(
26+
<RhythmEdit rhythm={rhythm} onSubmit={onSubmitCallback}/>
27+
);
28+
29+
actionInput = rendered.getByDisplayValue("I want to run");
30+
numeratorSelect = rendered.getByLabelText("Rhythm action count");
31+
denominatorSelect = rendered.getByLabelText(
32+
"Rhythm action count time span"
33+
);
34+
reasonInput = rendered.getByDisplayValue("I want to train for a marathon");
35+
submit = rendered.getByRole("button", { name: "Update Rhythm" });
36+
});
37+
38+
test("it renders the rhythm's editable properties", () => {
39+
expect(actionInput).toBeTruthy();
40+
expect(reasonInput).toBeTruthy();
41+
expect(numeratorSelect).toBeTruthy();
42+
expect(denominatorSelect).toBeTruthy();
43+
});
44+
45+
test('it can submit changes', async () => {
46+
userEvent.clear(actionInput);
47+
userEvent.type(actionInput, "I want to swim");
48+
49+
userEvent.selectOptions(numeratorSelect, "four times");
50+
userEvent.selectOptions(denominatorSelect, "five days");
51+
52+
userEvent.clear(reasonInput);
53+
userEvent.type(reasonInput, "I want to do a triathlon");
54+
55+
userEvent.click(submit);
56+
57+
expect(onSubmitCallback).toHaveBeenCalledTimes(1);
58+
expect(onSubmitCallback).toHaveBeenCalledWith({
59+
action: "I want to swim",
60+
frequency: [4, 5],
61+
reason: 'I want to do a triathlon'
62+
});
63+
});
64+
});
65+

components/rhythm/rhythm.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { useState } from 'react';
22
import styles from '../../styles/Rhythm.module.css';
33
import Occurrence from './occurrence';
4+
import { numeratorTerm } from '../../utils/numerator-term';
5+
import { denominatorTerm } from '../../utils/denominator-term';
46

57
function Rhythm({ rhythm, onEdit }) {
68
const [opportunityTaken, setOpportunity] = useState(false);
79
const toggleOpportunityTaken = () => setOpportunity(!opportunityTaken);
810
const opportunityStatus = opportunityTaken ? 'opportunity-taken' : 'opportunity';
11+
const [frequencyNumerator, frequencyDenominator] = rhythm.frequency;
912

1013
return (
1114
<div className={styles.rhythm}>
1215
<button onClick={() => onEdit(rhythm)}>Edit</button>
1316
<div className={styles.action}>{rhythm.action}</div>
14-
<div className={styles.frequency}>{rhythm.frequency}</div>
15-
<div className={styles.reason}>{rhythm.reason}</div>
17+
<div className={styles.frequency}>{numeratorTerm(frequencyNumerator)} every {denominatorTerm(frequencyDenominator)}</div>
18+
<div className={styles.reason}>because {rhythm.reason}</div>
1619
<div className={styles.occurrences}>
1720
<Occurrence status="miss" />
1821
<Occurrence status="miss" />

jest.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
transform: {
3+
'^.+\\.js$': 'babel-jest',
4+
'.+\\.(css|styl|less|sass|scss)$': 'jest-transform-css',
5+
},
6+
};

package.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,20 @@
55
"scripts": {
66
"dev": "next dev",
77
"build": "next build",
8-
"start": "next start"
8+
"start": "next start",
9+
"test": "jest"
910
},
1011
"dependencies": {
12+
"@testing-library/jest-dom": "^5.11.8",
13+
"@testing-library/react": "^11.2.2",
14+
"babel-jest": "^26.6.3",
15+
"jest": "^26.6.3",
16+
"jest-transform-css": "^2.1.0",
1117
"next": "10.0.3",
1218
"react": "17.0.1",
1319
"react-dom": "17.0.1"
20+
},
21+
"devDependencies": {
22+
"@testing-library/user-event": "^12.6.0"
1423
}
1524
}

pages/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export default function Home() {
1111
const [rhythmToEdit, setRhythmToEdit] = useState();
1212
const [rhythm, setRhythm] = useState({
1313
action: "Drink water every 8 hours",
14-
frequency: "once every day",
15-
reason: "because my body needs it",
14+
frequency: [1, 1],
15+
reason: "my body needs it",
1616
});
1717

1818
return (
@@ -27,7 +27,7 @@ export default function Home() {
2727
<RhythmEdit
2828
rhythm={rhythmToEdit}
2929
onClose={() => setModal(false)}
30-
onUpdate={(rhythm) => {
30+
onSubmit={(rhythm) => {
3131
setModal(false);
3232
setRhythmToEdit(null);
3333
setRhythm(rhythm);

utils/denominator-term.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export function denominatorTerm(number) {
2+
switch (number) {
3+
case 1:
4+
return "day ";
5+
case 2:
6+
return "two days";
7+
case 3:
8+
return "three days";
9+
case 4:
10+
return "four days";
11+
case 5:
12+
return "five days";
13+
case 6:
14+
return "six days";
15+
case 7:
16+
return "week";
17+
case 8:
18+
return "eight days";
19+
case 9:
20+
return "nine days";
21+
case 10:
22+
return "ten days";
23+
case 11:
24+
return "eleven days";
25+
case 12:
26+
return "twelve days";
27+
case 13:
28+
return "thirteen days";
29+
case 14:
30+
return "two weeks";
31+
}
32+
33+
throw new Error(`${number} is outside the range for a numerator term`);
34+
}

utils/numerator-term.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export function numeratorTerm(number) {
2+
switch (number) {
3+
case 1:
4+
return 'once'
5+
case 2:
6+
return 'twice'
7+
case 3:
8+
return 'thrice'
9+
case 4:
10+
return 'four times'
11+
case 5:
12+
return 'five times'
13+
case 6:
14+
return 'six times'
15+
case 7:
16+
return 'seven times'
17+
}
18+
19+
throw new Error(`${number} is outside the range for a numerator term`);
20+
}

0 commit comments

Comments
 (0)