Samuel Kendrick
Posted on April 26, 2023
You've been assigned at ticket! Check it out: https://github.com/VEuPathDB/web-eda/issues/1596
Here's a picture of the mocks:
I've already built out the map and the componentry outlined in pink. The scope of this work is to implement the blue.
People like to say they have good attention to detail. Those folks don't need test-first development (aka TDD). For other mortals, such as myself, I like to have a schematic and then implement code based on that schematic. You know who else does that? Civil engineers. Mechanical engineers. Chemical engineers. Electrical engineers. Basically everyone but software engineers.
My first schematic focused on converting the design into end-user behaviors:
import { render, screen } from '@testing-library/react';
import { MarkerConfigurationMenu } from './MarkerConfigurationMenu';
describe('<MarkerConfigurationMenu />', () => {
test('users can select a marker type', async () => { });
test('when a user selects a marker type, a marker-specific configuration panel appears', async () => { });
test('users can configure their marker selection', async () => { });
});
I also have another user in mind: coworkers. Put another way, my component should have other behaviors which the end-user doesn't care about. For example, the developers using this component will consume it's API (via props). One such behavior would be to fire an event for each time the user updates the marker configuration. As it is, many other components will care about what the user has selected. For instance, this legend needs to know what the user has selected.
Fleshing out the tests
import { render, screen } from '@testing-library/react';
import { MarkerConfigurationMenu } from './MarkerConfigurationMenu';
import { useState } from 'react';
const markerTypes = [
{
name: 'Donuts',
renderConfigurationMenu: function DonutConfigurationMenu() {
const [frostingSelection, setFrostingSelection] = useState('');
return (
<div>
{frostingSelection.length > 0 ? (
<p>A donut with {frostingSelection} frosting.</p>
) : (
<></>
)}
<label htmlFor="frostingFlavor">
Frosting flavor
<select
onChange={(event) => setFrostingSelection(event.target.value)}
name="frostingFlavor"
id="frostingFlavor"
>
<option value="">Unfrosted</option>
<option value="strawberry">Strawberry</option>
<option value="chocolate">Chocolate</option>
</select>
</label>
</div>
);
},
},
{
name: 'Bar plots',
renderConfigurationMenu: function BarPlotConfigurationMenu() {
const [barSelection, setBarSelection] = useState('Cheers');
const [plotSelection, setPlotSelection] = useState('Novel');
function handleSelection(event: React.ChangeEvent<HTMLSelectElement>) {
const { name, value } = event.target;
name === 'bar' ? setBarSelection(value) : setPlotSelection(value);
}
return (
<div>
<p>
You will be plotting a {plotSelection} at {barSelection}.
</p>
<label htmlFor="bar">
Bar options
<select
onChange={handleSelection}
value={barSelection}
name="bar"
id="bar"
>
<option value="Cheers">Cheers (Bostom, MA)</option>
<option value="The Blue Bar">The Blue Bar (NYC)</option>
<option value="Harry's Bar">Harry's Bar (Venice, IT)</option>
</select>
</label>
<label htmlFor="plot">
Plot options
<select
onChange={handleSelection}
value={plotSelection}
name="plot"
id="plot"
>
<option value="Novel">Novel</option>
<option value="Bank Heist">Bank Heist</option>
<option value="Points">Points on a graph</option>
</select>
</label>
</div>
);
},
},
];
describe('<MarkerConfigurationMenu />', () => {
test('after a user selects a marker type, a marker-specific configuration panel appears', async () => {
render(<MarkerConfigurationMenu markerTypes={markerTypes} />);
screen.getByText('Donuts').click();
screen.getByText('Frosting flavor');
screen.getByText('Strawberry').click();
expect(screen.getByText('A donut with strawberry frosting')).toBeVisible();
screen.getByText('Bar plots').click();
expect(
screen.getByText('You will be plotting a novel at Cheers.')
).toBeVisible();
});
test('users can configure their marker selection', async () => {
render(<MarkerConfigurationMenu markerTypes={markerTypes} />);
screen.getByText('Bar plots').click();
screen.getByLabelText('Bar options').click();
screen.getByLabelText('The Blue Bar').click();
expect(
screen.getByText('You will be plotting a novel at The Blue Bar.')
).toBeVisible();
});
});
I think it'd be wise for the <MarkerConfigurationMenu />
to have one responsibility: rendering the selected marker configuration. The UI for each marker configuration is drastically different for each given marker type. Looky here:
Bar plots:
Donuts:
<MarkerConfigurationMenu />
doesn't actually care about the particulars of the marker configuration.
Actually, I'm going to rename things:
import { render, screen } from '@testing-library/react';
import { MarkerConfigurationSelector } from './MarkerConfigurationSelector';
import { useState } from 'react';
const markerTypes = [
{
name: 'Donuts',
icon: () => {},
renderConfigurationMenu: function DonutConfigurationMenu() {
const [frostingSelection, setFrostingSelection] = useState('');
return (
<div>
{frostingSelection.length > 0 ? (
<p>A donut with {frostingSelection} frosting.</p>
) : (
<></>
)}
<label htmlFor="frostingFlavor">
Frosting flavor
<select
onChange={(event) => setFrostingSelection(event.target.value)}
name="frostingFlavor"
id="frostingFlavor"
>
<option value="">Unfrosted</option>
<option value="strawberry">Strawberry</option>
<option value="chocolate">Chocolate</option>
</select>
</label>
</div>
);
},
},
{
name: 'Bar plots',
icon: () => {},
renderConfigurationMenu: function BarPlotConfigurationMenu() {
const [barSelection, setBarSelection] = useState('Cheers');
const [plotSelection, setPlotSelection] = useState('Novel');
function handleSelection(event: React.ChangeEvent<HTMLSelectElement>) {
const { name, value } = event.target;
name === 'bar' ? setBarSelection(value) : setPlotSelection(value);
}
return (
<div>
<p>
You will be plotting a {plotSelection} at {barSelection}.
</p>
<label htmlFor="bar">
Bar options
<select
onChange={handleSelection}
value={barSelection}
name="bar"
id="bar"
>
<option value="Cheers">Cheers (Bostom, MA)</option>
<option value="The Blue Bar">The Blue Bar (NYC)</option>
<option value="Harry's Bar">Harry's Bar (Venice, IT)</option>
</select>
</label>
<label htmlFor="plot">
Plot options
<select
onChange={handleSelection}
value={plotSelection}
name="plot"
id="plot"
>
<option value="Novel">Novel</option>
<option value="Bank Heist">Bank Heist</option>
<option value="Points">Points on a graph</option>
</select>
</label>
</div>
);
},
},
];
describe('<MarkerConfigurationMenu />', () => {
test('after a user selects a marker type, a marker-specific configuration panel appears', async () => {
render(<MarkerConfigurationSelector markerTypes={markerTypes} />);
screen.getByText('Donuts').click();
screen.getByText('Frosting flavor');
screen.getByText('Strawberry').click();
expect(screen.getByText('A donut with strawberry frosting')).toBeVisible();
screen.getByText('Bar plots').click();
expect(
screen.getByText('You will be plotting a novel at Cheers.')
).toBeVisible();
});
test('users can configure their marker selection', async () => {
render(<MarkerConfigurationSelector markerTypes={markerTypes} />);
screen.getByText('Bar plots').click();
screen.getByLabelText('Bar options').click();
screen.getByLabelText('The Blue Bar').click();
expect(
screen.getByText('You will be plotting a novel at The Blue Bar.')
).toBeVisible();
});
});
Now at this point I have the privilege of showing the specs to my coworkers and get their feedback!
Posted on April 26, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.