Skip to content

Commit

Permalink
feature/add ability to sort pins
Browse files Browse the repository at this point in the history
  • Loading branch information
Dereje1 committed Oct 30, 2023
1 parent 6033bc4 commit dec6ae6
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 24 deletions.
16 changes: 13 additions & 3 deletions client/src/components/common/imagebuild/Imagebuild.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';
import { useSelector } from 'react-redux';
import MasonryPins from './MasonryPins';
import PinZoom from '../modal/modalzoom';
import { Loading } from '../common';
Expand All @@ -11,7 +12,7 @@ import {
getZoomedImageStyle,
} from '../../../utils/utils';
import {
PinType, userType, zoomedImageInfoType, imageMetadataType,
PinType, userType, zoomedImageInfoType, imageMetadataType, searchType,
} from '../../../interfaces';
import './imagebuild.scss';
import error from '../../../assets/error.png';
Expand Down Expand Up @@ -46,9 +47,18 @@ function ImageBuild({
const [batchSize, setBatchSize] = useState(initialDisplayPerScroll());
const [displayLogin, setDisplayLogin] = useState(false);

const isSorted = useSelector(({ search }: { search: searchType }) => search.sort);

useEffect(() => {
setLoadedPins(pinList);
}, [pinList]);
if (isSorted) {
const sortedList = pinList.slice().sort(
(a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt),
);
setLoadedPins(sortedList);
} else {
setLoadedPins(pinList);
}
}, [isSorted, pinList]);

useEffect(() => {
setActivePins(loadedPins.slice(0, batchSize));
Expand Down
23 changes: 20 additions & 3 deletions client/src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ import React, { ComponentType } from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { compose } from 'redux';
import IconButton from '@mui/material/IconButton';
import SortIcon from '@mui/icons-material/Sort';
import ShuffleIcon from '@mui/icons-material/Shuffle';
import { getUser } from '../../redux/userSlice';
import { updateSearch } from '../../redux/searchSlice';
import Search from './search';
import Cover from '../cover/cover';
import SignIn from '../signin/signin';
import CollapsibleMenu from './CollapsibleMenu';
import { Loading } from '../common/common';
import { userType } from '../../interfaces';
import { userType, searchType } from '../../interfaces';
import './menu.scss';

export const mapStateToProps = ({ user }: {user: userType}) => ({ user });
export const mapStateToProps = ({ user, search }:
{user: userType, search: searchType}) => ({ user, search });
const actionCreators = {
getUser,
updateSearch,
};

export function Brand() {
Expand Down Expand Up @@ -46,6 +52,8 @@ interface MenuProps {
location: {
pathname: string,
}
updateSearch: (val: string, tagSearch: boolean, sort: boolean) => void
search: searchType
}

interface MenuState {
Expand Down Expand Up @@ -86,14 +94,23 @@ export class Menu extends React.Component<MenuProps, MenuState> {
const {
user: { authenticated, service },
location: { pathname },
updateSearch: toggleSort,
search: { sort },
} = this.props;
const {
displaySignIn,
} = this.state;

return (
<>
<Brand />
<div style={{ display: 'flex', width: 175, justifyContent: 'space-between' }}>
<Brand />
<IconButton onClick={() => toggleSort('', false, !sort)}>
{
sort ? <ShuffleIcon color="primary" /> : <SortIcon color="secondary" />
}
</IconButton>
</div>
{
authenticated ? (
<CollapsibleMenu
Expand Down
3 changes: 2 additions & 1 deletion client/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export interface userType {

export interface searchType {
term: string | null,
tagSearch: boolean
tagSearch: boolean,
sort: boolean
}

export interface providerIconsType {
Expand Down
7 changes: 4 additions & 3 deletions client/src/redux/searchSlice.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { createSlice } from '@reduxjs/toolkit';
import { searchType } from '../interfaces';

const initialState: searchType = { term: null, tagSearch: false };
const initialState: searchType = { term: null, tagSearch: false, sort: false };

export const searchSlice = createSlice({
name: 'search',
initialState,
reducers: {
updateSearch: (state, action) => action.payload,
updateSearch: (state, action) => (action.payload),
},
});

export const updateSearch = (val: string, tagSearch = false) => (
export const updateSearch = (val: string, tagSearch = false, sort = false) => (
searchSlice.actions.updateSearch({
term: val.trim().length ? val.trim().toLowerCase() : null,
tagSearch,
sort,
})
);

Expand Down
10 changes: 10 additions & 0 deletions tests/client/src/components/common/imagebuild/Imagebuild.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
*/
import React from 'react';
import { EnzymePropSelector, shallow } from 'enzyme';
import * as redux from 'react-redux';
import toJson from 'enzyme-to-json';
import ImageBuild from '../../../../../../client/src/components/common/imagebuild/Imagebuild';
import RESTcall from '../../../../../../client/src/crud';
import { pinsStub, reduxStub } from '../../../../stub';
import { PinType, PinnerType, zoomedImageInfoType } from '../../../../../../client/src/interfaces';

// Mock redux hooks
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'), // use actual for all non-hook parts
useSelector: jest.fn((() => false)),
}));

jest.mock('../../../../../../client/src/crud');
const mockedRESTcall = jest.mocked(RESTcall);

Expand Down Expand Up @@ -40,6 +47,9 @@ describe('The ImageBuild component', () => {
});

test('will render....', () => {
jest
.spyOn(redux, 'useSelector')
.mockImplementationOnce(() => true);
const wrapper = shallow(<ImageBuild {...props} />);
expect(toJson(wrapper)).toMatchSnapshot();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ describe('The tags component', () => {
payload: {
tagSearch: true,
term: 'tag 1',
sort: false,
},
});
expect(props.closePin).toHaveBeenCalled();
Expand Down
6 changes: 3 additions & 3 deletions tests/client/src/components/home/home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('The Home Component', () => {
beforeEach(() => {
props = {
user: { ...reduxStub.user },
search: { term: null, tagSearch: false },
search: { term: null, tagSearch: false, sort: false },
};
});
afterEach(() => {
Expand Down Expand Up @@ -45,7 +45,7 @@ describe('The Home Component', () => {
test('Will filter pins if matching search found for description', async () => {
const updatedProps = {
...props,
search: { term: 'id-3', tagSearch: false },
search: { term: 'id-3', tagSearch: false, sort: false },
};
const wrapper = shallow<Home>(<Home {...updatedProps} />);
await Promise.resolve();
Expand All @@ -57,7 +57,7 @@ describe('The Home Component', () => {
test('Will filter pins if matching search found for tags', async () => {
const updatedProps = {
...props,
search: { term: 'tag 2', tagSearch: false },
search: { term: 'tag 2', tagSearch: false, sort: false },
};
const wrapper = shallow(<Home {...updatedProps} />);
await Promise.resolve();
Expand Down
38 changes: 36 additions & 2 deletions tests/client/src/components/menu/__snapshots__/menu.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,24 @@ exports[`The Menu component will render for an authenticated user 1`] = `
openSearch={[Function]}
pathname="/"
/>
<Brand />
<div
style={
{
"display": "flex",
"justifyContent": "space-between",
"width": 175,
}
}
>
<Brand />
<ForwardRef(IconButton)
onClick={[Function]}
>
<Memo(ForwardRef(SortIcon))
color="secondary"
/>
</ForwardRef(IconButton)>
</div>
<CollapsibleMenu
menuClicked={[Function]}
pathname="/"
Expand All @@ -29,7 +46,24 @@ exports[`The Menu component will render for users authenticated as guest 1`] = `
openSearch={[Function]}
pathname="/"
/>
<Brand />
<div
style={
{
"display": "flex",
"justifyContent": "space-between",
"width": 175,
}
}
>
<Brand />
<ForwardRef(IconButton)
onClick={[Function]}
>
<Memo(ForwardRef(SortIcon))
color="secondary"
/>
</ForwardRef(IconButton)>
</div>
<Login
showSignIn={[Function]}
/>
Expand Down
20 changes: 19 additions & 1 deletion tests/client/src/components/menu/menu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ describe('The Menu component', () => {
pathname: '/',
},
getUser: jest.fn(),
updateSearch: jest.fn(),
search: { term: null, tagSearch: false, sort: false },
};
});
afterEach(() => {
Expand Down Expand Up @@ -106,7 +108,7 @@ describe('The Menu component', () => {

test('will map redux state to props', () => {
const mappedProps = mapStateToProps(reduxStub);
expect(mappedProps).toEqual({ user: reduxStub.user });
expect(mappedProps).toEqual({ ...reduxStub });
});

test('will render the brand', () => {
Expand All @@ -118,4 +120,20 @@ describe('The Menu component', () => {
const wrapper = shallow(<Login showSignIn={jest.fn()} />);
expect(toJson(wrapper)).toMatchSnapshot();
});

test('will toggle the pin sort', async () => {
const updatedProps = {
...props,
search: { term: null, tagSearch: false, sort: true },
};
const wrapper = shallow<Menu>(<Menu {...updatedProps} />);
await Promise.resolve();
const sortButton: EnzymePropSelector = wrapper.find('ForwardRef(IconButton)');
const sortIcon: EnzymePropSelector = sortButton.find('Memo(ForwardRef(SortIcon))');
const shuffleIcon: EnzymePropSelector = sortButton.find('Memo(ForwardRef(ShuffleIcon))');
sortButton.props().onClick();
expect(props.updateSearch).toHaveBeenLastCalledWith('', false, false);
expect(sortIcon.isEmptyRender()).toBe(true);
expect(shuffleIcon.isEmptyRender()).toBe(false);
});
});
12 changes: 6 additions & 6 deletions tests/client/src/components/menu/search.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('The search component', () => {
searchInput.props().onChange({ target: { value: 'abc' } });
jest.advanceTimersByTime(1000);
expect(mockdispatch).toHaveBeenCalledWith({
payload: { tagSearch: false, term: 'abc' },
payload: { tagSearch: false, term: 'abc', sort: false },
type: 'search/updateSearch',
});
searchInput = wrapper.find('ForwardRef(InputBase)');
Expand All @@ -66,7 +66,7 @@ describe('The search component', () => {
searchInput.props().onChange({ target: { value: 'abc' } });
jest.advanceTimersByTime(1000);
expect(mockdispatch).toHaveBeenNthCalledWith(1, {
payload: { tagSearch: false, term: 'abc' },
payload: { tagSearch: false, term: 'abc', sort: false },
type: 'search/updateSearch',
});
searchInput = wrapper.find('ForwardRef(InputBase)');
Expand All @@ -77,7 +77,7 @@ describe('The search component', () => {
searchInput = wrapper.find('ForwardRef(InputBase)');
expect(searchInput.props().value).toBe('');
expect(mockdispatch).toHaveBeenNthCalledWith(2, {
payload: { tagSearch: false, term: null },
payload: { tagSearch: false, term: null, sort: false },
type: 'search/updateSearch',
});
});
Expand All @@ -90,7 +90,7 @@ describe('The search component', () => {
searchInput.props().onChange({ target: { value: 'abc' } });
jest.advanceTimersByTime(1000);
expect(mockdispatch).toHaveBeenNthCalledWith(1, {
payload: { tagSearch: false, term: 'abc' },
payload: { tagSearch: false, term: 'abc', sort: false },
type: 'search/updateSearch',
});
searchInput = wrapper.find('ForwardRef(InputBase)');
Expand All @@ -101,7 +101,7 @@ describe('The search component', () => {
searchInput = wrapper.find('ForwardRef(InputBase)');
expect(searchInput.props().value).toBe('');
expect(mockdispatch).toHaveBeenNthCalledWith(2, {
payload: { tagSearch: false, term: null },
payload: { tagSearch: false, term: null, sort: false },
type: 'search/updateSearch',
});
});
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('The search component', () => {
const wrapper = shallow(<Search {...props} isShowing />);
const searchInput = wrapper.find('ForwardRef(InputBase)');
expect(mockdispatch).toHaveBeenCalledWith({
payload: { tagSearch: false, term: 'tag search' },
payload: { tagSearch: false, term: 'tag search', sort: false },
type: 'search/updateSearch',
});
expect(props.openSearch).toHaveBeenCalled();
Expand Down
4 changes: 3 additions & 1 deletion tests/client/src/redux/searchReducer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('The user reducer', () => {
},
};
const newState = searchReducer(undefined, action);
expect(newState).toEqual({ tagSearch: false, term: null });
expect(newState).toEqual({ tagSearch: false, term: null, sort: false });
});
});

Expand All @@ -34,6 +34,7 @@ describe('search actions', () => {
payload: {
term: 'abc',
tagSearch: false,
sort: false,
},
});
});
Expand All @@ -44,6 +45,7 @@ describe('search actions', () => {
payload: {
term: null,
tagSearch: false,
sort: false,
},
});
});
Expand Down
2 changes: 1 addition & 1 deletion tests/client/stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const pinsStub = [
];

export const reduxStub = {
search: { term: null, tagSearch: false },
search: { term: null, tagSearch: false, sort: false },
user: {
authenticated: true,
userId: 'a stub user id',
Expand Down

0 comments on commit dec6ae6

Please sign in to comment.