Skip to content

Commit

Permalink
Merge pull request #22304 from code-dot-org/responses-tab-redux
Browse files Browse the repository at this point in the history
Text responses tab: add textResponsesRedux
  • Loading branch information
Madelyn Kasula committed May 10, 2018
2 parents cc213f9 + 25e0dc7 commit 379acf3
Show file tree
Hide file tree
Showing 2 changed files with 234 additions and 0 deletions.
125 changes: 125 additions & 0 deletions apps/src/templates/textResponses/textResponsesRedux.js
@@ -0,0 +1,125 @@
import {PropTypes} from 'react';

// Shape for an individual text response
export const textResponsePropType = PropTypes.shape({
puzzle: PropTypes.number.isRequired,
question: PropTypes.string,
response: PropTypes.string.isRequired,
stage: PropTypes.string.isRequired,
studentId: PropTypes.number.isRequired,
studentName: PropTypes.string.isRequired,
url: PropTypes.string.isRequired
});

// Initial state of textResponsesRedux
const initialState = {
sectionId: null,
responseDataByScript: {},
isLoadingResponses: false
};

const SET_SECTION_ID = 'textResponses/SET_SECTION_ID';
const SET_TEXT_RESPONSES = 'textResponses/SET_TEXT_RESPONSES';
const START_LOADING_TEXT_RESPONSES = 'textResponses/START_LOADING_TEXT_RESPONSES';
const FINISH_LOADING_TEXT_RESPONSES = 'textResponses/FINISH_LOADING_TEXT_RESPONSES';

// Action creators
export const setSectionId = sectionId => ({ type: SET_SECTION_ID, sectionId });
export const setTextResponses = (scriptId, responseData) => ({ type: SET_TEXT_RESPONSES, scriptId, responseData});
export const startLoadingResponses = () => ({ type: START_LOADING_TEXT_RESPONSES });
export const finishLoadingResponses = () => ({ type: FINISH_LOADING_TEXT_RESPONSES });

export const asyncLoadTextResponses = (sectionId, scriptId, onComplete) => {
return (dispatch, getState) => {
const state = getState().textResponses;

// Don't load data if it's already stored in redux.
if (state.responseDataByScript[scriptId]) {
onComplete();
return;
}

dispatch(startLoadingResponses());
loadTextResponsesFromServer(sectionId, scriptId, (error, data) => {
if (error) {
console.error(error);
} else {
dispatch(setTextResponses(scriptId, data));
onComplete();
}
dispatch(finishLoadingResponses());
});
};
};

export default function textResponses(state=initialState, action) {
if (action.type === SET_SECTION_ID) {
// Setting the sectionId is the first action to be called when switching
// sections, which requires us to reset our state. This might need to change
// once switching sections is in react/redux.
return {
...initialState,
sectionId: action.sectionId
};
}
if (action.type === SET_TEXT_RESPONSES) {
return {
...state,
responseDataByScript: {
...state.responseDataByScript,
[action.scriptId]: action.responseData
}
};
}
if (action.type === START_LOADING_TEXT_RESPONSES) {
return {
...state,
isLoadingResponses: true
};
}
if (action.type === FINISH_LOADING_TEXT_RESPONSES) {
return {
...state,
isLoadingResponses: false
};
}

return state;
}

// Flatten text responses returned from server to remove nested student object
export const convertTextResponseServerData = (textResponses) => {
let responses = [];
textResponses.forEach(response => {
const {id, name} = response.student;
delete response.student;

responses.push({
...response,
studentId: id,
studentName: name
});
});

return responses;
};

// Make a request to the server for text responses
// scriptId is not required; endpoint will use the default script if no scriptId is supplied
const loadTextResponsesFromServer = (sectionId, scriptId, onComplete) => {
let payload = {};
if (scriptId) {
payload.script_id = scriptId;
}

$.ajax({
url: `/dashboardapi/section_text_responses/${sectionId}`,
method: 'GET',
contentType: 'application/json;charset=UTF-8',
data: payload
}).done(responseData => {
onComplete(null, convertTextResponseServerData(responseData));
}).fail((jqXhr, status) => {
onComplete(status, jqXhr.responseJSON);
});
};
109 changes: 109 additions & 0 deletions apps/test/unit/templates/textResponses/textResponsesReduxTest.js
@@ -0,0 +1,109 @@
import {assert} from '../../../util/configuredChai';
import textResponses, {
setSectionId,
setTextResponses,
startLoadingResponses,
finishLoadingResponses,
convertTextResponseServerData
} from '@cdo/apps/templates/textResponses/textResponsesRedux';

describe('textResponsesRedux', () => {
const initialState = textResponses(undefined, {});

describe('setSectionId', () => {
it('sets sectionId', () => {
const action = setSectionId(3);
const nextState = textResponses(initialState, action);
assert.equal(nextState.sectionId, 3);
});

it('resets all other state to initialState', () => {
const currentState = {
sectionId: 1,
isLoadingResponses: true,
responseDataByScript: {
1: {question: 'Question 1', response: 'Response 1'},
2: {question: 'Question 2', response: 'Response 2'}
}
};
const newSectionId = 2;
const expectedNextState = {
...initialState,
sectionId: newSectionId
};
const action = setSectionId(newSectionId);
const nextState = textResponses(currentState, action);
assert.deepEqual(nextState, expectedNextState);
});
});

describe('setTextResponses', () => {
it('associates the response data to the correct script', () => {
const scriptId = 2;
const responseData = {
question: 'Free Response',
response: 'I love to code!'
};
const action = setTextResponses(scriptId, responseData);
const nextState = textResponses(initialState, action);
const actualResponseData = nextState.responseDataByScript[scriptId];
assert.deepEqual(actualResponseData, responseData);
});
});

describe('startLoadingResponses', () => {
it('sets isLoadingResponses to true', () => {
const action = startLoadingResponses();
const nextState = textResponses(initialState, action);
assert.isTrue(nextState.isLoadingResponses);
});
});

describe('finishLoadingResponses', () => {
it('sets isLoadingResponses to false', () => {
const action = finishLoadingResponses();
const nextState = textResponses(initialState, action);
assert.isFalse(nextState.isLoadingResponses);
});
});

describe('convertTextResponseServerData', () => {
it('re-formats server data correctly', () => {
const serverTextResponses = [
{
question: 'Question 1',
response: 'Response 1',
student: {
id: 1,
name: 'Student 1'
}
},
{
question: 'Question 2',
response: 'Response 2',
student: {
id: 2,
name: 'Student 2'
}
},
];
const expectedTextResponses = [
{
question: 'Question 1',
response: 'Response 1',
studentId: 1,
studentName: 'Student 1'
},
{
question: 'Question 2',
response: 'Response 2',
studentId: 2,
studentName: 'Student 2'
},
];

const convertedTextResponses = convertTextResponseServerData(serverTextResponses);
assert.deepEqual(convertedTextResponses, expectedTextResponses);
});
});
});

0 comments on commit 379acf3

Please sign in to comment.