Skip to content

Commit

Permalink
Make <CreateGroupForm /> character counters work
Browse files Browse the repository at this point in the history
Make the character counters on the new create-group page (the
`<CreateGroupForm />` component) actually work: the counts change as the
text in the fields change, and the counters and fields go into an error
state when too many characters are entered.
  • Loading branch information
seanh committed Jul 12, 2024
1 parent ad20953 commit 44028e8
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 34 deletions.
97 changes: 73 additions & 24 deletions h/static/scripts/group-forms/components/CreateGroupForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useId } from 'preact/hooks';
import { useId, useState } from 'preact/hooks';

import { Button, Input, Textarea } from '@hypothesis/frontend-shared';

Expand All @@ -10,15 +10,20 @@ function CharacterCounter({
value,
limit,
testid,
error = false,
}: {
value: number;
limit: number;
testid: string;
error?: boolean;
}) {
return (
<div className="flex">
<div className="grow" />
<span data-testid={testid}>
<span
data-testid={testid}
className={error ? 'text-red-error font-bold' : undefined}
>
{value}/{limit}
</span>
</div>
Expand All @@ -42,36 +47,80 @@ function Label({
);
}

export default function CreateGroupForm() {
const nameId = useId();
const descriptionId = useId();
function NameField() {
const id = useId();
const [characterCount, setCharacterCount] = useState(0);

function handleInput(e: any) {
setCharacterCount(e.target.value.length);
}

const limit = 25;
const error = characterCount > limit;

return (
<div className="mb-4">
<Label htmlFor={id} text="Name" required />
<Input
id={id}
onInput={handleInput}
autofocus
autocomplete="off"
required
data-testid="name"
error={error ? `Must be ${limit} characters or less.` : undefined}
/>
<CharacterCounter
value={characterCount}
limit={25}
testid="charcounter-name"
error={error ? true : false}
/>
</div>
);
}

function DescriptionField() {
const id = useId();
const [characterCount, setCharacterCount] = useState(0);

function handleInput(e: any) {
setCharacterCount(e.target.value.length);
}

const limit = 250;
const error = characterCount > limit;

return (
<div className="mb-4">
<Label htmlFor={id} text="Description" />
<Textarea
id={id}
classes="h-24"
onInput={handleInput}
data-testid="description"
error={error ? `Must be ${limit} characters or less.` : undefined}
/>
<CharacterCounter
value={characterCount}
limit={250}
testid="charcounter-description"
error={error ? true : false}
/>
</div>
);
}

export default function CreateGroupForm() {
return (
<div className="text-grey-6 text-sm/tight">
<h1 className="mt-14 mb-8 text-grey-7 text-xl/none">
Create a new private group
</h1>

<form>
<div className="mb-4">
<Label htmlFor={nameId} text="Name" required />
<Input id={nameId} autofocus autocomplete="off" required />
<CharacterCounter
value={0}
limit={25}
testid="character-counter-name"
/>
</div>

<div className="mb-4">
<Label htmlFor={descriptionId} text="Description" />
<Textarea id={descriptionId} classes="h-24" />
<CharacterCounter
value={0}
limit={250}
testid="character-counter-description"
/>
</div>
<NameField />
<DescriptionField />

<div className="flex">
<div className="grow" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,83 @@ import { mount } from 'enzyme';
import CreateGroupForm from '../CreateGroupForm';

describe('CreateGroupForm', () => {
const createWrapper = () => mount(<CreateGroupForm />);
/* Return true if counterEl is in its error state. */
const counterIsInErrorState = counterEl => {
return (
counterEl.hasClass('text-red-error') && counterEl.hasClass('font-bold')
);
};

/* Return true if component is in its error state. */
const componentIsInErrorState = component => {
return component.prop('error') !== undefined;
};

const getElements = wrapper => {
return {
name: {
counterEl: wrapper.find('[data-testid="charcounter-name"]'),
fieldComponent: wrapper.find('Input[data-testid="name"]'),
fieldEl: wrapper.find('input[data-testid="name"]'),
},
description: {
counterEl: wrapper.find('[data-testid="charcounter-description"]'),
fieldComponent: wrapper.find('Textarea[data-testid="description"]'),
fieldEl: wrapper.find('textarea[data-testid="description"]'),
},
};
};

const createWrapper = () => {
const wrapper = mount(<CreateGroupForm />);
const elements = getElements(wrapper);
return { wrapper, elements };
};

[
{ counter: 'name', limit: 25 },
{ counter: 'description', limit: 250 },
].forEach(({ counter, limit }) => {
describe(`${counter} character counter`, () => {
it(`initially renders 0/${limit}`, () => {
const wrapper = createWrapper();
const counterEl = wrapper.find(
`[data-testid="character-counter-${counter}"]`,
);
{
field: 'name',
limit: 25,
},
{
field: 'description',
limit: 250,
},
].forEach(({ field, limit }) => {
describe(`${field} character counter`, () => {
it('renders a character counter', () => {
const { counterEl, fieldComponent } = createWrapper().elements[field];

assert.equal(counterEl.text(), `0/${limit}`);
assert.isNotOk(counterIsInErrorState(counterEl));
assert.isNotOk(componentIsInErrorState(fieldComponent));
});

it("changes the count when the field's text changes", () => {
const { wrapper, elements } = createWrapper();
const fieldEl = elements[field].fieldEl;

fieldEl.getDOMNode().value = 'new text';
fieldEl.simulate('input');
wrapper.update();

const { counterEl, fieldComponent } = getElements(wrapper)[field];
assert.equal(counterEl.text(), `8/${limit}`);
assert.isNotOk(counterIsInErrorState(counterEl));
assert.isNotOk(componentIsInErrorState(fieldComponent));
});

it('goes into an error state when too many characters are entered', () => {
const { wrapper, elements } = createWrapper();
const fieldEl = elements[field].fieldEl;

fieldEl.getDOMNode().value = 'a'.repeat(limit + 1);
fieldEl.simulate('input');
wrapper.update();

const { counterEl, fieldComponent } = getElements(wrapper)[field];
assert.isOk(counterIsInErrorState(counterEl));
assert.isOk(componentIsInErrorState(fieldComponent));
});
});
});
Expand Down

0 comments on commit 44028e8

Please sign in to comment.