From fe9a4b17cfd1188422e53cc3864bdc4c50040f3f Mon Sep 17 00:00:00 2001 From: Donal Stewart Date: Tue, 12 Jan 2021 10:23:42 +0000 Subject: [PATCH 1/5] Fixed question capitalisation --- sharedmodel/src/Content.jsx | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/sharedmodel/src/Content.jsx b/sharedmodel/src/Content.jsx index edcff47..2fd8c4c 100644 --- a/sharedmodel/src/Content.jsx +++ b/sharedmodel/src/Content.jsx @@ -114,25 +114,25 @@ export const sectionsContent = [ {addQuestion( SCALE_WITH_COMMENT, "classroom", - "We have an outdoor classroom area that can be used by a whole class" + "we have an outdoor classroom area that can be used by a whole class" )}
{addQuestion( SCALE_WITH_COMMENT, "seating", - "We have outdoor seating areas for working in smaller groups" + "we have outdoor seating areas for working in smaller groups" )}
{addQuestion( SCALE_WITH_COMMENT, "sheltered", - "Outdoor classrooms and seating areas are reasonably sheltered and comfortable to use" + "outdoor classrooms and seating areas are reasonably sheltered and comfortable to use" )}
{addQuestion( SCALE_WITH_COMMENT, "disturbance", - "Using outdoor classrooms and curriculum features doesn't cause significant disturbance to indoor classes" + "using outdoor classrooms and curriculum features doesn't cause significant disturbance to indoor classes" )} ), @@ -538,7 +538,7 @@ export const sectionsContent = [ {addQuestion( TEXT_AREA, "namegreenspace", - "What area of nearby local greenspace has the most potential for regular school use for outdoor learning and play?" + "what area of nearby local greenspace has the most potential for regular school use for outdoor learning and play?" )}

@@ -552,51 +552,51 @@ export const sectionsContent = [ {addQuestion( SCALE_WITH_COMMENT, "accessible", - "This area is readily accessible for regular use by the school" + "this area is readily accessible for regular use by the school" )}


{addQuestion( TEXT_AREA, "improveaccessible", - "How might you be able to improve its accessibility - for example with a gate, clearing a path, bridging a ditch etc?" + "how might you be able to improve its accessibility - for example with a gate, clearing a path, bridging a ditch etc?" )}
{addQuestion( SCALE_WITH_COMMENT, "frequentuse", - "The school uses this space alot (e.g. at least monthly)" + "the school uses this space alot (e.g. at least monthly)" )}
{addQuestion( SCALE_WITH_COMMENT, "wildlife", - "The area is really valuable for wildlife" + "the area is really valuable for wildlife" )}
{addQuestion( TEXT_AREA, "improvewildlife", - "How might you improve the area for wildlife?" + "how might you improve the area for wildlife?" )}
{addQuestion( SCALE_WITH_COMMENT, "teaching", - "As it is, the area is a really useful space for teaching and play" + "as it is, the area is a really useful space for teaching and play" )}
{addQuestion( TEXT_AREA, "improveteaching", - "How could it be made more useful for teaching and play?" + "how could it be made more useful for teaching and play?" )}
- {addQuestion(TEXT_AREA, "listowner", "Who owns this area of land?")} + {addQuestion(TEXT_AREA, "listowner", "who owns this area of land?")}
{addQuestion( SCALE_WITH_COMMENT, "changes", - "Will it be straightforward to agree any changes you'd like to make with the land owner?" + "will it be straightforward to agree any changes you'd like to make with the land owner?" )} ), @@ -708,13 +708,13 @@ export const sectionsContent = [ {addQuestion( SCALE_WITH_COMMENT, "groundsadvisor", - "If there was a school grounds advisor available in this area then would you make use of them in your school?" + "if there was a school grounds advisor available in this area then would you make use of them in your school?" )}
{addQuestion( SCALE_WITH_COMMENT, "onlineresources", - "Would it be helpful to have online information and resources on how to develop some of the ideas suggested in this audit?" + "would it be helpful to have online information and resources on how to develop some of the ideas suggested in this audit?" )}
{addQuestion( @@ -726,13 +726,13 @@ export const sectionsContent = [ {addQuestion( SCALE_WITH_COMMENT, "straightforward", - "Would you say it was fairly straightforward to answer the questions in this audit?" + "would you say it was fairly straightforward to answer the questions in this audit?" )}
{addQuestion( SCALE_WITH_COMMENT, "ideas", - "Would you say that the process of completing this audit has given you some ideas for how to improve your outdoor learning and play provision?" + "would you say that the process of completing this audit has given you some ideas for how to improve your outdoor learning and play provision?" )} ), From c4180f13550f055f75c16e20d0b4db5dd7d17035 Mon Sep 17 00:00:00 2001 From: Donal Stewart Date: Tue, 12 Jan 2021 13:03:52 +0000 Subject: [PATCH 2/5] Bugfix - exporting to csv twice didn't work --- adminclient/src/App.js | 115 ++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 64 deletions(-) diff --git a/adminclient/src/App.js b/adminclient/src/App.js index 5e24a50..596309b 100644 --- a/adminclient/src/App.js +++ b/adminclient/src/App.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState } from "react"; import "./App.css"; import AppBar from "@material-ui/core/AppBar"; import Toolbar from "@material-ui/core/Toolbar"; @@ -65,6 +65,15 @@ const useStyles = makeStyles((theme) => ({ }, })); +function getSurveyIdsToRetrieve(selectedSurveyIds, fullSurveyResponses) { + return selectedSurveyIds.filter( + (surveyId) => + fullSurveyResponses.find( + (fullSurveyResponse) => surveyId === fullSurveyResponse.id + ) === undefined + ); +} + function App() { const classes = useStyles(); const [surveyResponses, setSurveyResponses] = useState([]); @@ -72,8 +81,7 @@ function App() { const [dataRows, setDataRows] = useState([]); const [openSurveyResponses, setOpenSurveyResponses] = useState(false); const [selectedSurveyIds, setSelectedSurveyIds] = useState([]); - const exportCsvRequested = useRef(false); - const allResponsesRetrieved = useRef(true); + const [exportCsvRequested, setExportCsvRequested] = useState(false); useEffect(() => { if (surveyResponses != null && surveyResponses.length > 0) { @@ -118,67 +126,52 @@ function App() { }, []); useEffect(() => { - if (selectedSurveyIds.length === 0) { - allResponsesRetrieved.current = true; - return; - } - const surveyIdsToRetrieve = selectedSurveyIds.filter( - (surveyId) => - fullSurveyResponses.find( - (fullSurveyResponse) => surveyId === fullSurveyResponse.id - ) === undefined + const surveyIdsToRetrieve = getSurveyIdsToRetrieve( + selectedSurveyIds, + fullSurveyResponses ); - if (surveyIdsToRetrieve.length === 0) { - allResponsesRetrieved.current = true; - return; - } - - allResponsesRetrieved.current = false; - - Auth.currentCredentials() - .then((credentials) => { - const dynamodbClient = new DynamoDBClient({ - region: REGION, - credentials: credentials, + if (surveyIdsToRetrieve.length > 0) { + Auth.currentCredentials() + .then((credentials) => { + const dynamodbClient = new DynamoDBClient({ + region: REGION, + credentials: credentials, + }); + const params = { + RequestItems: {}, + ReturnConsumedCapacity: "TOTAL", + }; + params.RequestItems[SURVEY_RESPONSES_TABLE] = { + Keys: surveyIdsToRetrieve.map((id) => { + return { id: { S: id } }; + }), + }; + console.log("Retrieving full responses data", params); + return dynamodbClient.send(new BatchGetItemCommand(params)); + }) + .then((result) => { + console.log("Survey responses", result); + const retrievedResponses = result.Responses[ + SURVEY_RESPONSES_TABLE + ].map((item) => unmarshall(item)); + setFullSurveyResponses((fullSurveyResponses) => [ + ...fullSurveyResponses, + ...retrievedResponses, + ]); + }) + .catch((error) => { + console.log("User not logged in", error); }); - const params = { - RequestItems: {}, - ReturnConsumedCapacity: "TOTAL", - }; - params.RequestItems[SURVEY_RESPONSES_TABLE] = { - Keys: surveyIdsToRetrieve.map((id) => { - return { id: { S: id } }; - }), - }; - console.log("Retrieving full responses data", params); - return dynamodbClient.send(new BatchGetItemCommand(params)); - }) - .then((result) => { - console.log("Survey responses", result); - const retrievedResponses = result.Responses[ - SURVEY_RESPONSES_TABLE - ].map((item) => unmarshall(item)); - setFullSurveyResponses((fullSurveyResponses) => [ - ...fullSurveyResponses, - ...retrievedResponses, - ]); - allResponsesRetrieved.current = true; - }) - .catch((error) => { - console.log("User not logged in", error); - }); + } }, [selectedSurveyIds, fullSurveyResponses]); useEffect(() => { - console.log( - "export status", - exportCsvRequested, - allResponsesRetrieved, + const surveyIdsToRetrieve = getSurveyIdsToRetrieve( selectedSurveyIds, fullSurveyResponses ); - if (exportCsvRequested.current && allResponsesRetrieved.current) { - exportCsvRequested.current = false; + if (exportCsvRequested && surveyIdsToRetrieve.length === 0) { + setExportCsvRequested(false); exportSurveysAsCsv( fullSurveyResponses.filter((surveyResponse) => @@ -186,17 +179,11 @@ function App() { ) ); } - }, [ - selectedSurveyIds, - fullSurveyResponses, - exportCsvRequested, - allResponsesRetrieved, - ]); + }, [selectedSurveyIds, fullSurveyResponses, exportCsvRequested]); function requestExportCsv(surveyIds) { - allResponsesRetrieved.current = false; - exportCsvRequested.current = true; setSelectedSurveyIds(surveyIds); + setExportCsvRequested(true); } return ( From 3478a2d45c47519583d8b8aaeefc0d12be5dd74f Mon Sep 17 00:00:00 2001 From: Donal Stewart Date: Tue, 12 Jan 2021 14:34:45 +0000 Subject: [PATCH 3/5] Remove survey use when logged out --- surveyclient/src/App.css | 7 -- .../src/components/auth/AuthSignInOut.jsx | 67 ++++++++++++++--- .../components/auth/ConfirmRegistration.jsx | 2 - .../auth/ConfirmRegistration.test.js | 38 +--------- .../auth/ContinueSignedOutButton.jsx | 24 ------- .../auth/ContinueSignedOutButton.test.js | 72 ------------------- .../components/auth/ForgotPasswordRequest.jsx | 2 - .../auth/ForgotPasswordRequest.test.js | 38 +--------- .../components/auth/ForgotPasswordSubmit.jsx | 2 - .../auth/ForgotPasswordSubmit.test.js | 71 ++++++------------ surveyclient/src/components/auth/Register.jsx | 2 - .../src/components/auth/Register.test.js | 34 +-------- .../components/auth/RequireNewPassword.jsx | 2 - .../auth/RequireNewPassword.test.js | 34 +-------- surveyclient/src/components/auth/SignIn.jsx | 2 - .../src/components/auth/SignIn.test.js | 33 +-------- 16 files changed, 96 insertions(+), 334 deletions(-) delete mode 100644 surveyclient/src/components/auth/ContinueSignedOutButton.jsx delete mode 100644 surveyclient/src/components/auth/ContinueSignedOutButton.test.js diff --git a/surveyclient/src/App.css b/surveyclient/src/App.css index 63e753c..dc21813 100644 --- a/surveyclient/src/App.css +++ b/surveyclient/src/App.css @@ -870,13 +870,6 @@ textarea { padding: 0; } -.section.authenticator button#continue-signed-out-button { - margin-left: 40px; - background-color: var(--colour-ltl-orange); - color: black; - font-weight: bold; -} - .section.authenticator h2, .section.get-started h2 { font-family: var(--font-family-ltl-heading); diff --git a/surveyclient/src/components/auth/AuthSignInOut.jsx b/surveyclient/src/components/auth/AuthSignInOut.jsx index 2b439b2..b3771d1 100644 --- a/surveyclient/src/components/auth/AuthSignInOut.jsx +++ b/surveyclient/src/components/auth/AuthSignInOut.jsx @@ -3,9 +3,50 @@ import { useDispatch, useSelector } from "react-redux"; import { SIGN_IN, SIGNED_OUT, SIGNED_IN } from "../../model/AuthStates"; import { setAuthState, signOut } from "../../model/AuthActions"; import "../../App.css"; +import Modal from "@material-ui/core/Modal"; + +function ConfirmDialog({ closeDialog }) { + return ( + window.document.body : undefined} + keepMounted={false} + open={true} + onClose={() => closeDialog(false)} + style={{ + display: "flex", + alignItems: "center", + justifyContent: "center", + }} + > +
+

Log out?

+

Are you sure you want to log out?

+ + +
+
+ ); +} export default function AuthSignInOut() { const [signedIn, setSignedIn] = useState(false); + const [showConfirmDialog, setShowConfirmDialog] = useState(false); const dispatch = useDispatch(); const authState = useSelector((state) => state.authentication.state); @@ -19,16 +60,26 @@ export default function AuthSignInOut() { }, [authState]); function handleClick(event) { - dispatch(signedIn ? signOut() : setAuthState(SIGN_IN)); + signedIn ? setShowConfirmDialog(true) : dispatch(setAuthState(SIGN_IN)); } return ( - + <> + + {showConfirmDialog && ( + { + closeConfirmed && dispatch(signOut()); + setShowConfirmDialog(false); + }} + /> + )} + ); } diff --git a/surveyclient/src/components/auth/ConfirmRegistration.jsx b/surveyclient/src/components/auth/ConfirmRegistration.jsx index 912a654..83f4085 100644 --- a/surveyclient/src/components/auth/ConfirmRegistration.jsx +++ b/surveyclient/src/components/auth/ConfirmRegistration.jsx @@ -6,7 +6,6 @@ import { resendConfirmCode, confirmRegistration, } from "../../model/AuthActions"; -import ContinueSignedOutButton from "./ContinueSignedOutButton"; const EMAIL_ID = "confirmEmailInput"; const CODE_ID = "codeInput"; @@ -74,7 +73,6 @@ export default function ConfirmRegistration() { > {loading ?
: CONFIRM} -
- ); -} diff --git a/surveyclient/src/components/auth/ContinueSignedOutButton.test.js b/surveyclient/src/components/auth/ContinueSignedOutButton.test.js deleted file mode 100644 index 3751faa..0000000 --- a/surveyclient/src/components/auth/ContinueSignedOutButton.test.js +++ /dev/null @@ -1,72 +0,0 @@ -import React from "react"; -import { render, unmountComponentAtNode } from "react-dom"; -import { act } from "react-dom/test-utils"; -import ContinueSignedOutButton from "./ContinueSignedOutButton"; -import surveyStore from "../../model/SurveyModel"; -import { Provider } from "react-redux"; -import { REFRESH_STATE } from "../../model/ActionTypes"; -import { SIGNED_OUT, REGISTER } from "../../model/AuthStates"; - -describe("component ContinueSignedOutButton", () => { - var container = null; - beforeEach(() => { - // setup a DOM element as a render target - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - // cleanup on exiting - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - - it("first login", () => { - surveyStore.dispatch({ - type: REFRESH_STATE, - state: { hasEverLoggedIn: false }, - }); - renderComponent(); - - expect(continueButton()).toBeNull(); - }); - - it("not first login", () => { - surveyStore.dispatch({ - type: REFRESH_STATE, - state: { - hasEverLoggedIn: true, - authentication: { errorMessage: "", state: REGISTER, user: undefined }, - }, - }); - renderComponent(); - - expect(continueButton()).not.toBeNull(); - click(continueButton()); - - expect(surveyStore.getState().authentication.state).toStrictEqual( - SIGNED_OUT - ); - }); - - const continueButton = () => - container.querySelector("#continue-signed-out-button"); - - function click(element) { - act(() => { - element.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); - } - - function renderComponent() { - act(() => { - render( - - - , - container - ); - }); - } -}); diff --git a/surveyclient/src/components/auth/ForgotPasswordRequest.jsx b/surveyclient/src/components/auth/ForgotPasswordRequest.jsx index 4360163..75e28b3 100644 --- a/surveyclient/src/components/auth/ForgotPasswordRequest.jsx +++ b/surveyclient/src/components/auth/ForgotPasswordRequest.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { SIGN_IN } from "../../model/AuthStates"; import { setAuthState, forgotPasswordRequest } from "../../model/AuthActions"; -import ContinueSignedOutButton from "./ContinueSignedOutButton"; const EMAIL_ID = "emailInput"; @@ -42,7 +41,6 @@ export default function ForgotPasswordRequest() { > {loading ?
: SEND CODE} -
-
-
Already have an account?{" "} diff --git a/surveyclient/src/components/auth/Register.test.js b/surveyclient/src/components/auth/Register.test.js index 6f55df5..4dedb9c 100644 --- a/surveyclient/src/components/auth/Register.test.js +++ b/surveyclient/src/components/auth/Register.test.js @@ -4,12 +4,8 @@ import { act, Simulate } from "react-dom/test-utils"; import Register from "./Register"; import surveyStore from "../../model/SurveyModel"; import { Provider } from "react-redux"; -import { - SET_AUTH_STATE, - SET_AUTH_ERROR, - REFRESH_STATE, -} from "../../model/ActionTypes"; -import { SIGN_IN, SIGNED_OUT, REGISTER } from "../../model/AuthStates"; +import { SET_AUTH_STATE, SET_AUTH_ERROR } from "../../model/ActionTypes"; +import { SIGN_IN, REGISTER } from "../../model/AuthStates"; import { setAuthState, register } from "../../model/AuthActions"; const TEST_USER = "test@example.com"; @@ -42,14 +38,13 @@ describe("component Register", () => { container = null; }); - it("initial render and enable register action - first login", () => { + it("initial render and enable register action", () => { renderComponent(); expect(emailInput().value).toStrictEqual(""); expect(passwordInput().value).toStrictEqual(""); expect(tncCheck()).not.toBeChecked(); expect(gdprCheck()).not.toBeChecked(); expect(registerButton()).toBeDisabled(); - expect(continueButton()).toBeNull(); // Form complete enterEmail(TEST_USER); @@ -169,27 +164,6 @@ describe("component Register", () => { expect(registerButton()).not.toBeDisabled(); }); - it("not first login can continue", () => { - surveyStore.dispatch({ - type: REFRESH_STATE, - state: { - hasEverLoggedIn: true, - authentication: { - errorMessage: "", - state: REGISTER, - user: undefined, - }, - }, - }); - renderComponent(); - - expect(continueButton()).not.toBeNull(); - click(continueButton()); - - expect(setAuthState).toHaveBeenCalledTimes(1); - expect(setAuthState).toHaveBeenCalledWith(SIGNED_OUT); - }); - it("gdpr popup", () => { renderComponent(); expect(gdprPopup()).toBeNull(); @@ -220,8 +194,6 @@ describe("component Register", () => { const passwordInput = () => container.querySelector("#passwordInput"); const registerButton = () => container.querySelector("#register-button"); const signInButton = () => container.querySelector("#signin-button"); - const continueButton = () => - container.querySelector("#continue-signed-out-button"); const gdprPopupCloseButton = () => document.querySelector(".tooltip-popup .close-button"); const backdrop = () => diff --git a/surveyclient/src/components/auth/RequireNewPassword.jsx b/surveyclient/src/components/auth/RequireNewPassword.jsx index 7aa6872..9007366 100644 --- a/surveyclient/src/components/auth/RequireNewPassword.jsx +++ b/surveyclient/src/components/auth/RequireNewPassword.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { SIGN_IN } from "../../model/AuthStates"; import { setAuthState, completeNewPassword } from "../../model/AuthActions"; -import ContinueSignedOutButton from "./ContinueSignedOutButton"; const PASSWORD_ID = "passwordInput"; const MIN_PASSWORD_LENGTH = 8; @@ -47,7 +46,6 @@ export default function RequireNewPassword() { > {loading ?
: CHANGE} -
-
Don't have an account?{" "} diff --git a/surveyclient/src/components/auth/SignIn.test.js b/surveyclient/src/components/auth/SignIn.test.js index 652cd34..bde9752 100644 --- a/surveyclient/src/components/auth/SignIn.test.js +++ b/surveyclient/src/components/auth/SignIn.test.js @@ -4,14 +4,9 @@ import { act, Simulate } from "react-dom/test-utils"; import SignIn from "./SignIn"; import surveyStore from "../../model/SurveyModel"; import { Provider } from "react-redux"; -import { - SET_AUTH_STATE, - SET_AUTH_ERROR, - REFRESH_STATE, -} from "../../model/ActionTypes"; +import { SET_AUTH_STATE, SET_AUTH_ERROR } from "../../model/ActionTypes"; import { SIGN_IN, - SIGNED_OUT, SIGNED_IN, REGISTER, FORGOT_PASSWORD_REQUEST, @@ -48,12 +43,11 @@ describe("component SignIn", () => { container = null; }); - it("initial render and enable signIn action - first login", () => { + it("initial render and enable signIn action", () => { renderComponent(); expect(emailInput().value).toStrictEqual(""); expect(passwordInput().value).toStrictEqual(""); expect(signInButton()).toBeDisabled(); - expect(continueButton()).toBeNull(); // Form complete enterEmail(TEST_USER); @@ -143,35 +137,12 @@ describe("component SignIn", () => { expect(signInButton()).not.toBeDisabled(); }); - it("not first login can continue", () => { - surveyStore.dispatch({ - type: REFRESH_STATE, - state: { - hasEverLoggedIn: true, - authentication: { - errorMessage: "", - state: SIGN_IN, - user: undefined, - }, - }, - }); - renderComponent(); - - expect(continueButton()).not.toBeNull(); - click(continueButton()); - - expect(setAuthState).toHaveBeenCalledTimes(1); - expect(setAuthState).toHaveBeenCalledWith(SIGNED_OUT); - }); - const emailInput = () => container.querySelector("#emailInput"); const passwordInput = () => container.querySelector("#passwordInput"); const registerButton = () => container.querySelector("#register-button"); const forgotPasswordButton = () => container.querySelector("#forgot-password-button"); const signInButton = () => container.querySelector("#signin-button"); - const continueButton = () => - container.querySelector("#continue-signed-out-button"); function enterEmail(value) { act(() => { From 0c540fa0850a385b6ea55ecb139cfa766fecee93 Mon Sep 17 00:00:00 2001 From: Donal Stewart Date: Tue, 12 Jan 2021 17:27:20 +0000 Subject: [PATCH 4/5] Added confirm dialogs and restart button --- surveyclient/src/App.css | 30 +++-- surveyclient/src/App.js | 10 +- surveyclient/src/components/ConfirmDialog.jsx | 45 ++++++++ .../src/components/ConfirmDialog.test.js | 82 +++++++++++++ .../src/components/GetStartedScreen.jsx | 2 +- .../src/components/QuestionCommentDialog.jsx | 4 +- .../src/components/QuestionPhotosDialog.jsx | 2 +- .../components/QuestionSelectWithComment.jsx | 2 +- surveyclient/src/components/QuestionText.jsx | 2 +- .../src/components/QuestionTextWithYear.jsx | 2 +- .../src/components/QuestionUserSelect.jsx | 2 +- surveyclient/src/components/RestartButton.jsx | 38 ++++++ .../src/components/RestartButton.test.js | 108 ++++++++++++++++++ surveyclient/src/components/SubmitSection.jsx | 59 +++++++--- .../src/components/SubmitSection.test.js | 82 ++++++++++--- .../{AuthSignInOut.jsx => AuthSignOut.jsx} | 0 ...hSignInOut.test.js => AuthSignOut.test.js} | 0 surveyclient/src/model/ActionTypes.js | 1 + surveyclient/src/model/SurveyModel.js | 17 ++- surveyclient/src/model/SurveyModel.test.js | 20 +++- 20 files changed, 455 insertions(+), 53 deletions(-) create mode 100644 surveyclient/src/components/ConfirmDialog.jsx create mode 100644 surveyclient/src/components/ConfirmDialog.test.js create mode 100644 surveyclient/src/components/RestartButton.jsx create mode 100644 surveyclient/src/components/RestartButton.test.js rename surveyclient/src/components/auth/{AuthSignInOut.jsx => AuthSignOut.jsx} (100%) rename surveyclient/src/components/auth/{AuthSignInOut.test.js => AuthSignOut.test.js} (100%) diff --git a/surveyclient/src/App.css b/surveyclient/src/App.css index a0f7201..b6a6e3d 100644 --- a/surveyclient/src/App.css +++ b/surveyclient/src/App.css @@ -114,8 +114,9 @@ textarea { /** Active shadows **/ -.app-bar #auth-signin-signout, +.app-bar #auth-signout, .app-bar .download-button, +.app-bar #restart-button, .section.authenticator button, .dialog button, .submit-survey-button, @@ -125,8 +126,9 @@ textarea { box-shadow: 0px 2px 4px #00000080; } -.app-bar #auth-signin-signout:active, +.app-bar #auth-signout:active, .app-bar .download-button:active, +.app-bar #restart-button:active, .section.authenticator button:disabled, .section.authenticator button:active, .dialog button:active, @@ -194,8 +196,9 @@ textarea { margin-right: 130px; } -.app-bar #auth-signin-signout, -.app-bar .download-button { +.app-bar #auth-signout, +.app-bar .download-button, +.app-bar #restart-button { position: absolute; width: 100px; height: 40px; @@ -204,11 +207,16 @@ textarea { font-weight: normal; border: none; } -.app-bar #auth-signin-signout { +.app-bar #auth-signout { background-color: var(--colour-ltl-blue); color: white; top: 70px; } +.app-bar #restart-button { + background-color: var(--colour-ltl-orange); + color: black; + top: 120px; +} .app-bar .download-button { background-color: var(--colour-ltl-green); color: black; @@ -225,7 +233,7 @@ textarea { .app-bar .auth-current-user { display: block; position: absolute; - right: 20px; + left: 20px; top: 150px; font-weight: bold; font-size: 15px; @@ -1180,16 +1188,22 @@ textarea { } .app-bar .download-button, - .app-bar #auth-signin-signout { + .app-bar #restart-button, + .app-bar #auth-signout { width: 75px; height: 30px; } - .app-bar #auth-signin-signout { + .app-bar #auth-signout { right: 20px; top: 60px; } + .app-bar #restart-button { + right: 20px; + top: 100px; + } + .app-bar .download-button { right: 20px; top: 20px; diff --git a/surveyclient/src/App.js b/surveyclient/src/App.js index 959da64..c4c5df8 100644 --- a/surveyclient/src/App.js +++ b/surveyclient/src/App.js @@ -9,8 +9,9 @@ import GallerySection from "./components/GallerySection"; import SubmitSection from "./components/SubmitSection"; import NavDrawer from "./components/NavDrawer"; import Section from "./components/Section"; -import AuthSignInOut from "./components/auth/AuthSignInOut"; +import AuthSignOut from "./components/auth/AuthSignOut"; import AuthCurrentUser from "./components/auth/AuthCurrentUser"; +import RestartButton from "./components/RestartButton"; import GetStartedScreen from "./components/GetStartedScreen"; import { useDispatch, useSelector } from "react-redux"; import { refreshState } from "./model/SurveyModel"; @@ -50,7 +51,7 @@ function App() { const authState = useSelector((state) => state.authentication.state); const hasSeenSplashPage = useSelector((state) => state.hasSeenSplashPage); - const [currentSection, _setCurrentSection] = useState("introduction"); + const [currentSection, _setCurrentSection] = useState(INTRODUCTION); const [popupNavDrawerOpen, setPopupNavDrawerOpen] = useState(false); // Restore locally stored answers if existing @@ -286,7 +287,7 @@ function App() {
{titleLogo()} {titleText()} - +
@@ -309,7 +310,8 @@ function App() { {titleLogo()} {titleText()} - + + setCurrentSection(INTRODUCTION)} /> {downloadButtonAppBar()} {!isLive &&
{ENVIRONMENT_NAME}
} diff --git a/surveyclient/src/components/ConfirmDialog.jsx b/surveyclient/src/components/ConfirmDialog.jsx new file mode 100644 index 0000000..095a436 --- /dev/null +++ b/surveyclient/src/components/ConfirmDialog.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import "../App.css"; +import Modal from "@material-ui/core/Modal"; + +export default function ConfirmDialog({ + closeDialog, + yesText = "YES", + noText = "NO", + children, +}) { + return ( + window.document.body : undefined} + keepMounted={false} + open={true} + onClose={() => closeDialog(false)} + style={{ + display: "flex", + alignItems: "center", + justifyContent: "center", + }} + > +
+ {children} +
+ + +
+
+
+ ); +} diff --git a/surveyclient/src/components/ConfirmDialog.test.js b/surveyclient/src/components/ConfirmDialog.test.js new file mode 100644 index 0000000..80e2720 --- /dev/null +++ b/surveyclient/src/components/ConfirmDialog.test.js @@ -0,0 +1,82 @@ +import React from "react"; +import { render, unmountComponentAtNode } from "react-dom"; +import { act } from "react-dom/test-utils"; +import ConfirmDialog from "./ConfirmDialog"; + +var closeDialogResponse = null; +function closeDialog(value) { + closeDialogResponse = value; +} +describe("component ConfirmDialog", () => { + var container = null; + beforeEach(() => { + // setup a DOM element as a render target + container = document.createElement("div"); + document.body.appendChild(container); + closeDialogResponse = null; + }); + + afterEach(() => { + // cleanup on exiting + unmountComponentAtNode(container); + container.remove(); + container = null; + }); + + it("render default buttons", () => { + renderComponent(); + + expect(confirmYesButton().textContent).toStrictEqual("YES"); + expect(confirmNoButton().textContent).toStrictEqual("NO"); + }); + + it("render custom buttons", () => { + act(() => { + render(, container); + }); + + expect(confirmYesButton().textContent).toStrictEqual("test yes"); + expect(confirmNoButton().textContent).toStrictEqual("test no"); + }); + + it("cancel", () => { + renderComponent(); + + click(confirmNoButton()); + + expect(closeDialogResponse).toStrictEqual(false); + }); + + it("cancel click background", () => { + renderComponent(); + + click(backdrop()); + + expect(closeDialogResponse).toStrictEqual(false); + }); + + it("confirm", () => { + renderComponent(); + + click(confirmYesButton()); + + expect(closeDialogResponse).toStrictEqual(true); + }); + + const confirmYesButton = () => document.querySelector(".dialog #yes-button"); + const confirmNoButton = () => document.querySelector(".dialog #no-button"); + const backdrop = () => + document.querySelector("#dialog-container div:first-child"); + + function click(element) { + act(() => { + element.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + } + + function renderComponent() { + act(() => { + render(, container); + }); + } +}); diff --git a/surveyclient/src/components/GetStartedScreen.jsx b/surveyclient/src/components/GetStartedScreen.jsx index 3fd5f10..fd5e432 100644 --- a/surveyclient/src/components/GetStartedScreen.jsx +++ b/surveyclient/src/components/GetStartedScreen.jsx @@ -1,6 +1,6 @@ import React from "react"; import { useDispatch, useSelector } from "react-redux"; -import { CONFIRM_WELCOME } from "../model/ActionTypes.js"; +import { CONFIRM_WELCOME } from "../model/ActionTypes"; export default function GetStartedScreen({ downloadButton }) { const dispatch = useDispatch(); diff --git a/surveyclient/src/components/QuestionCommentDialog.jsx b/surveyclient/src/components/QuestionCommentDialog.jsx index 7a077f2..71c7935 100644 --- a/surveyclient/src/components/QuestionCommentDialog.jsx +++ b/surveyclient/src/components/QuestionCommentDialog.jsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import "../App.css"; import { useDispatch, useSelector } from "react-redux"; -import { SET_ANSWER } from "../model/ActionTypes.js"; +import { SET_ANSWER } from "../model/ActionTypes"; import Modal from "@material-ui/core/Modal"; export default function QuestionCommentDialog({ @@ -43,7 +43,7 @@ export default function QuestionCommentDialog({ justifyContent: "center", }} > -
+

Add Note

{questionNumber}
diff --git a/surveyclient/src/components/QuestionPhotosDialog.jsx b/surveyclient/src/components/QuestionPhotosDialog.jsx index 714ba20..7cce890 100644 --- a/surveyclient/src/components/QuestionPhotosDialog.jsx +++ b/surveyclient/src/components/QuestionPhotosDialog.jsx @@ -47,7 +47,7 @@ export default function QuestionPhotosDialog({ justifyContent: "center", }} > -
+

Add Photos

{questionNumber}
diff --git a/surveyclient/src/components/QuestionSelectWithComment.jsx b/surveyclient/src/components/QuestionSelectWithComment.jsx index 5a706d7..d08b480 100644 --- a/surveyclient/src/components/QuestionSelectWithComment.jsx +++ b/surveyclient/src/components/QuestionSelectWithComment.jsx @@ -1,7 +1,7 @@ import React from "react"; import "../App.css"; import { useDispatch, useSelector } from "react-redux"; -import { SET_ANSWER } from "../model/ActionTypes.js"; +import { SET_ANSWER } from "../model/ActionTypes"; import QuestionAddCommentButton from "./QuestionAddCommentButton"; import QuestionAddPhotosButton from "./QuestionAddPhotosButton"; diff --git a/surveyclient/src/components/QuestionText.jsx b/surveyclient/src/components/QuestionText.jsx index bd8e0da..c7f7080 100644 --- a/surveyclient/src/components/QuestionText.jsx +++ b/surveyclient/src/components/QuestionText.jsx @@ -1,7 +1,7 @@ import React from "react"; import "../App.css"; import { useDispatch, useSelector } from "react-redux"; -import { SET_ANSWER } from "../model/ActionTypes.js"; +import { SET_ANSWER } from "../model/ActionTypes"; import QuestionAddPhotosButton from "./QuestionAddPhotosButton"; function QuestionText({ diff --git a/surveyclient/src/components/QuestionTextWithYear.jsx b/surveyclient/src/components/QuestionTextWithYear.jsx index bcd9a27..367b31c 100644 --- a/surveyclient/src/components/QuestionTextWithYear.jsx +++ b/surveyclient/src/components/QuestionTextWithYear.jsx @@ -1,7 +1,7 @@ import React from "react"; import "../App.css"; import { useDispatch, useSelector } from "react-redux"; -import { SET_ANSWER } from "../model/ActionTypes.js"; +import { SET_ANSWER } from "../model/ActionTypes"; import QuestionAddPhotosButton from "./QuestionAddPhotosButton"; function QuestionTextWithYear({ sectionId, question, questionNumber }) { diff --git a/surveyclient/src/components/QuestionUserSelect.jsx b/surveyclient/src/components/QuestionUserSelect.jsx index 2c26e6a..0e73e03 100644 --- a/surveyclient/src/components/QuestionUserSelect.jsx +++ b/surveyclient/src/components/QuestionUserSelect.jsx @@ -1,7 +1,7 @@ import React from "react"; import "../App.css"; import { useDispatch, useSelector } from "react-redux"; -import { SET_ANSWER } from "../model/ActionTypes.js"; +import { SET_ANSWER } from "../model/ActionTypes"; function QuestionUserSelect({ sectionId, question, }) { const questionId = question.id; diff --git a/surveyclient/src/components/RestartButton.jsx b/surveyclient/src/components/RestartButton.jsx new file mode 100644 index 0000000..eb22a44 --- /dev/null +++ b/surveyclient/src/components/RestartButton.jsx @@ -0,0 +1,38 @@ +import React, { useState } from "react"; +import { useDispatch } from "react-redux"; +import { signOut } from "../model/AuthActions"; +import ConfirmDialog from "./ConfirmDialog"; +import "../App.css"; +import { RESTART_SURVEY } from "../model/ActionTypes"; + +export default function RestartButton({ returnToStart }) { + const [showConfirmDialog, setShowConfirmDialog] = useState(false); + + const dispatch = useDispatch(); + + return ( + <> + + {showConfirmDialog && ( + { + closeConfirmed && dispatch({ type: RESTART_SURVEY }); + closeConfirmed && returnToStart(); + setShowConfirmDialog(false); + }} + > +

Are you sure you want to Restart your survey?

+

This will delete all your survey answers.

+
+ )} + + ); +} diff --git a/surveyclient/src/components/RestartButton.test.js b/surveyclient/src/components/RestartButton.test.js new file mode 100644 index 0000000..3b8f8d7 --- /dev/null +++ b/surveyclient/src/components/RestartButton.test.js @@ -0,0 +1,108 @@ +import React from "react"; +import { render, unmountComponentAtNode } from "react-dom"; +import { act } from "react-dom/test-utils"; +import RestartButton from "./RestartButton"; +import surveyStore from "../model/SurveyModel"; +import { Provider } from "react-redux"; +import { RESTART_SURVEY, REFRESH_STATE } from "../model/ActionTypes"; +import { RESULTS } from "./FixedSectionTypes"; +import { INPUT_STATE, EMPTY_STATE } from "../model/TestUtils"; + +const returnToStart = jest.fn(); + +describe("component RestartButton", () => { + var container = null; + beforeEach(() => { + // setup a DOM element as a render target + container = document.createElement("div"); + document.body.appendChild(container); + surveyStore.dispatch({ type: REFRESH_STATE, state: INPUT_STATE }); + + returnToStart.mockReset(); + }); + + afterEach(() => { + // cleanup on exiting + unmountComponentAtNode(container); + container.remove(); + container = null; + }); + + it("confirm dialog appears", () => { + renderComponent(); + expect(confirmYesButton()).toBeNull(); + + click(restartButton()); + expect(confirmYesButton()).not.toBeNull(); + }); + + it("restart cancel", () => { + renderComponent(); + click(restartButton()); + renderComponent(); + + click(confirmNoButton()); + renderComponent(); + + expect(confirmYesButton()).toBeNull(); + expect(surveyStore.getState()).toStrictEqual(INPUT_STATE); + expect(returnToStart).not.toHaveBeenCalled(); + }); + + it("restart cancel click background", () => { + renderComponent(); + click(restartButton()); + renderComponent(); + + click(backdrop()); + renderComponent(); + + expect(confirmYesButton()).toBeNull(); + expect(surveyStore.getState()).toStrictEqual(INPUT_STATE); + expect(returnToStart).not.toHaveBeenCalled(); + }); + + it("restart confirm", () => { + renderComponent(); + click(restartButton()); + renderComponent(); + + click(confirmYesButton()); + renderComponent(); + + expect(confirmYesButton()).toBeNull(); + expect(surveyStore.getState()).toStrictEqual({ + ...INPUT_STATE, + answers: EMPTY_STATE.answers, + answerCounts: EMPTY_STATE.answerCounts, + initialisingState: false, + photoDetails: {}, + photos: {}, + }); + expect(returnToStart).toHaveBeenCalledTimes(1); + }); + + const restartButton = () => container.querySelector("#restart-button"); + + const confirmYesButton = () => document.querySelector(".dialog #yes-button"); + const confirmNoButton = () => document.querySelector(".dialog #no-button"); + const backdrop = () => + document.querySelector("#dialog-container div:first-child"); + + function click(element) { + act(() => { + element.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + } + + function renderComponent() { + act(() => { + render( + + + , + container + ); + }); + } +}); diff --git a/surveyclient/src/components/SubmitSection.jsx b/surveyclient/src/components/SubmitSection.jsx index 9022689..465dca9 100644 --- a/surveyclient/src/components/SubmitSection.jsx +++ b/surveyclient/src/components/SubmitSection.jsx @@ -1,16 +1,22 @@ import React, { useState } from "react"; import "../App.css"; -import { useSelector } from "react-redux"; +import { useSelector, useDispatch } from "react-redux"; import { SUBMIT } from "./FixedSectionTypes"; import SectionBottomNavigation from "./SectionBottomNavigation"; import { SIGNED_IN } from "../model/AuthStates"; +import { signOut } from "../model/AuthActions"; import Modal from "@material-ui/core/Modal"; import { uploadResults } from "../model/SubmitAction"; +import { RESET_STATE } from "../model/ActionTypes"; + import { SUBMIT_COMPLETE, SUBMIT_FAILED } from "../model/SubmitStates"; +import ConfirmDialog from "./ConfirmDialog"; function SubmitSection({ sections, setCurrentSection, endpoint }) { + const dispatch = useDispatch(); const state = useSelector((state) => state); const authState = useSelector((state) => state.authentication.state); + const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [submitState, setSubmitState] = useState(null); const [progressValue, setProgressValue] = useState(0); @@ -19,6 +25,14 @@ function SubmitSection({ sections, setCurrentSection, endpoint }) { uploadResults(setSubmitState, setProgressValue, state, endpoint); } + function handleCloseDialog() { + if (submitState === SUBMIT_COMPLETE) { + dispatch({ type: RESET_STATE }); + dispatch(signOut()); + } + setSubmitState(null); + } + return (

Upload survey response

@@ -27,20 +41,37 @@ function SubmitSection({ sections, setCurrentSection, endpoint }) { Landscapes.

- You still make changes to your survey answers or add anything you've - missed after uploading, as long as you upload the survey again after you - have finished making your changes. + You will not be able to access or make changes to your survey responses + once your survey has been uploaded.

{authState !== SIGNED_IN ? (

Login before submitting survey.

) : ( - + <> + + {showConfirmDialog && ( + { + closeConfirmed && handleUploadResults(); + setShowConfirmDialog(false); + }} + > +

Are you sure you want to Upload your survey?

+

+ You will not be able to access or make changes to your survey + once it has been uploaded. +

+
+ )} + )}
-
+

Uploading Survey Response

{submitState !== SUBMIT_COMPLETE && submitState !== SUBMIT_FAILED &&

Please wait...

} @@ -81,10 +112,10 @@ function SubmitSection({ sections, setCurrentSection, endpoint }) { submitState === SUBMIT_FAILED) && ( )}
diff --git a/surveyclient/src/components/SubmitSection.test.js b/surveyclient/src/components/SubmitSection.test.js index c352c2b..167e26a 100644 --- a/surveyclient/src/components/SubmitSection.test.js +++ b/surveyclient/src/components/SubmitSection.test.js @@ -10,7 +10,7 @@ import { Provider } from "react-redux"; import { REFRESH_STATE, SET_AUTH_STATE } from "../model/ActionTypes"; import { SUBMIT } from "./FixedSectionTypes"; import { SIGNED_IN, SIGNED_OUT } from "../model/AuthStates"; -import { INPUT_STATE } from "../model/TestUtils"; +import { INPUT_STATE, EMPTY_STATE } from "../model/TestUtils"; import { SUBMITTING_START, SUBMITTING_PHOTOS, @@ -18,9 +18,11 @@ import { SUBMIT_COMPLETE, SUBMIT_FAILED, } from "../model/SubmitStates"; +import { signOut } from "../model/AuthActions"; const TEST_ENDPOINT = "http://localhost:9999/testEndpoint"; +jest.mock("../model/AuthActions"); jest.mock("../model/SubmitAction"); jest.mock("@aws-amplify/auth"); @@ -42,6 +44,9 @@ describe("component SubmitSection", () => { surveyStore.dispatch({ type: REFRESH_STATE, state: INPUT_STATE }); uploadResults.mockReset(); + + signOut.mockReset(); + signOut.mockImplementation(() => () => "dummy action"); }); afterEach(() => { @@ -76,14 +81,49 @@ describe("component SubmitSection", () => { storedSectionId = null; renderComponent(); - clickPreviousButton(); + click(previousButton()); expect(storedSectionId).toStrictEqual("section2"); }); + it("confirm dialog appears", () => { + renderComponent(); + expect(confirmYesButton()).toBeNull(); + + click(uploadButton()); + expect(confirmYesButton()).not.toBeNull(); + }); + + it("confirm upload cancel", () => { + renderComponent(); + click(uploadButton()); + renderComponent(); + + click(confirmNoButton()); + renderComponent(); + + expect(confirmYesButton()).toBeNull(); + expect(uploadResults).not.toHaveBeenCalled(); + }); + + it("confirm upload cancel click background", () => { + renderComponent(); + click(uploadButton()); + renderComponent(); + + click(backdrop()); + renderComponent(); + + expect(confirmYesButton()).toBeNull(); + expect(uploadResults).not.toHaveBeenCalled(); + }); + it("upload success and close", () => { renderComponent(); expect(uploadResults).toHaveBeenCalledTimes(0); - clickUploadButton(); + click(uploadButton()); + renderComponent(); + click(confirmYesButton()); + renderComponent(); const { setSubmitState, setProgressValue } = checkUploadAndGetCallbacks( INPUT_STATE, @@ -97,16 +137,23 @@ describe("component SubmitSection", () => { checkDialogComplete(); - clickCloseButton(); + click(closeButton()); renderComponent(); expect(dialog()).toBeNull(); + expect(surveyStore.getState()).toStrictEqual({ + ...EMPTY_STATE, + initialisingState: false, + }); }); it("upload failure and close", () => { renderComponent(); expect(uploadResults).toHaveBeenCalledTimes(0); - clickUploadButton(); + click(uploadButton()); + renderComponent(); + click(confirmYesButton()); + renderComponent(); const { setSubmitState, setProgressValue } = checkUploadAndGetCallbacks( INPUT_STATE, @@ -120,16 +167,20 @@ describe("component SubmitSection", () => { checkDialogFailed("50%"); - clickCloseButton(); + click(closeButton()); renderComponent(); expect(dialog()).toBeNull(); + expect(surveyStore.getState()).toStrictEqual(INPUT_STATE); }); it("progress changes during upload", () => { renderComponent(); expect(uploadResults).toHaveBeenCalledTimes(0); - clickUploadButton(); + click(uploadButton()); + renderComponent(); + click(confirmYesButton()); + renderComponent(); const { setSubmitState, setProgressValue } = checkUploadAndGetCallbacks( INPUT_STATE, @@ -215,7 +266,8 @@ describe("component SubmitSection", () => { expect(closeButton()).not.toBeNull(); } - const sectionContent = () => container.querySelector(".section .submit-content"); + const sectionContent = () => + container.querySelector(".section .submit-content"); const previousButton = () => container.querySelector(".section .previous-section-button"); const uploadButton = () => @@ -227,20 +279,18 @@ describe("component SubmitSection", () => { const closeButton = () => document.querySelector(".dialog .close-button"); const progressBar = () => document.querySelector(".dialog .progress-bar-active"); + const confirmYesButton = () => document.querySelector(".dialog #yes-button"); + const confirmNoButton = () => document.querySelector(".dialog #no-button"); + const backdrop = () => + document.querySelector("#dialog-container div:first-child"); function progressBarValue() { const styleValue = progressBar().getAttribute("style"); return styleValue.substring(7, styleValue.length - 1); } - function clickCloseButton() { - closeButton().dispatchEvent(new MouseEvent("click", { bubbles: true })); - } - function clickPreviousButton() { - previousButton().dispatchEvent(new MouseEvent("click", { bubbles: true })); - } - function clickUploadButton() { - uploadButton().dispatchEvent(new MouseEvent("click", { bubbles: true })); + function click(element) { + element.dispatchEvent(new MouseEvent("click", { bubbles: true })); } function renderComponent() { diff --git a/surveyclient/src/components/auth/AuthSignInOut.jsx b/surveyclient/src/components/auth/AuthSignOut.jsx similarity index 100% rename from surveyclient/src/components/auth/AuthSignInOut.jsx rename to surveyclient/src/components/auth/AuthSignOut.jsx diff --git a/surveyclient/src/components/auth/AuthSignInOut.test.js b/surveyclient/src/components/auth/AuthSignOut.test.js similarity index 100% rename from surveyclient/src/components/auth/AuthSignInOut.test.js rename to surveyclient/src/components/auth/AuthSignOut.test.js diff --git a/surveyclient/src/model/ActionTypes.js b/surveyclient/src/model/ActionTypes.js index a0ee7fb..6f6a335 100644 --- a/surveyclient/src/model/ActionTypes.js +++ b/surveyclient/src/model/ActionTypes.js @@ -1,5 +1,6 @@ export const SET_ANSWER = "setAnswer"; export const REFRESH_STATE = "refreshState"; +export const RESTART_SURVEY = "restartSurvey"; export const ADD_PHOTO = "addPhoto"; export const DELETE_PHOTO = "deletePhoto"; export const UPDATE_PHOTO_DESCRIPTION = "updatePhotoDescription"; diff --git a/surveyclient/src/model/SurveyModel.js b/surveyclient/src/model/SurveyModel.js index 24e2291..86d7717 100644 --- a/surveyclient/src/model/SurveyModel.js +++ b/surveyclient/src/model/SurveyModel.js @@ -3,6 +3,7 @@ import thunk from "redux-thunk"; import { SET_ANSWER, REFRESH_STATE, + RESTART_SURVEY, RESET_STATE, ADD_PHOTO, DELETE_PHOTO, @@ -11,7 +12,7 @@ import { SET_AUTH_ERROR, CLEAR_AUTH_ERROR, CONFIRM_WELCOME, -} from "./ActionTypes.js"; +} from "./ActionTypes"; import { sectionsContent, SURVEY_VERSION, @@ -102,6 +103,20 @@ export function surveyReducer(state = initialState(), action) { writePhotos(newState); return newState; + case RESTART_SURVEY: + // console.log("RESTART_SURVEY"); + newState = { + ...state, + answers: createEmptyAnswers(), + answerCounts: createAnswerCounts(), + photos: {}, + photoDetails: {}, + initialisingState: false, + }; + writeAnswers(newState); + writePhotos(newState); + return newState; + case REFRESH_STATE: // console.log("REFRESH_STATE", action.state); return action.state; diff --git a/surveyclient/src/model/SurveyModel.test.js b/surveyclient/src/model/SurveyModel.test.js index 6b5fd03..4dd5380 100644 --- a/surveyclient/src/model/SurveyModel.test.js +++ b/surveyclient/src/model/SurveyModel.test.js @@ -1,9 +1,10 @@ -import { surveyReducer, refreshState, loadPhoto } from "./SurveyModel.js"; +import { surveyReducer, refreshState, loadPhoto } from "./SurveyModel"; import configureStore from "redux-mock-store"; import thunk from "redux-thunk"; import { SET_ANSWER, REFRESH_STATE, + RESTART_SURVEY, RESET_STATE, ADD_PHOTO, DELETE_PHOTO, @@ -12,7 +13,7 @@ import { SET_AUTH_ERROR, CLEAR_AUTH_ERROR, CONFIRM_WELCOME, -} from "./ActionTypes.js"; +} from "./ActionTypes"; import { SIGNED_IN, SIGN_IN } from "./AuthStates"; import rfdc from "rfdc"; // local forage is mocked in setupTests.js @@ -51,6 +52,21 @@ describe("surveyReducer", () => { ).toStrictEqual({ ...EMPTY_STATE, initialisingState: false }); }); + it("action RESTART_SURVEY", () => { + expect( + surveyReducer(INPUT_STATE, { + type: RESTART_SURVEY, + }) + ).toStrictEqual({ + ...INPUT_STATE, + answers: EMPTY_STATE.answers, + answerCounts: EMPTY_STATE.answerCounts, + initialisingState: false, + photoDetails: {}, + photos: {}, + }); + }); + it("action REFRESH_STATE", () => { expect( surveyReducer(EMPTY_STATE, { From b0ac75658439270e2c3d2c22064a6d2ec6574e6c Mon Sep 17 00:00:00 2001 From: Donal Stewart Date: Tue, 12 Jan 2021 17:38:15 +0000 Subject: [PATCH 5/5] Added confirm dialogs and restart button --- .../src/components/auth/AuthSignOut.jsx | 82 ++++--------------- .../src/components/auth/AuthSignOut.test.js | 71 ++++++++-------- 2 files changed, 53 insertions(+), 100 deletions(-) diff --git a/surveyclient/src/components/auth/AuthSignOut.jsx b/surveyclient/src/components/auth/AuthSignOut.jsx index b3771d1..0fe6a34 100644 --- a/surveyclient/src/components/auth/AuthSignOut.jsx +++ b/surveyclient/src/components/auth/AuthSignOut.jsx @@ -1,84 +1,38 @@ -import React, { useState, useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { SIGN_IN, SIGNED_OUT, SIGNED_IN } from "../../model/AuthStates"; -import { setAuthState, signOut } from "../../model/AuthActions"; +import React, { useState } from "react"; +import { useDispatch } from "react-redux"; +import { signOut } from "../../model/AuthActions"; +import ConfirmDialog from "../ConfirmDialog"; import "../../App.css"; -import Modal from "@material-ui/core/Modal"; -function ConfirmDialog({ closeDialog }) { - return ( - window.document.body : undefined} - keepMounted={false} - open={true} - onClose={() => closeDialog(false)} - style={{ - display: "flex", - alignItems: "center", - justifyContent: "center", - }} - > -
-

Log out?

-

Are you sure you want to log out?

- - -
-
- ); -} - -export default function AuthSignInOut() { - const [signedIn, setSignedIn] = useState(false); +export default function AuthSignOut() { const [showConfirmDialog, setShowConfirmDialog] = useState(false); const dispatch = useDispatch(); - const authState = useSelector((state) => state.authentication.state); - - useEffect(() => { - if (authState === SIGNED_IN) { - setSignedIn(true); - } else if (authState === SIGNED_OUT) { - setSignedIn(false); - } - }, [authState]); - - function handleClick(event) { - signedIn ? setShowConfirmDialog(true) : dispatch(setAuthState(SIGN_IN)); - } return ( <> {showConfirmDialog && ( { closeConfirmed && dispatch(signOut()); setShowConfirmDialog(false); }} - /> + > +

Are you sure you want to Log Out?

+

+ You will not be able to continue your survey and you will need + access to an internet connection to Login again. +

+
)} ); diff --git a/surveyclient/src/components/auth/AuthSignOut.test.js b/surveyclient/src/components/auth/AuthSignOut.test.js index b341991..225125a 100644 --- a/surveyclient/src/components/auth/AuthSignOut.test.js +++ b/surveyclient/src/components/auth/AuthSignOut.test.js @@ -1,21 +1,14 @@ import React from "react"; import { render, unmountComponentAtNode } from "react-dom"; import { act } from "react-dom/test-utils"; -import AuthSignInOut from "./AuthSignInOut"; +import AuthSignOut from "./AuthSignOut"; import surveyStore from "../../model/SurveyModel"; import { Provider } from "react-redux"; -import { SET_AUTH_STATE } from "../../model/ActionTypes"; -import { - SIGNED_OUT, - SIGNED_IN, - SIGN_IN, - REGISTER, -} from "../../model/AuthStates"; -import { signOut, setAuthState } from "../../model/AuthActions"; +import { signOut } from "../../model/AuthActions"; jest.mock("../../model/AuthActions"); -describe("component AuthSignInOut", () => { +describe("component AuthSignOut", () => { var container = null; beforeEach(() => { // setup a DOM element as a render target @@ -24,8 +17,6 @@ describe("component AuthSignInOut", () => { signOut.mockReset(); signOut.mockImplementation(() => () => "dummy action"); - setAuthState.mockReset(); - setAuthState.mockImplementation(() => () => "dummy action"); }); afterEach(() => { @@ -35,48 +26,56 @@ describe("component AuthSignInOut", () => { container = null; }); - it("signed in", () => { - surveyStore.dispatch({ type: SET_AUTH_STATE, authState: SIGNED_IN }); + it("confirm dialog appears", () => { renderComponent(); + expect(confirmYesButton()).toBeNull(); - expect(signInOutButton().textContent).toStrictEqual("LOG OUT"); - click(signInOutButton()); - - expect(signOut).toHaveBeenCalledTimes(1); - expect(setAuthState).not.toHaveBeenCalledTimes(1); + click(signOutButton()); + expect(confirmYesButton()).not.toBeNull(); }); - it("signed out", () => { - surveyStore.dispatch({ type: SET_AUTH_STATE, authState: SIGNED_OUT }); + it("signed out cancel", () => { + renderComponent(); + click(signOutButton()); renderComponent(); - expect(signInOutButton().textContent).toStrictEqual("LOG IN"); - click(signInOutButton()); + click(confirmNoButton()); + renderComponent(); + expect(confirmYesButton()).toBeNull(); expect(signOut).not.toHaveBeenCalled(); - expect(setAuthState).toHaveBeenCalledTimes(1); - expect(setAuthState).toHaveBeenCalledWith(SIGN_IN); }); - it("auth state changes", () => { - surveyStore.dispatch({ type: SET_AUTH_STATE, authState: SIGNED_IN }); + it("signed out cancel click background", () => { + renderComponent(); + click(signOutButton()); renderComponent(); - expect(signInOutButton().textContent).toStrictEqual("LOG OUT"); - surveyStore.dispatch({ type: SET_AUTH_STATE, authState: REGISTER }); + click(backdrop()); renderComponent(); - expect(signInOutButton().textContent).toStrictEqual("LOG OUT"); - surveyStore.dispatch({ type: SET_AUTH_STATE, authState: SIGNED_OUT }); + expect(confirmYesButton()).toBeNull(); + expect(signOut).not.toHaveBeenCalled(); + }); + + it("signed out confirm", () => { + renderComponent(); + click(signOutButton()); renderComponent(); - expect(signInOutButton().textContent).toStrictEqual("LOG IN"); - surveyStore.dispatch({ type: SET_AUTH_STATE, authState: REGISTER }); + click(confirmYesButton()); renderComponent(); - expect(signInOutButton().textContent).toStrictEqual("LOG IN"); + + expect(confirmYesButton()).toBeNull(); + expect(signOut).toHaveBeenCalledTimes(1); }); - const signInOutButton = () => container.querySelector("#auth-signin-signout"); + const signOutButton = () => container.querySelector("#auth-signout"); + + const confirmYesButton = () => document.querySelector(".dialog #yes-button"); + const confirmNoButton = () => document.querySelector(".dialog #no-button"); + const backdrop = () => + document.querySelector("#dialog-container div:first-child"); function click(element) { act(() => { @@ -88,7 +87,7 @@ describe("component AuthSignInOut", () => { act(() => { render( - + , container );