-
- {listings.map(l => (
-
- ))}
-
+
@@ -83,43 +119,64 @@ export const SearchPageComponent = props => {
SearchPageComponent.defaultProps = {
tab: 'listings',
listings: [],
+ pagination: null,
searchParams: {},
searchListingsError: null,
};
-const { array, oneOf, object, instanceOf, bool } = PropTypes;
+const { array, arrayOf, bool, func, instanceOf, number, oneOf, object, shape, string } = PropTypes;
SearchPageComponent.propTypes = {
- tab: oneOf(['filters', 'listings', 'map']).isRequired,
+ flattenedRoutes: arrayOf(propTypes.route).isRequired,
listings: array,
+ location: shape({
+ search: string.isRequired,
+ }).isRequired,
+ pagination: shape({
+ page: number.isRequired,
+ per_page: number, // TODO handle snake_case
+ total_items: number,
+ total_pages: number.isRequired,
+ }),
+ // history.push from withRouter
+ push: func.isRequired,
searchParams: object,
searchInProgress: bool.isRequired,
searchListingsError: instanceOf(Error),
+ tab: oneOf(['filters', 'listings', 'map']).isRequired,
};
const mapStateToProps = state => {
const {
- searchParams,
+ currentPageResultIds,
+ pagination,
searchInProgress,
searchListingsError,
- currentPageResultIds,
+ searchParams,
} = state.SearchPage;
return {
listings: getListingsById(state.data, currentPageResultIds),
- searchParams,
+ pagination,
searchInProgress,
searchListingsError,
+ searchParams,
};
};
-const SearchPage = connect(mapStateToProps)(SearchPageComponent);
+const SearchPage = connect(mapStateToProps)(withRouter(SearchPageComponent));
SearchPage.loadData = (params, search) => {
const queryParams = parse(search, {
latlng: ['origin'],
latlngBounds: ['bounds'],
});
- return searchListings({ ...queryParams, include: ['author', 'images'] });
+ const page = queryParams.page || 1;
+ return searchListings({
+ ...queryParams,
+ page,
+ per_page: RESULT_PAGE_SIZE,
+ include: ['author', 'images'],
+ });
};
export default SearchPage;
diff --git a/src/containers/SearchPage/SearchPage.test.js b/src/containers/SearchPage/SearchPage.test.js
index 6c0e2b04..efa32478 100644
--- a/src/containers/SearchPage/SearchPage.test.js
+++ b/src/containers/SearchPage/SearchPage.test.js
@@ -18,14 +18,14 @@ const { LatLng } = types;
describe('SearchPageComponent', () => {
it('matches snapshot', () => {
- const tree = renderShallow(
- v}
- dispatch={() => null}
- intl={fakeIntl}
- searchInProgress={false}
- />
- );
+ const props = {
+ flattenedRoutes: [],
+ location: { search: '' },
+ push: () => console.log('HistoryPush called'),
+ tab: 'listings',
+ searchInProgress: false,
+ };
+ const tree = renderShallow();
expect(tree).toMatchSnapshot();
});
});
diff --git a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap
index 6c1c6972..6d062af7 100644
--- a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap
+++ b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap
@@ -8,7 +8,21 @@ exports[`SearchPageComponent matches snapshot 1`] = `
-
+
diff --git a/src/index.js b/src/index.js
index 5cf465fb..902aa2d4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -87,4 +87,4 @@ export default renderApp;
// exporting matchPathname and configureStore for server side rendering.
// matchPathname helps to figure out which route is called and if it has preloading needs
// configureStore is used for creating initial store state for Redux after preloading
-export { matchPathname, configureStore };
+export { matchPathname, configureStore, routeConfiguration };
diff --git a/src/util/__snapshots__/routes.test.js.snap b/src/util/__snapshots__/routes.test.js.snap
index 402a114d..0694a70a 100644
--- a/src/util/__snapshots__/routes.test.js.snap
+++ b/src/util/__snapshots__/routes.test.js.snap
@@ -1,6 +1,6 @@
-exports[`withFlattenedRoutes should inject the provided routes 1`] = `
`;
+exports[`util/routes.js withFlattenedRoutes should inject the provided routes 1`] = `
`;
-exports[`withFlattenedRoutes should inject the provided routes 2`] = `
+exports[`util/routes.js withFlattenedRoutes should inject the provided routes 2`] = `
SomePage
diff --git a/src/util/routes.js b/src/util/routes.js
index f21b54e1..e6deccad 100644
--- a/src/util/routes.js
+++ b/src/util/routes.js
@@ -2,7 +2,7 @@ import React, { PropTypes } from 'react';
import { find } from 'lodash';
import { matchPath } from 'react-router-dom';
import pathToRegexp from 'path-to-regexp';
-import routesConfiguration from '../routesConfiguration';
+import { stringify } from './urlHelpers';
import * as propTypes from './propTypes';
// Flatten the routes config.
@@ -50,7 +50,7 @@ export const pathByRouteName = (nameToFind, flattenedRoutes, params = {}) =>
*
* @return {Array<{ route, params }>} - All matches as { route, params } objects
*/
-export const matchPathname = pathname => {
+export const matchPathname = (pathname, routesConfiguration) => {
// TODO: remove flattening when routesConfiguration is flat
const flattenedRoutes = flattenRoutes(routesConfiguration);
@@ -92,3 +92,19 @@ export const withFlattenedRoutes = Component => {
return WithFlattenedRoutesComponent;
};
+
+/**
+ * ResourceLocatorString is used to direct webapp to correct page.
+ * In contrast to Universal Resource Locator (URL), this doesn't contain protocol, host, or port.
+ */
+export const createResourceLocatorString = (
+ routeName,
+ flattenedRoutes,
+ pathParams = {},
+ searchParams = {}
+) => {
+ const searchQuery = stringify(searchParams);
+ const includeSearchQuery = searchQuery.length > 0 ? `?${searchQuery}` : '';
+ const path = pathByRouteName(routeName, flattenedRoutes, pathParams);
+ return `${path}${includeSearchQuery}`;
+};
diff --git a/src/util/routes.test.js b/src/util/routes.test.js
index 50ae433f..2476c9b2 100644
--- a/src/util/routes.test.js
+++ b/src/util/routes.test.js
@@ -1,28 +1,97 @@
import React, { PropTypes } from 'react';
import { RoutesProvider } from '../components';
+import routesConfiguration from '../routesConfiguration';
import { renderDeep, renderShallow } from './test-helpers';
import * as propTypes from './propTypes';
-import { withFlattenedRoutes } from './routes';
+import { createResourceLocatorString, flattenRoutes, withFlattenedRoutes } from './routes';
const { arrayOf } = PropTypes;
-describe('withFlattenedRoutes', () => {
- it('should inject the provided routes', () => {
- const CompComp = props =>
{props.flattenedRoutes[0].name}
;
- CompComp.propTypes = { flattenedRoutes: arrayOf(propTypes.route).isRequired };
- const Comp = withFlattenedRoutes(CompComp);
- const routes = [{ name: 'SomePage', path: 'some-page', component: () => null }];
- const shallowTree = renderShallow(
-
-
-
- );
- expect(shallowTree).toMatchSnapshot();
- const deepTree = renderDeep(
-
-
-
- );
- expect(deepTree).toMatchSnapshot();
+describe('util/routes.js', () => {
+ describe('withFlattenedRoutes', () => {
+ it('should inject the provided routes', () => {
+ const CompComp = props =>
{props.flattenedRoutes[0].name}
;
+ CompComp.propTypes = { flattenedRoutes: arrayOf(propTypes.route).isRequired };
+ const Comp = withFlattenedRoutes(CompComp);
+ const routes = [{ name: 'SomePage', path: 'some-page', component: () => null }];
+ const shallowTree = renderShallow(
+
+
+
+ );
+ expect(shallowTree).toMatchSnapshot();
+ const deepTree = renderDeep(
+
+
+
+ );
+ expect(deepTree).toMatchSnapshot();
+ });
+ });
+
+ describe('createResourceLocatorString', () => {
+ const flattenedRoutes = flattenRoutes(routesConfiguration);
+
+ it('should return meaningful strings if parameters are not needed', () => {
+ // default links without params in path or search query
+ expect(
+ createResourceLocatorString('SearchPage', flattenedRoutes, undefined, undefined)
+ ).toEqual('/s');
+ expect(createResourceLocatorString('SearchPage', flattenedRoutes, {}, {})).toEqual('/s');
+ });
+
+ it('should return meaningful strings with path parameters', () => {
+ expect(
+ createResourceLocatorString(
+ 'ListingPage',
+ flattenedRoutes,
+ { id: '1234', slug: 'nice-listing' },
+ {}
+ )
+ ).toEqual('/l/nice-listing/1234');
+ expect(() =>
+ createResourceLocatorString('ListingPage', flattenedRoutes, {}, {})).toThrowError(
+ TypeError('Expected "slug" to be defined')
+ );
+ expect(() =>
+ createResourceLocatorString(
+ 'ListingPage',
+ flattenedRoutes,
+ { id: '1234' },
+ {}
+ )).toThrowError(TypeError('Expected "slug" to be defined'));
+ expect(() =>
+ createResourceLocatorString(
+ 'ListingPage',
+ flattenedRoutes,
+ { slug: 'nice-listing' },
+ {}
+ )).toThrowError(TypeError('Expected "id" to be defined'));
+ });
+
+ it('should return meaningful strings with search parameters', () => {
+ expect(createResourceLocatorString('SearchPage', flattenedRoutes, {}, { page: 2 })).toEqual(
+ '/s?page=2'
+ );
+ expect(
+ createResourceLocatorString(
+ 'SearchPage',
+ flattenedRoutes,
+ {},
+ { address: 'Helsinki', page: 2 }
+ )
+ ).toEqual('/s?address=Helsinki&page=2');
+ });
+
+ it('should return meaningful strings with path and search parameters', () => {
+ expect(
+ createResourceLocatorString(
+ 'ListingPage',
+ flattenedRoutes,
+ { id: '1234', slug: 'nice-listing' },
+ { extrainfo: true }
+ )
+ ).toEqual('/l/nice-listing/1234?extrainfo=true');
+ });
});
});