-
Notifications
You must be signed in to change notification settings - Fork 227
improve dynamicLists typing #1848
Changes from all commits
45aa9ac
34610be
e450856
aa57d27
715607c
ee11f9b
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 |
---|---|---|
|
@@ -13,9 +13,14 @@ describe('useDynamicList', () => { | |
return {price: '', optionName: '', optionValue: ''}; | ||
}; | ||
function DynamicListComponent(config: FieldListConfig<Variant>) { | ||
const {fields, addItem, removeItem, moveItem, reset} = useDynamicList< | ||
Variant | ||
>(config, factory); | ||
const { | ||
fields, | ||
addItem, | ||
removeItem, | ||
moveItem, | ||
reset, | ||
dirty, | ||
} = useDynamicList<Variant>(config, factory); | ||
|
||
return ( | ||
<> | ||
|
@@ -48,6 +53,7 @@ describe('useDynamicList', () => { | |
<button type="button" onClick={reset}> | ||
Reset | ||
</button> | ||
<p>Dirty: {dirty.toString()}</p> | ||
</> | ||
); | ||
} | ||
|
@@ -73,6 +79,9 @@ describe('useDynamicList', () => { | |
}); | ||
|
||
it('will throw an error if new position is out of index range', () => { | ||
const consoleErrorMock = jest.spyOn(console, 'error'); | ||
consoleErrorMock.mockImplementation(); | ||
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. |
||
|
||
const variants: Variant[] = [ | ||
{price: 'A', optionName: 'A', optionValue: 'A'}, | ||
{price: 'B', optionName: 'B', optionValue: 'B'}, | ||
|
@@ -86,6 +95,8 @@ describe('useDynamicList', () => { | |
.findAll('button', {children: 'Move Variant up'})![0] | ||
.trigger('onClick'); | ||
}).toThrow('Failed to move item from 0 to -1'); | ||
|
||
consoleErrorMock.mockRestore(); | ||
}); | ||
|
||
it('can remove field', () => { | ||
|
@@ -108,8 +119,6 @@ describe('useDynamicList', () => { | |
.find('button', {children: 'Add Variant'})! | ||
.trigger('onClick', clickEvent()); | ||
|
||
// const addedTextField = wrapper.findAll(TextField)![0]; | ||
|
||
expect(wrapper).toContainReactComponent(TextField, { | ||
name: 'price1', | ||
value: '', | ||
|
@@ -203,6 +212,66 @@ describe('useDynamicList', () => { | |
expect(wrapper).toContainReactComponent(TextField); | ||
}); | ||
}); | ||
|
||
describe('dirty dynamic list', () => { | ||
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. So far there was no dirty related test 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. hmm 🤔 @sylvhama . There are dirty tests in the baselist since that is where it is originally implemented 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. So I dont think we will need to have it in the dynamiclist test since dynamic list just returns what the baselist is returning 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. same case for reset 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. But 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. Oh I dont mean 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. but |
||
it('handles dirty state when adding a field and resetting it', () => { | ||
const wrapper = mount( | ||
<DynamicListComponent list={randomVariants(1)} />, | ||
); | ||
|
||
expect(wrapper).toContainReactText('Dirty: false'); | ||
|
||
wrapper | ||
.find(TextField, {name: 'price0'})! | ||
.trigger('onChange', 'new value'); | ||
|
||
expect(wrapper).toContainReactText('Dirty: true'); | ||
|
||
wrapper | ||
.find('button', {children: 'Reset'})! | ||
.trigger('onClick', clickEvent()); | ||
|
||
expect(wrapper).toContainReactText('Dirty: false'); | ||
}); | ||
|
||
it('handles dirty state when adding a field and resetting it', () => { | ||
const wrapper = mount(<DynamicListComponent list={randomVariants()} />); | ||
|
||
expect(wrapper).toContainReactText('Dirty: false'); | ||
|
||
wrapper | ||
.find('button', {children: 'Add Variant'})! | ||
.trigger('onClick', clickEvent()); | ||
|
||
expect(wrapper).toContainReactText('Dirty: true'); | ||
|
||
wrapper | ||
.find('button', {children: 'Reset'})! | ||
.trigger('onClick', clickEvent()); | ||
|
||
expect(wrapper).toContainReactText('Dirty: false'); | ||
}); | ||
|
||
it('handles dirty state when removing a field and resetting it', () => { | ||
const wrapper = mount( | ||
<DynamicListComponent list={randomVariants(1)} />, | ||
); | ||
|
||
expect(wrapper).toContainReactText('Dirty: false'); | ||
|
||
wrapper | ||
.find('button', {children: 'Remove Variant'})! | ||
.trigger('onClick', clickEvent()); | ||
|
||
expect(wrapper).toContainReactText('Dirty: true'); | ||
|
||
wrapper | ||
.find('button', {children: 'Reset'})! | ||
.trigger('onClick', clickEvent()); | ||
|
||
expect(wrapper).toContainReactText('Dirty: false'); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('add mulitiple items with payload', () => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import React from 'react'; | ||
import {mount} from '@shopify/react-testing'; | ||
|
||
import { | ||
FormWithDynamicVariantList, | ||
TextField, | ||
isDirty, | ||
fakeProduct, | ||
hitSubmit, | ||
hitReset, | ||
waitForSubmit, | ||
} from './utilities'; | ||
|
||
import {submitSuccess, submitFail} from '..'; | ||
|
||
describe('useForm with dynamic list', () => { | ||
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. those tests are inspired by origin
|
||
describe('dirty state', () => { | ||
it('dirty state is false when no field has been changed', () => { | ||
const wrapper = mount( | ||
<FormWithDynamicVariantList data={fakeProduct()} />, | ||
); | ||
|
||
expect(isDirty(wrapper)).toBe(false); | ||
}); | ||
|
||
it('dirty state is true when a new variant item has been added', () => { | ||
const wrapper = mount( | ||
<FormWithDynamicVariantList data={fakeProduct()} />, | ||
); | ||
|
||
wrapper.find('button', {children: 'Add item'}).trigger('onClick'); | ||
|
||
expect(isDirty(wrapper)).toBe(true); | ||
}); | ||
|
||
it('dirty state is true when a variant item has been removed', () => { | ||
const wrapper = mount( | ||
<FormWithDynamicVariantList data={fakeProduct()} />, | ||
); | ||
|
||
wrapper.find('button', {children: 'Remove item'}).trigger('onClick'); | ||
|
||
expect(isDirty(wrapper)).toBe(true); | ||
}); | ||
|
||
it('dirty state is true when a variant item has been edited', () => { | ||
const wrapper = mount( | ||
<FormWithDynamicVariantList data={fakeProduct()} />, | ||
); | ||
|
||
wrapper | ||
.find(TextField, {label: 'price'}) | ||
.trigger('onChange', 'next price'); | ||
|
||
expect(isDirty(wrapper)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('submit', () => { | ||
it('validates dynamic list fields with their latest values before submitting and bails out if any fail', () => { | ||
const submitSpy = jest.fn(() => Promise.resolve(submitSuccess())); | ||
const product = fakeProduct(); | ||
const wrapper = mount( | ||
<FormWithDynamicVariantList data={product} onSubmit={submitSpy} />, | ||
); | ||
|
||
const textFields = wrapper.findAll(TextField, {label: 'option'}); | ||
|
||
textFields.forEach(textField => textField.trigger('onChange', '')); | ||
hitSubmit(wrapper); | ||
|
||
expect(submitSpy).not.toHaveBeenCalled(); | ||
|
||
expect(wrapper).toContainReactComponentTimes( | ||
TextField, | ||
textFields.length, | ||
{ | ||
error: 'Option name is required!', | ||
}, | ||
); | ||
}); | ||
|
||
it('propagates remote submission errors to matching fields', async () => { | ||
const errors = [ | ||
{ | ||
field: ['variants', '0', 'price'], | ||
message: 'The server hates your price', | ||
}, | ||
]; | ||
const promise = Promise.resolve(submitFail(errors)); | ||
const wrapper = mount( | ||
<FormWithDynamicVariantList | ||
data={fakeProduct()} | ||
onSubmit={() => promise} | ||
/>, | ||
); | ||
|
||
await waitForSubmit(wrapper, promise); | ||
|
||
expect(wrapper).toContainReactComponent(TextField, { | ||
error: errors[0].message, | ||
}); | ||
}); | ||
}); | ||
|
||
describe('reset', () => { | ||
it('reset dynamic list after adding new item', () => { | ||
const wrapper = mount( | ||
<FormWithDynamicVariantList data={fakeProduct()} />, | ||
); | ||
|
||
wrapper.find('button', {children: 'Add item'}).trigger('onClick'); | ||
|
||
expect(wrapper).toContainReactComponentTimes(TextField, 3, { | ||
label: 'option', | ||
}); | ||
|
||
hitReset(wrapper); | ||
|
||
expect(wrapper).toContainReactComponentTimes(TextField, 2, { | ||
label: 'option', | ||
}); | ||
expect(isDirty(wrapper)).toBe(false); | ||
}); | ||
|
||
it('reset dynamic list after removing item', () => { | ||
const wrapper = mount( | ||
<FormWithDynamicVariantList data={fakeProduct()} />, | ||
); | ||
|
||
wrapper.find('button', {children: 'Remove item'}).trigger('onClick'); | ||
|
||
expect(wrapper).toContainReactComponentTimes(TextField, 1, { | ||
label: 'option', | ||
}); | ||
|
||
hitReset(wrapper); | ||
|
||
expect(wrapper).toContainReactComponentTimes(TextField, 2, { | ||
label: 'option', | ||
}); | ||
expect(isDirty(wrapper)).toBe(false); | ||
}); | ||
}); | ||
}); |
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 am wondering, would you have to pass in a type for the second part of the generic for every useForm call that already passes in a generic type for the form?
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.
nope, as you can see in the codesandbox (App.tsx), I made two form and I don't need to pass the generic types.