-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Formatic 3000 #124
base: master
Are you sure you want to change the base?
WIP: Formatic 3000 #124
Changes from 26 commits
230f394
8eec2ce
cebd6fa
8f390f4
2c827b3
c5d0bc8
6739955
f23a600
3fe0ad1
f193215
af6e3e0
0fa226e
067a500
8878def
0f4c5d5
0c571f3
018b2b6
221b69c
2476fc3
ace4df5
565da6f
be8de3c
7e5f9bd
abe9f0c
737daa6
dfc5f54
9cc81ac
9218c37
c84bc32
6aeaf24
7d7a3ad
9b6362d
0185cdd
089c4f3
06c06b4
4bc8326
65f7c08
87660c8
a4a0242
4d3981b
302cf96
0b80b23
a08c04a
5e9b175
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/*global jest, describe, test, expect, afterEach*/ | ||
import React from 'react'; | ||
import { render, fireEvent, cleanup } from 'react-testing-library'; | ||
|
||
import { FormContainer, TextField, useField } from '@/src/future'; | ||
|
||
afterEach(cleanup); | ||
|
||
describe('avoid rerendering', () => { | ||
const defaultValue = { firstName: '', lastName: '' }; | ||
|
||
test('should avoid unnecessary rerendering with uncontrolled container', async () => { | ||
const renderSpy = jest.fn(); | ||
function FirstName() { | ||
renderSpy('firstName'); | ||
const { value } = useField('firstName'); | ||
return <div>First Name: {value}</div>; | ||
} | ||
function LastName() { | ||
renderSpy('lastName'); | ||
const { value } = useField('lastName'); | ||
return <div>First Name: {value}</div>; | ||
} | ||
const { getByLabelText } = render( | ||
<FormContainer defaultValue={defaultValue}> | ||
<FirstName /> | ||
<LastName /> | ||
<TextField fieldKey="firstName" id="firstName" label="First Name" /> | ||
<TextField fieldKey="lastName" id="lastName" label="Last Name" /> | ||
</FormContainer> | ||
); | ||
fireEvent.change(getByLabelText('First Name'), { | ||
target: { value: 'Joe' }, | ||
}); | ||
expect(renderSpy.mock.calls).toEqual([ | ||
['firstName'], | ||
['lastName'], | ||
['firstName'], | ||
]); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/*global jest, describe, test, expect, afterEach*/ | ||
import React from 'react'; | ||
import { render, fireEvent, cleanup } from 'react-testing-library'; | ||
|
||
import { ExampleForm, defaultValue } from '@/demo/future/BuiltInField'; | ||
|
||
afterEach(cleanup); | ||
|
||
describe('built-in field', () => { | ||
test('should fire onChange correctly', () => { | ||
const onChangeSpy = jest.fn(); | ||
const { getByLabelText } = render( | ||
<ExampleForm defaultValue={defaultValue} onChange={onChangeSpy} /> | ||
); | ||
fireEvent.change(getByLabelText('First Name'), { | ||
target: { value: 'Joe' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[0][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: '', | ||
}); | ||
fireEvent.change(getByLabelText('Last Name'), { | ||
target: { value: 'Foo' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[1][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: 'Foo', | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/*global jest, describe, test, expect, afterEach*/ | ||
import React from 'react'; | ||
import { render, fireEvent, cleanup } from 'react-testing-library'; | ||
|
||
import { FormContainer, TextField } from '@/src/future'; | ||
|
||
afterEach(cleanup); | ||
|
||
describe('built-in field with auto input ids', () => { | ||
test('should have auto-generated input ids', () => { | ||
const onChangeSpy = jest.fn(); | ||
const defaultValue = { | ||
firstName: '', | ||
lastName: '', | ||
}; | ||
|
||
const renderForm = () => ( | ||
<FormContainer defaultValue={defaultValue} onChange={onChangeSpy}> | ||
<div data-testid="firstNameWrapper"> | ||
<TextField fieldKey="firstName" label="First Name" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this approach a lot - you can specify your fields with JSX and control where it goes, add wrapper stuff, etc. I also really like the Just my first impressions, but this is all feeling very nice! |
||
</div> | ||
<div data-testid="lastNameWrapper"> | ||
<TextField fieldKey="lastName" label="Last Name" /> | ||
</div> | ||
</FormContainer> | ||
); | ||
const { getByTestId, rerender } = render(renderForm()); | ||
|
||
const firstNameInput = getByTestId('firstNameWrapper').querySelector( | ||
'input' | ||
); | ||
const lastNameInput = getByTestId('lastNameWrapper').querySelector('input'); | ||
|
||
const firstNameId = firstNameInput.getAttribute('id'); | ||
const lastNameId = lastNameInput.getAttribute('id'); | ||
|
||
expect(firstNameId).toBeTruthy(); | ||
expect(lastNameId).toBeTruthy(); | ||
expect(firstNameId).not.toEqual(lastNameId); | ||
|
||
fireEvent.change(firstNameInput, { | ||
target: { value: 'Joe' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[0][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: '', | ||
}); | ||
fireEvent.change(lastNameInput, { | ||
target: { value: 'Foo' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[1][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: 'Foo', | ||
}); | ||
|
||
rerender(renderForm()); | ||
|
||
// Ids should not change when rerendering. | ||
expect(firstNameId).toEqual(firstNameInput.getAttribute('id')); | ||
expect(lastNameId).toEqual(lastNameInput.getAttribute('id')); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/*global jest, describe, test, expect, afterEach*/ | ||
import React from 'react'; | ||
import { render, fireEvent, cleanup } from 'react-testing-library'; | ||
|
||
import { ExampleForm, defaultValue } from '@/demo/future/BuiltInInput'; | ||
|
||
afterEach(cleanup); | ||
|
||
describe('built-in input', () => { | ||
test('should fire onChange correctly', () => { | ||
const onChangeSpy = jest.fn(); | ||
const { getByLabelText } = render( | ||
<ExampleForm defaultValue={defaultValue} onChange={onChangeSpy} /> | ||
); | ||
fireEvent.change(getByLabelText('First Name'), { | ||
target: { value: 'Joe' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[0][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: '', | ||
}); | ||
fireEvent.change(getByLabelText('Last Name'), { | ||
target: { value: 'Foo' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[1][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: 'Foo', | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/*global jest, describe, test, expect, afterEach*/ | ||
import React from 'react'; | ||
import { render, fireEvent, cleanup } from 'react-testing-library'; | ||
|
||
import { | ||
ExampleForm, | ||
value, | ||
} from '@/demo/future/ControlledCustomFormWithHooks'; | ||
|
||
afterEach(cleanup); | ||
|
||
describe('controlled custom form with hooks', () => { | ||
test('should fire onChange correctly', () => { | ||
const onChangeSpy = jest.fn(); | ||
const { getByLabelText, rerender } = render( | ||
<ExampleForm onChange={onChangeSpy} value={value} /> | ||
); | ||
fireEvent.change(getByLabelText('First Name'), { | ||
target: { value: 'Joe' }, | ||
}); | ||
const newValue = onChangeSpy.mock.calls[0][0]; | ||
expect(newValue).toEqual({ | ||
firstName: 'Joe', | ||
lastName: '', | ||
}); | ||
rerender(<ExampleForm onChange={onChangeSpy} value={newValue} />); | ||
fireEvent.change(getByLabelText('Last Name'), { | ||
target: { value: 'Foo' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[1][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: 'Foo', | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/*global jest, describe, test, expect, afterEach*/ | ||
import React from 'react'; | ||
import { render, fireEvent, cleanup } from 'react-testing-library'; | ||
|
||
import { | ||
ExampleForm, | ||
defaultValue, | ||
} from '@/demo/future/CustomFormWithFieldContainer'; | ||
|
||
afterEach(cleanup); | ||
|
||
describe('custom form with FieldContainer', () => { | ||
test('should fire onChange correctly', () => { | ||
const onChangeSpy = jest.fn(); | ||
const { getByLabelText } = render( | ||
<ExampleForm defaultValue={defaultValue} onChange={onChangeSpy} /> | ||
); | ||
fireEvent.change(getByLabelText('First Name'), { | ||
target: { value: 'Joe' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[0][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: '', | ||
}); | ||
fireEvent.change(getByLabelText('Last Name'), { | ||
target: { value: 'Foo' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[1][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: 'Foo', | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/*global jest, describe, test, expect, afterEach */ | ||
import React from 'react'; | ||
import { render, fireEvent, cleanup } from 'react-testing-library'; | ||
|
||
import { ExampleForm, defaultValue } from '@/demo/future/CustomFormWithHooks'; | ||
|
||
afterEach(cleanup); | ||
|
||
describe('custom form with hooks', () => { | ||
test('should fire onChange correctly', () => { | ||
const onChangeSpy = jest.fn(); | ||
const { getByLabelText } = render( | ||
<ExampleForm defaultValue={defaultValue} onChange={onChangeSpy} /> | ||
); | ||
fireEvent.change(getByLabelText('First Name'), { | ||
target: { value: 'Joe' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[0][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: '', | ||
}); | ||
fireEvent.change(getByLabelText('Last Name'), { | ||
target: { value: 'Foo' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[1][0]).toEqual({ | ||
firstName: 'Joe', | ||
lastName: 'Foo', | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/*global jest, describe, test, expect, afterEach*/ | ||
import React from 'react'; | ||
import { render, fireEvent, cleanup } from 'react-testing-library'; | ||
|
||
import { ExampleForm, defaultValue } from '@/demo/future/NestedValues'; | ||
|
||
afterEach(cleanup); | ||
|
||
describe('nested values with FieldContainer', () => { | ||
test('should fire onChange correctly', () => { | ||
const onChangeSpy = jest.fn(); | ||
const { getByLabelText } = render( | ||
<ExampleForm defaultValue={defaultValue} onChange={onChangeSpy} /> | ||
); | ||
fireEvent.change(getByLabelText('First Name'), { | ||
target: { value: 'Joe' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[0][0]).toEqual({ | ||
name: { firstName: 'Joe', lastName: '' }, | ||
username: '', | ||
}); | ||
fireEvent.change(getByLabelText('Last Name'), { | ||
target: { value: 'Foo' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[1][0]).toEqual({ | ||
name: { | ||
firstName: 'Joe', | ||
lastName: 'Foo', | ||
}, | ||
username: '', | ||
}); | ||
fireEvent.change(getByLabelText('Username'), { | ||
target: { value: 'joefoo' }, | ||
}); | ||
expect(onChangeSpy.mock.calls[2][0]).toEqual({ | ||
name: { | ||
firstName: 'Joe', | ||
lastName: 'Foo', | ||
}, | ||
username: 'joefoo', | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/*global jest, describe, test, expect, afterEach*/ | ||
import React from 'react'; | ||
import { render, cleanup } from 'react-testing-library'; | ||
|
||
import { ExampleForm, defaultValue } from '@/demo/future/StyledBuiltInField'; | ||
|
||
afterEach(cleanup); | ||
|
||
describe('styled built-in field', () => { | ||
test('should use emotion to override styling', () => { | ||
const onChangeSpy = jest.fn(); | ||
const { getByLabelText } = render( | ||
<ExampleForm defaultValue={defaultValue} onChange={onChangeSpy} /> | ||
); | ||
// Example upper-cases labels, and | ||
// Emotion generates a class with "css-" in it, so look for that. | ||
expect( | ||
getByLabelText('FIRST NAME') | ||
.getAttribute('class') | ||
.indexOf('css-') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Super minor—another way to get this might be: expect(
getByLabelText('FIRST NAME').className.includes('css-')
).toBe(true); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, that's a little nicer! |
||
).toBeGreaterThan(-1); | ||
expect( | ||
getByLabelText('LAST NAME') | ||
.getAttribute('class') | ||
.indexOf('css-') | ||
).toBeGreaterThan(-1); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import React, { useState } from 'react'; | ||
|
||
function TextField({ fieldKey, label, value, onChangeField }) { | ||
return ( | ||
<div> | ||
<div> | ||
<label htmlFor={fieldKey}>{label}</label> | ||
</div> | ||
<div> | ||
<input | ||
id={fieldKey} | ||
onChange={event => onChangeField(fieldKey, event.target.value)} | ||
type="text" | ||
value={value} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export function ExampleForm({ defaultValue, onChange }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One question regarding syntax: does Formatic keep the same style guidelines we try to use company-wide? We say to always use arrow functions unless we need to keep context of I'm all for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does use Zapier's linting, but I don't think it's stuck with all of our internal style rules. This was just something I was trying though. I've found myself liking There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally agree |
||
const [formState, setFormState] = useState(defaultValue); | ||
const onChangeField = (key, value) => { | ||
const newFormState = { | ||
...formState, | ||
[key]: value, | ||
}; | ||
setFormState(newFormState); | ||
onChange(newFormState); | ||
}; | ||
return ( | ||
<form> | ||
<TextField | ||
fieldKey="firstName" | ||
label="First Name" | ||
onChangeField={onChangeField} | ||
value={formState.firstName} | ||
/> | ||
<TextField | ||
fieldKey="lastName" | ||
label="Last Name" | ||
onChangeField={onChangeField} | ||
value={formState.lastName} | ||
/> | ||
</form> | ||
); | ||
} | ||
|
||
export const defaultValue = { firstName: '', lastName: '' }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the
setupTestFrameworkScriptFile
option injest.config
would call setupcleanup
for all tests. Then we wouldn't need to explicitly call it in every test file.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that seems reasonable. The only downside of that is you can't opt out of that behavior then? For example, if you wanted to do some iterative tests on the same DOM, you could use
afterAll
, but with the global setting, your DOM would be wiped out each time.