-
Notifications
You must be signed in to change notification settings - Fork 516
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
feat(priceRanges): Add BEM classes and tests #390
Conversation
{fromInput} | ||
<span className={this.props.cssClasses.separator}> {this.props.labels.separator} </span> | ||
{toInput} | ||
<button className={this.props.cssClasses.button} type="submit">{this.props.labels.button}</button> |
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.
The only issue with that code is that there won't be any space between the toInput
and the button, right?
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.
Right, but why do you need one? This should be handled by CSS
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'm happy with that; it's the same for the checkboxes in the refinementList widget anyway.
Are you able to rebase so we can merge? |
On it |
388adc8
to
bfcc4a8
Compare
return this.getItemFromFacetValue(facetValue); | ||
})} | ||
</div> | ||
{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.
Why not directly call the <Form
> instead of calling a function? Not sure this is a common pattern in React community.
The <Form>
call is only 5 LOC and it would be ovious to read the render() then?
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.
The goal was to be able to:
- Unit test the
getForm
method separatly - Mock the
getForm
when testingrender
This lets me focus on one thing in each test. In the end I think I did not unit test getForm
(my bad, I might have forgotten).
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'll add the missing test and rebase
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.
The goal was to be able to:
Unit test the getForm method separatly
Mock the getForm when testing render
This lets me focus on one thing in each test. In the end I think I did not unit test getForm (my bad, I might have forgotten).
I think we should try to avoid mocking when we can, you can always store the expected <Form>
in a variable in your test rather than modifying the implementation to make it easily testable.
The least mocking we have in testing render() the better?
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.
Small detail in the end, if you test getForm() that's ok
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.
This is maybe due to the fact that most components we always only test the render() method even if that's some boilerplate to add. This way we can just refactor the whole component really easily.
In the end testing a React component output is just asserting some JSX on render(), the underlying implementation should not have to be tested when we can avoid it (sometimes for handlers we need to test it).
really really awesome :D |
bfcc4a8
to
bd63acd
Compare
it('starts a refine on submit', () => { | ||
// Given | ||
let refine = sinon.spy(); | ||
let handleSubmitMock = sinon.spy(PriceRangesForm.prototype, 'handleSubmit'); |
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.
Why not test the instance method rather than the prototype:
let component = <PriceRangesForm refine={refine} />;
TestUtils.renderIntoDocument(<PriceRangesForm refine={refine} />);
If you keep the prototype, you will also need to PriceRangesForm.prototype.handleSubmit.restore();
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.
Also here I think it's just a "spy" rather than a full mock. http://sinonjs.org/docs/#mocks
Found it strange to read "mock" here
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.
Why not test the instance method rather than the prototype:
Well... I tried but failed. <PriceRangesForm refine={refine} />
does not have an handleSubmit
method so I cannot spy it. Once "rendered into document" it does have one, but it is then too late to spy on it as it has already been called. That's why I ended up spying on the prototype.
But you're right that I should restore afterwards (I did it in the other tests, forgot about it here).
And I tend to call everything mock
as a generic term (spies and stubs), but I'll rename to make it clearer.
If you have a better solution for not spying on the prototype let me know and I'll update the PR. Otherwise I'll simply fix the restore/naming.
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.
If you have a better solution for not spying on the prototype let me know and I'll update the PR. Otherwise I'll simply fix the restore/naming.
The PR was merged no big deal but here it is how it was done before: https://github.com/algolia/instantsearch.js/pull/390/files#diff-40651fdb5ca1bd7385705422de4e7029L102
Well... I tried but failed. does not have an handleSubmit method so I cannot spy it. Once "rendered into document" it does have one, but it is then too late to spy on it as it has already been called. That's why I ended up spying on the prototype.
Weird 1. it does not have one, I thought we already did the same tests in other components? 2. is it really called on render? Because it should be called only on submit
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.
The PR was merged no big deal but here it is how it was done before:
The way we did it before was not checking that our handleSubmit
method was called when submitting the form, it was checking the side effects of calling the handleSubmit
. Not exactly the same thing and I'd rather test the call to the method when submitting, and testing the effect of the method independently.
is it really called on render? Because it should be called only on submit
Guess I posted too fast. Will have to check again. I remember the whole testing plumbing here was hard to implement, I might have overlooked something.
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.
it was checking the side effects of calling the handleSubmit
Nope it was actually simulating changing the form and submiting it: https://github.com/algolia/instantsearch.js/pull/390/files#diff-40651fdb5ca1bd7385705422de4e7029L115
Which is the interface we want to test:
- input: submit form
- output: call my spy refine with the right values
Testing that an underlying prototype implementation was called is not the same, here you enforce your implementation to have a certain method versus testing the INPUT/OUTPUT you expose
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.
Let me start by saying I love this discussion :)
You're actually right. I think I forgot a test on handleSubmit
. What I wanted to test was:
- Calling handleSubmit calls the passed
refine
props with the correct args - Submitting the for calls
handleSubmit
with values from the inputs
I did only half the job and thus, the previous test was better. Don't hesitate to stall the merging for issues like that next time.
Fixes #387 BREAKING CHANGE: `ais-price-ranges--range` are now named `ais-price-ranges--item` and are wrapped in a `ais-price-ranges--list`. I've moved the bottom form into it's own PriceRangesForm component, along with its own tests. I've fixed a minor typo where the component was internally named PriceRange (without the final __s__). I factorize some logic form the render in individual methods and manage to individually test them. This was not an easy task. I had to mock the default `render` (so it does nothing) before instanciating the component. Then, I was able to call each inner method individually. This requires to stub prototype methods in beforeEach, then restore them in afterEach. I've added a few helper methods, this can surely be simplified again but this gives nice granularity in testing. I've renamed the `range` items to `item` and wrapped them in a `list`. I've also added classes to all elements we add (`label`, `separator`, etc). I've removed the empty `span`s.
bd63acd
to
ad58d7a
Compare
feat(priceRanges): Add BEM classes and tests
|
||
// Then | ||
expect(handleSubmitMock.calledOnce).toBe(true); | ||
expect(refine.calledWith(10, 20)).toBe(true); |
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.
Actually you are testing both the input output and your internal implementation here. I would get rid of the check on handleSubmit because there's no need of it. It's not a mock since the behavior is not changed. You are just checking that your code follow an internal path you specified in your test but there's no added value versus checking (refine.calledWith)
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.
Yep, I now see what you mean. I was convinced I did two tests: one for checking that handleSubmit
was called on submit
, and another to test that handleSubmit
correctly calls refine
. I did not and tested everything in the same test, my bad
Fixes #387
BREAKING CHANGE:
ais-price-ranges--range
are now namedais-price-ranges--item
and are wrapped ina
ais-price-ranges--list
.I've moved the bottom form into it's own PriceRangesForm component,
along with its own tests. I've fixed a minor typo where the component
was internally named PriceRange (without the final s).
I factorize some logic form the render in individual methods and
manage to individually test them. This was not an easy task. I had to
mock the default
render
(so it does nothing) before instanciatingthe component. Then, I was able to call each inner method
individually. This requires to stub prototype methods in beforeEach,
then restore them in afterEach. I've added a few helper methods, this
can surely be simplified again but this gives nice granularity in
testing.
I've renamed the
range
items toitem
and wrapped them in alist
.I've also added classes to all elements we add (
label
,separator
,etc). I've removed the empty
span
s.