From 0016e12acdef470cc58f653702f3414d178b7578 Mon Sep 17 00:00:00 2001 From: Christoph Lienhard Date: Wed, 30 Dec 2020 22:46:26 +0100 Subject: [PATCH] #11 Refactor: Disentangle QuestionList --- .../src/backend/queries/question.mock.ts | 37 ++++- .../src/backend/queries/question.ts | 17 ++ .../src/components/DialogChangeCategory.tsx | 2 +- .../src/components/DialogChangeQuestion.tsx | 153 +++++++++++++----- .../src/components/QuestionList.tsx | 142 ++-------------- .../question-list.integration.test.tsx | 4 +- 6 files changed, 185 insertions(+), 170 deletions(-) diff --git a/redaktions-app/src/backend/queries/question.mock.ts b/redaktions-app/src/backend/queries/question.mock.ts index ee656d7..672d248 100644 --- a/redaktions-app/src/backend/queries/question.mock.ts +++ b/redaktions-app/src/backend/queries/question.mock.ts @@ -1,5 +1,11 @@ import {MockedResponse} from "@apollo/client/testing"; -import {BasicQuestionResponse, GET_ALL_QUESTIONS, GetAllQuestionsResponse} from "./question"; +import { + BasicQuestionResponse, + GET_ALL_QUESTIONS, + GET_QUESTION_BY_ID, + GetAllQuestionsResponse, + GetQuestionByIdResponse +} from "./question"; export const questionNodesMock: Array = [{ @@ -46,3 +52,32 @@ export const getAllQuestionsMock: Array> }, ] +export const getQuestionByIdMock: Array> = [ + ...questionNodesMock.map(q => ({ + request: { + query: GET_QUESTION_BY_ID, + variables: { + id: q.id, + }, + }, + result: { + data: { + question: q, + }, + }, + })), + { + request: { + query: GET_QUESTION_BY_ID, + variables: { + id: "", + }, + }, + result: { + data: { + question: null, + }, + }, + } +] + diff --git a/redaktions-app/src/backend/queries/question.ts b/redaktions-app/src/backend/queries/question.ts index f9e842e..5026870 100644 --- a/redaktions-app/src/backend/queries/question.ts +++ b/redaktions-app/src/backend/queries/question.ts @@ -52,3 +52,20 @@ export interface GetAllQuestionsResponse { __typename: "QuestionsConnection", } } + +export const GET_QUESTION_BY_ID = gql` + query GetQuestionById($id:ID!) { + question(id: $id) { + ...BasicQuestionFragment + } + } + ${BasicQuestionFragment} +` + +export interface GetQuestionByIdResponse { + question: BasicQuestionResponse | null, +} + +export interface GetQuestionByIdVariables { + id: string, +} diff --git a/redaktions-app/src/components/DialogChangeCategory.tsx b/redaktions-app/src/components/DialogChangeCategory.tsx index 26f10a8..467fc02 100644 --- a/redaktions-app/src/components/DialogChangeCategory.tsx +++ b/redaktions-app/src/components/DialogChangeCategory.tsx @@ -42,7 +42,7 @@ export default function DialogChangeCategory() { setTitle(data.category?.title || ""); setDetails(data.category?.description || "") }) - }) + }); const [editCategory, {loading: editLoading}] = useMutation(EDIT_CATEGORY, { onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}), onCompleted: (response) => { diff --git a/redaktions-app/src/components/DialogChangeQuestion.tsx b/redaktions-app/src/components/DialogChangeQuestion.tsx index 1a2c642..a60934c 100644 --- a/redaktions-app/src/components/DialogChangeQuestion.tsx +++ b/redaktions-app/src/components/DialogChangeQuestion.tsx @@ -1,61 +1,134 @@ -import React from 'react'; +import React, {useState} from 'react'; import Dialog from '@material-ui/core/Dialog'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; -import {BasicCategoryResponse} from "../backend/queries/category"; -import CategorySelectionMenu from "./CategorySelectionMenu"; import {DialogActionBar} from "./DialogActionBar"; import {DialogTitleAndDetails} from "./DialogTitleAndDetails"; +import {makeVar, useMutation, useQuery, useReactiveVar} from "@apollo/client"; +import {useSnackbar} from "notistack"; +import { + ADD_QUESTION, + AddQuestionResponse, + AddQuestionVariables, + EDIT_QUESTION, + EditQuestionResponse, + EditQuestionVariables +} from "../backend/mutations/question"; +import { + BasicQuestionFragment, + BasicQuestionResponse, + GET_QUESTION_BY_ID, + GetQuestionByIdResponse, + GetQuestionByIdVariables +} from "../backend/queries/question"; +import CategorySelectionMenu from "./CategorySelectionMenu"; +import {GET_ALL_CATEGORIES, GetAllCategoriesResponse} from "../backend/queries/category"; +export const dialogChangeQuestionId = makeVar(""); +export const dialogChangeQuestionOpen = makeVar(false); -export interface ChangeQuestionDialogContent { - id: string - title: string, - details: string | null, - categoryId: number | null, -} +export default function DialogChangeQuestion() { + const [addMode, setAddMode] = useState(true); + const [title, setTitle] = useState(""); + const [details, setDetails] = useState(""); + const [categoryRowId, setCategoryRowId] = useState(null); + const questionId = useReactiveVar(dialogChangeQuestionId); + const open = useReactiveVar(dialogChangeQuestionOpen); + const {enqueueSnackbar} = useSnackbar(); + useQuery(GET_QUESTION_BY_ID, { + variables: { + id: questionId, + }, + onCompleted: (data => { + setAddMode(!data.question && !questionId) + setTitle(data.question?.title || ""); + setDetails(data.question?.description || ""); + setCategoryRowId(data.question?.categoryByCategoryRowId?.rowId || null) + }) + }) + const categories = useQuery(GET_ALL_CATEGORIES).data?.allCategories.nodes; -interface DialogChangeQuestionProps { - title: string, - confirmButtonText: string, - open: boolean, - content: ChangeQuestionDialogContent, - loading: boolean, - categories?: Array, + const [editQuestion, {loading: editLoading}] = useMutation(EDIT_QUESTION, { + onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}), + onCompleted: (response) => { + if (response.updateQuestion) { + enqueueSnackbar("Frage erfolgreich geändert.", {variant: "success"}) + dialogChangeQuestionOpen(false); + } else { + enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"}) + } + } + }); + const [addQuestion, {loading: addLoading}] = useMutation(ADD_QUESTION, { + onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}), + onCompleted: (response) => { + if (response.createQuestion) { + enqueueSnackbar("Frage erfolgreich hinzugefügt.", {variant: "success"}) + dialogChangeQuestionOpen(false); + } else { + enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"}) + } + }, + update: (cache, {data}) => { + cache.modify({ + fields: { + allQuestions(existingQuestions = {nodes: []}) { + const newQuestionRef = cache.writeFragment({ + data: data?.createQuestion?.question, + fragment: BasicQuestionFragment, + fragmentName: "BasicQuestionFragment", + }); + return {nodes: [...existingQuestions.nodes, newQuestionRef]}; + } + } + }); + } + }); - handleContentChange(content: ChangeQuestionDialogContent): void + const handleConfirmButtonClick = () => { + if (addMode) { + addQuestion({ + variables: { + title, + description: details, + categoryRowId: categoryRowId, + } + }) + } else { + editQuestion({ + variables: { + id: questionId, + title: title, + description: details, + categoryRowId: categoryRowId, + } + }) + } + } - handleConfirmButtonClick(): void, - - handleClose(): void, -} - - -export default function DialogChangeQuestion(props: DialogChangeQuestionProps) { return ( - - {props.title} + dialogChangeQuestionOpen(false)} aria-labelledby="form-dialog-title"> + + {addMode ? "Neue Frage erstellen" : "Frage bearbeiten"} + props.handleContentChange({...props.content, title: newTitle})} - onDetailsChange={newDetails => props.handleContentChange({...props.content, details: newDetails})} + title={title} + details={details} + onTitleChange={newTitle => setTitle(newTitle)} + onDetailsChange={newDetails => setDetails(newDetails)} /> props.handleContentChange({ - ...props.content, - categoryId: categoryId, - })} + selectedCategoryId={categoryRowId} + categories={categories} + handleCategoryChange={(categoryId) => setCategoryRowId(categoryId)} /> dialogChangeQuestionOpen(false)} + onConfirmButtonClick={handleConfirmButtonClick} + confirmButtonText={addMode ? "Erstellen" : "Speichern"} + loading={addMode ? addLoading : editLoading} /> ); diff --git a/redaktions-app/src/components/QuestionList.tsx b/redaktions-app/src/components/QuestionList.tsx index 457b716..69cdb90 100644 --- a/redaktions-app/src/components/QuestionList.tsx +++ b/redaktions-app/src/components/QuestionList.tsx @@ -1,27 +1,16 @@ import {Paper, Typography} from "@material-ui/core"; -import React, {useState} from "react"; +import React from "react"; import {makeStyles} from "@material-ui/core/styles"; -import {useMutation, useQuery} from "@apollo/client"; +import {useQuery} from "@apollo/client"; import AddCard from "./AddCard"; import AccordionWithEdit from "./AccordionWithEdit"; -import { - BasicQuestionFragment, - BasicQuestionResponse, - GET_ALL_QUESTIONS, - GetAllQuestionsResponse -} from "../backend/queries/question"; -import DialogChangeQuestion, {ChangeQuestionDialogContent} from "./DialogChangeQuestion"; -import {GET_ALL_CATEGORIES, GetAllCategoriesResponse} from "../backend/queries/category"; -import { - ADD_QUESTION, - AddQuestionResponse, - AddQuestionVariables, - EDIT_QUESTION, - EditQuestionResponse, - EditQuestionVariables -} from "../backend/mutations/question"; -import {useSnackbar} from 'notistack'; -import DialogDeleteQuestion, {dialogDeleteQuestionId, dialogDeleteQuestionOpen, dialogDeleteQuestionTitle} from "./DialogDeleteQuestion"; +import {BasicQuestionResponse, GET_ALL_QUESTIONS, GetAllQuestionsResponse} from "../backend/queries/question"; +import DialogChangeQuestion, {dialogChangeQuestionId, dialogChangeQuestionOpen} from "./DialogChangeQuestion"; +import DialogDeleteQuestion, { + dialogDeleteQuestionId, + dialogDeleteQuestionOpen, + dialogDeleteQuestionTitle +} from "./DialogDeleteQuestion"; const useStyles = makeStyles((theme) => ({ root: { @@ -31,82 +20,18 @@ const useStyles = makeStyles((theme) => ({ }, })); -const emptyChangeQuestionDialog: ChangeQuestionDialogContent = { - id: "", - title: "", - details: "", - categoryId: null, -} - export default function QuestionList() { - const [changeDialogOpen, setChangeDialogOpen] = useState(false); - const [dialogTitle, setDialogTitle] = useState(""); - const [dialogConfirmButtonText, setDialogConfirmButtonText] = useState(""); - const [changeDialogContent, setChangeDialogContent] = useState(emptyChangeQuestionDialog); - const {enqueueSnackbar} = useSnackbar(); const questions = useQuery(GET_ALL_QUESTIONS).data?.allQuestions.nodes; - const categories = useQuery(GET_ALL_CATEGORIES).data?.allCategories.nodes; - const [editQuestion, {loading: editLoading}] = useMutation(EDIT_QUESTION, { - onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}), - onCompleted: (response) => { - if (response.updateQuestion) { - enqueueSnackbar("Frage erfolgreich geändert.", {variant: "success"}) - setChangeDialogOpen(false); - } else { - enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"}) - } - } - }); - const [addQuestion, {loading: addLoading}] = useMutation(ADD_QUESTION, { - onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}), - onCompleted: (response) => { - if (response.createQuestion) { - enqueueSnackbar("Frage erfolgreich hinzugefügt.", {variant: "success"}) - setChangeDialogOpen(false); - } else { - enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"}) - } - }, - update: (cache, {data}) => { - cache.modify({ - fields: { - allQuestions(existingQuestions = {nodes: []}) { - const newQuestionRef = cache.writeFragment({ - data: data?.createQuestion?.question, - fragment: BasicQuestionFragment, - fragmentName: "BasicQuestionFragment", - }); - return {nodes: [...existingQuestions.nodes, newQuestionRef]}; - } - } - }); - } - }); const classes = useStyles(); - const loading = editLoading || addLoading; - - const handleAddClick = () => { - setDialogTitle("Neue Frage erstellen"); - setDialogConfirmButtonText("Erstellen"); - if (changeDialogContent.id !== "") { - setChangeDialogContent(emptyChangeQuestionDialog); - } - setChangeDialogOpen(true); + const handleAddButtonClick = () => { + dialogChangeQuestionId("") + dialogChangeQuestionOpen(true) } const handleEditButtonClick = (question: BasicQuestionResponse) => { - setDialogTitle("Frage bearbeiten"); - setDialogConfirmButtonText("Speichern") - if (changeDialogContent.id !== question.id) { - setChangeDialogContent({ - id: question.id, - title: question.title, - details: question.description, - categoryId: question.categoryByCategoryRowId ? question.categoryByCategoryRowId.rowId : null, - }) - } - setChangeDialogOpen(true); + dialogChangeQuestionId(question.id) + dialogChangeQuestionOpen(true) }; const handleDeleteButtonClick = (question: BasicQuestionResponse) => { @@ -115,31 +40,6 @@ export default function QuestionList() { dialogDeleteQuestionOpen(true); } - const handleDialogContentChange = (content: ChangeQuestionDialogContent) => { - setChangeDialogContent(content) - } - - const handleChangeConfirmButtonClick = () => { - if (changeDialogContent.id !== "") { - editQuestion({ - variables: { - id: changeDialogContent.id, - title: changeDialogContent.title, - description: changeDialogContent.details, - categoryRowId: changeDialogContent.categoryId, - } - }) - } else { - addQuestion({ - variables: { - title: changeDialogContent.title, - description: changeDialogContent.details, - categoryRowId: changeDialogContent.categoryId, - } - }) - } - }; - return ( Fragen @@ -152,18 +52,8 @@ export default function QuestionList() { onDeleteButtonClick={() => handleDeleteButtonClick(question)} /> )} - - setChangeDialogOpen(false)} - /> + + ) diff --git a/redaktions-app/src/integration-tests/question-list.integration.test.tsx b/redaktions-app/src/integration-tests/question-list.integration.test.tsx index d96145d..c33d6bf 100644 --- a/redaktions-app/src/integration-tests/question-list.integration.test.tsx +++ b/redaktions-app/src/integration-tests/question-list.integration.test.tsx @@ -4,7 +4,7 @@ import {MockedProvider, MockedResponse} from '@apollo/client/testing'; import {MemoryRouter} from 'react-router-dom'; import QuestionList from "../components/QuestionList"; import {SnackbarProvider} from "notistack"; -import {getAllQuestionsMock, questionNodesMock} from "../backend/queries/question.mock"; +import {getAllQuestionsMock, getQuestionByIdMock, questionNodesMock} from "../backend/queries/question.mock"; import {getAllCategoriesMock} from "../backend/queries/category.mock"; import {addQuestionMock, deleteQuestionMock, editQuestionMock} from "../backend/mutations/question.mock"; import { @@ -125,7 +125,7 @@ describe('The QuestionList', () => { }); function renderQuestionList(additionalMocks?: Array) { - const initialMocks = [...getAllQuestionsMock, ...getAllCategoriesMock]; + const initialMocks = [...getAllQuestionsMock, ...getQuestionByIdMock, ...getAllCategoriesMock]; const allMocks = additionalMocks ? [...initialMocks, ...additionalMocks] : initialMocks return render(