-
Notifications
You must be signed in to change notification settings - Fork 398
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
Add category page (close #1378) #1398
Conversation
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.
Nice, I like how the category component extends the search component. I had some questions and requested a few changes.
convertFiltersToQueryParams(filters); | ||
const paginator = count && hasSearchParams > 0 ? ( | ||
<Paginate LinkComponent={LinkComponent} count={count} currentPage={page} | ||
pathname={pathname} queryParams={queryParams} showPages={0} /> |
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 is showPages
always 0? Is that because the UI shouldn't ever show links to numbered pages?
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.
Yeah; we use the numbered pages in the admin but not AMO.
It's a bit of a holdover, arguably showPages={0}
should be the default. Filed: #1405
|
||
|
||
export function mapStateToProps(state, ownProps) { | ||
const filters = { |
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 think it would make sense to move this definition and other relevant ones into the block if (filtersMatch(...
since that's the only place they are needed. A function like mapStateToProps()
will run on every application state change so it's a bit sensitive.
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.
But these filters are declared here to compare them with state and make sure everything matches. They're passed to if (filtersMatch([...])) {
, so moving them into there would mean duplication.
They're only passed to the state if they match.
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.
Ah, ok, I see on a second look that all of these do need to be defined outside of the if block.
if (!isLoaded({ state: state.search, page, filters })) { | ||
return performSearch({ dispatch, page, api: state.api, auth: state.auth, filters }); | ||
} | ||
return 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.
even though it's supported by asyncConnect
, I think it's bad practice to mix promise and non-promise return values. I'd rather see: return Promise.resolve(true)
or maybe simply Promise.resolve()
if that's enough.
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.
}); | ||
|
||
assert.deepEqual(props, { | ||
hasSearchParams: 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.
this parameter will be overidden by ...state.search
. Maybe this object should begin with ...state.search
instead of end with it?
|
||
|
||
describe('CategoryPage.mapStateToProps()', () => { | ||
const state = { |
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 think it would be less fragile to create this state by dispatching real actions. It should be just as easy to do. Example: https://github.com/mozilla/addons-frontend/issues/1371
queryParams: { page: 1 }, | ||
}); | ||
}); | ||
}); |
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 appears to be missing a test for the case when state.search
is empty (i.e. {}
).
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 don't think that should ever happen, as it's set by searchStart
. Am I missing a path to an empty search state?
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 component may render with state.search = {}
for a split second if the API response is slow but, no worries, we could always add the test later if a bug appears with handling empty state.
@@ -16,3 +21,77 @@ describe('searchUtils mapStateToProps()', () => { | |||
assert.deepEqual(props, { hasSearchParams: false }); | |||
}); | |||
}); | |||
|
|||
describe('searchUtils loadCategoryResultsIfNeeded()', () => { | |||
const state = { |
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'd also like to this state built by dispatching real actions, if possible
slug: 'ad-block', | ||
}; | ||
assert.strictEqual( | ||
loadCategoryResultsIfNeeded({ store, location, params }), 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.
I think this test would be better if it verified that mockApi
is never called, which is a stronger indicator that data is not fetched from the API.
return true; | ||
} | ||
|
||
export function loadCategoryResultsIfNeeded({ store: { dispatch, getState }, location, params }) { |
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 could be shortened slightly to loadByCategoryIfNeeded
category: params.slug, | ||
clientApp: params.application, | ||
}; | ||
|
||
if (!isLoaded({ state: state.search, page, filters })) { |
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 do the search results need to be cached? I think it should make an API request every time. Otherwise, the single page app will go stale -- the only way a user can obtain newly added results is to refresh the page. The browser will already cache the API call as needed so I don't really see the value in adding a new caching layer on top of that.
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.
Good point. Again, it was like that when I got here; the search page currently caches results. When flipping through paginated results it makes going back and forth between pages quick.
I've filed #1404, but I'll remove it from this bit of code because why not and I agree; we can always add it back.
b535565
to
c7f363e
Compare
Okay, I think I fixed all the issues (mostly regarding state testing) in the PR, and created issues for the things that were slightly out-of-scope. Ready for another r? |
Odd: https://travis-ci.org/mozilla/addons-frontend/builds/177831776 is failing but passing locally, and the error is quite strange. |
Try rebasing with master. That's what I was running into yesterday and switching back to npm seemed to fix it. |
Ah right, cool. Figured this was related, cheers.
|
c7f363e
to
0d1d89c
Compare
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.
r+wc
Thanks for filing all the fixup issues.
if (!isLoaded({ state: state.search, page, filters })) { | ||
return performSearch({ dispatch, page, api: state.api, auth: state.auth, filters }); | ||
} | ||
return true; | ||
} | ||
|
||
export function loadByCategoryIfNeeded( |
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 name no longer makes sense :) I think loadByCategory
would be fine.
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.
Is this making another API request on the client after JS loading on initial render? I think that's why we've normally included the caching.
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.
Hmm, maybe it still needs caching. I was hoping to achieve the rules defined here: https://github.com/mozilla/addons-frontend/issues/1404#issuecomment-262306143
const store = createStore(); | ||
store.dispatch(searchStart({ filters })); | ||
const mismatchedState = store.getState(); | ||
mismatchedState.search.filters.clientApp = 'nothing'; |
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 dispatch (like above) is the way to do it IMO. By using dispatch you are not coupling this code with the raw state representation. Here would be a way to rewrite it:
store.dispatch(searchStart({ filters: { ...filters, clientApp: 'nothing' } }));
const mismatchedState = store.getState();
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.
Oops, good point. Will do.
queryParams: { page: 1 }, | ||
}); | ||
}); | ||
}); |
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 component may render with state.search = {}
for a split second if the API response is slow but, no worries, we could always add the test later if a bug appears with handling empty state.
@@ -16,3 +20,38 @@ describe('searchUtils mapStateToProps()', () => { | |||
assert.deepEqual(props, { hasSearchParams: false }); | |||
}); | |||
}); | |||
|
|||
describe('searchUtils loadByCategoryIfNeeded()', () => { |
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.
loadByCategoryIfNeeded
would need to be renamed too
0d1d89c
to
98c26b0
Compare
Add the actual category URL (matches addons-server) including pagination.
Screenshot: