aboutsummaryrefslogtreecommitdiff
path: root/src/client/react/store
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/react/store')
-rw-r--r--src/client/react/store/actions.js14
-rw-r--r--src/client/react/store/reducers.js142
-rw-r--r--src/client/react/store/reducers.test.js253
3 files changed, 409 insertions, 0 deletions
diff --git a/src/client/react/store/actions.js b/src/client/react/store/actions.js
new file mode 100644
index 0000000..c4cc9ba
--- /dev/null
+++ b/src/client/react/store/actions.js
@@ -0,0 +1,14 @@
+import users from '../users';
+
+export function showRoomFinder() {
+ return (dispatch, getState, getHistory) => {
+ const { user, setUser } = getHistory();
+
+ if (user == null || users.byId[user].type !== 'r') {
+ // We are not currently viewing a room, correct the situation.
+ setUser(users.allRoomIds[0]);
+ }
+
+ dispatch({ type: 'ROOM_FINDER/SHOW' });
+ };
+}
diff --git a/src/client/react/store/reducers.js b/src/client/react/store/reducers.js
new file mode 100644
index 0000000..c2ee7e9
--- /dev/null
+++ b/src/client/react/store/reducers.js
@@ -0,0 +1,142 @@
+/**
+ * Copyright (C) 2018 Noah Loomans
+ *
+ * This file is part of rooster.hetmml.nl.
+ *
+ * rooster.hetmml.nl is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * rooster.hetmml.nl is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rooster.hetmml.nl. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import getSearchResults from '../lib/getSearchResults';
+import users from '../users';
+
+const DEFAULT_STATE = {
+ // results: [
+ // 's/18562',
+ // ],
+ search: {
+ results: [],
+ text: '',
+ selected: null,
+ },
+ isRoomFinderVisible: false,
+ schedules: {},
+};
+
+const schedule = (state = {}, action) => {
+ switch (action.type) {
+ case 'VIEW/FETCH_SCHEDULE_REQUEST':
+ return {
+ ...state,
+ state: 'FETCHING',
+ };
+ case 'VIEW/FETCH_SCHEDULE_SUCCESS':
+ return {
+ ...state,
+ state: 'FINISHED',
+ htmlStr: action.htmlStr,
+ };
+ default:
+ return state;
+ }
+};
+
+function reducer(state = DEFAULT_STATE, action) {
+ switch (action.type) {
+ case 'SEARCH/SET_USER': {
+ const { user } = action;
+
+ if (user == null) {
+ return {
+ ...state,
+ search: DEFAULT_STATE.search,
+ };
+ }
+
+ return {
+ ...state,
+ search: {
+ results: [],
+ text: users.byId[user].value,
+ selected: user,
+ },
+ };
+ }
+
+ case 'SEARCH/INPUT_CHANGE':
+ return {
+ ...state,
+ search: {
+ results: getSearchResults(action.searchText),
+ text: action.searchText,
+ selected: null,
+ },
+ };
+
+ case 'SEARCH/CHANGE_SELECTED_RESULT': {
+ const prevSelectedResult = state.search.selected;
+ const prevSelectedResultIndex = state.search.results.indexOf(prevSelectedResult);
+ let nextSelectedResultIndex = prevSelectedResultIndex + action.relativeChange;
+
+ if (nextSelectedResultIndex < -1) {
+ nextSelectedResultIndex = state.search.results.length - 1;
+ } else if (nextSelectedResultIndex > state.search.results.length - 1) {
+ nextSelectedResultIndex = -1;
+ }
+
+ const nextSelectedResult = nextSelectedResultIndex === -1
+ ? null
+ : state.search.results[nextSelectedResultIndex];
+
+ return {
+ ...state,
+ search: {
+ ...state.search,
+ selected: nextSelectedResult,
+ },
+ };
+ }
+
+ case 'ROOM_FINDER/SHOW':
+ return {
+ ...state,
+ isRoomFinderVisible: true,
+ };
+
+ case 'ROOM_FINDER/HIDE':
+ return {
+ ...state,
+ isRoomFinderVisible: false,
+ };
+
+ case 'VIEW/FETCH_SCHEDULE_REQUEST':
+ case 'VIEW/FETCH_SCHEDULE_SUCCESS':
+ return {
+ ...state,
+ schedules: {
+ ...state.schedules,
+ [`${action.user}:${action.week}`]:
+ schedule(state.schedules[`${action.user}:${action.week}`], action),
+ },
+ };
+
+ default:
+ return state;
+ }
+}
+
+export default reducer;
+export const _test = {
+ DEFAULT_STATE,
+};
diff --git a/src/client/react/store/reducers.test.js b/src/client/react/store/reducers.test.js
new file mode 100644
index 0000000..cd195b0
--- /dev/null
+++ b/src/client/react/store/reducers.test.js
@@ -0,0 +1,253 @@
+/**
+ * Copyright (C) 2018 Noah Loomans
+ *
+ * This file is part of rooster.hetmml.nl.
+ *
+ * rooster.hetmml.nl is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * rooster.hetmml.nl is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rooster.hetmml.nl. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+window.USERS = [
+ { type: 's', value: '18561' },
+ { type: 's', value: '18562' },
+ { type: 's', value: '18563' },
+ { type: 's', value: '18564' },
+ { type: 's', value: '18565' },
+ { type: 's', value: '18566' },
+ { type: 's', value: '18567' },
+ { type: 's', value: '18568' },
+ { type: 's', value: '18569' },
+];
+
+const deepFreeze = require('deep-freeze');
+const reducer = require('./reducers').default;
+const { DEFAULT_STATE } = require('./reducers')._test;
+
+describe('reducers', () => {
+ describe('search', () => {
+ describe('SEARCH/SET_USER', () => {
+ it('Resets the search state if the user is null', () => {
+ const prevState = { search: { foo: 'bar' } };
+ const action = { type: 'SEARCH/SET_USER', user: null };
+
+ deepFreeze([prevState, action]);
+
+ expect(reducer(prevState, action)).toEqual({
+ search: DEFAULT_STATE.search,
+ });
+ });
+
+ it('Sets all the values of that user properly', () => {
+ expect(reducer(undefined, { type: 'SEARCH/SET_USER', user: 's/18561' })).toEqual({
+ ...DEFAULT_STATE,
+ search: {
+ results: [],
+ text: '18561',
+ selected: 's/18561',
+ },
+ });
+ });
+ });
+
+ describe('SEARCH/INPUT_CHANGE', () => {
+ it('Returns no results when nothing is typed in', () => {
+ expect(reducer(undefined, { type: 'SEARCH/INPUT_CHANGE', searchText: '' })).toEqual({
+ ...DEFAULT_STATE,
+ search: {
+ results: [],
+ text: '',
+ selected: null,
+ },
+ });
+ });
+
+ it('Returns no results when a space is typed in', () => {
+ expect(reducer(undefined, { type: 'SEARCH/INPUT_CHANGE', searchText: ' ' })).toEqual({
+ ...DEFAULT_STATE,
+ search: {
+ results: [],
+ text: ' ',
+ selected: null,
+ },
+ });
+ });
+
+ it('Preforms a basic search, only returning four results', () => {
+ expect(reducer(undefined, { type: 'SEARCH/INPUT_CHANGE', searchText: '18' })).toEqual({
+ ...DEFAULT_STATE,
+ search: {
+ results: [
+ 's/18561',
+ 's/18562',
+ 's/18563',
+ 's/18564',
+ ],
+ text: '18',
+ selected: null,
+ },
+ });
+ });
+ });
+
+ describe('SEARCH/CHANGE_SELECTED_RESULT', () => {
+ it('Does nothing when there are no results', () => {
+ const actionPlus = { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: +1 };
+ const actionMin = { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: -1 };
+
+ deepFreeze([DEFAULT_STATE, actionPlus, actionMin]);
+
+ const nextStatePlus = reducer(DEFAULT_STATE, actionPlus);
+ const nextStateMin = reducer(DEFAULT_STATE, actionMin);
+ expect(nextStatePlus).toEqual(DEFAULT_STATE);
+ expect(nextStateMin).toEqual(DEFAULT_STATE);
+ });
+
+ it('Switches to the correct selectedResult when no selected result is selected', () => {
+ const prevState = {
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: null,
+ },
+ };
+
+ const actionPlus = { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: +1 };
+ const actionMin = { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: -1 };
+
+ deepFreeze([prevState, actionPlus, actionMin]);
+
+ const nextStatePlus = reducer(prevState, actionPlus);
+ const nextStateMin = reducer(prevState, actionMin);
+
+ expect(nextStatePlus).toEqual({
+ ...prevState,
+ search: {
+ ...prevState.search,
+ selected: 's/18561',
+ },
+ });
+ expect(nextStateMin).toEqual({
+ ...prevState,
+ search: {
+ ...prevState.search,
+ selected: 's/18563',
+ },
+ });
+ });
+
+ it('Switches to the correct selectedResult when there is a selected result selected', () => {
+ const prevState = {
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: 's/18562',
+ },
+ };
+
+ const actionPlus = { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: +1 };
+ const actionMin = { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: -1 };
+
+ deepFreeze([prevState, actionPlus, actionMin]);
+
+ const nextStatePlus = reducer(prevState, actionPlus);
+ const nextStateMin = reducer(prevState, actionMin);
+
+ expect(nextStatePlus).toEqual({
+ ...prevState,
+ search: {
+ ...prevState.search,
+ selected: 's/18563',
+ },
+ });
+ expect(nextStateMin).toEqual({
+ ...prevState,
+ search: {
+ ...prevState.search,
+ selected: 's/18561',
+ },
+ });
+ });
+
+ it('Properly wraps around when incrementing', () => {
+ expect(reducer({
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: 's/18563',
+ },
+ }, { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: +1 })).toEqual({
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: null,
+ },
+ });
+
+ expect(reducer({
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: null,
+ },
+ }, { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: +1 })).toEqual({
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: 's/18561',
+ },
+ });
+ });
+
+ it('Properly wraps around when decrementing', () => {
+ expect(reducer({
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: 's/18561',
+ },
+ }, { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: -1 })).toEqual({
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: null,
+ },
+ });
+
+ expect(reducer({
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: null,
+ },
+ }, { type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: -1 })).toEqual({
+ ...DEFAULT_STATE,
+ search: {
+ results: ['s/18561', 's/18562', 's/18563'],
+ text: '1856',
+ selected: 's/18563',
+ },
+ });
+ });
+ });
+ });
+});