From ff505226ab7c00b88a0e2f3809aa0cbf9fb9b706 Mon Sep 17 00:00:00 2001 From: Christoph Lienhard Date: Wed, 30 Dec 2020 22:13:48 +0100 Subject: [PATCH] #11 Refactor: Disentangle CategoryList --- .../src/backend/queries/category.mock.ts | 35 ++++- .../src/backend/queries/category.ts | 16 +++ .../src/components/CategoryList.tsx | 126 ++-------------- .../src/components/DialogChangeCategory.tsx | 134 ++++++++++++++---- .../src/components/DialogChangeQuestion.tsx | 4 +- .../src/components/DialogTitleAndDetails.tsx | 8 +- .../category-list.integration.test.tsx | 4 +- 7 files changed, 170 insertions(+), 157 deletions(-) diff --git a/redaktions-app/src/backend/queries/category.mock.ts b/redaktions-app/src/backend/queries/category.mock.ts index 4676687..1145b5d 100644 --- a/redaktions-app/src/backend/queries/category.mock.ts +++ b/redaktions-app/src/backend/queries/category.mock.ts @@ -1,5 +1,11 @@ import {MockedResponse} from "@apollo/client/testing"; -import {BasicCategoryResponse, GET_ALL_CATEGORIES, GetAllCategoriesResponse} from "./category"; +import { + BasicCategoryResponse, + GET_ALL_CATEGORIES, + GET_CATEGORY_BY_ID, + GetAllCategoriesResponse, + GetCategoryByIdResponse +} from "./category"; export const categoryNodesMock: Array = [ @@ -33,3 +39,30 @@ export const getAllCategoriesMock: Array> = [...categoryNodesMock.map(c => ({ + request: { + query: GET_CATEGORY_BY_ID, + variables: { + id: c.id, + }, + }, + result: { + data: { + category: c, + }, + }, +})), + { + request: { + query: GET_CATEGORY_BY_ID, + variables: { + id: "", + }, + }, + result: { + data: { + category: null, + }, + }, + } +] diff --git a/redaktions-app/src/backend/queries/category.ts b/redaktions-app/src/backend/queries/category.ts index 6c6dbfe..30757e0 100644 --- a/redaktions-app/src/backend/queries/category.ts +++ b/redaktions-app/src/backend/queries/category.ts @@ -35,3 +35,19 @@ export interface GetAllCategoriesResponse { } } +export const GET_CATEGORY_BY_ID = gql` + query GetCategoryById($id:ID!) { + category(id: $id) { + ...BasicCategoryFragment + } + } + ${BasicCategoryFragment} +` + +export interface GetCategoryByIdResponse { + category: BasicCategoryResponse | null, +} + +export interface GetCategoryByIdVariables { + id: string, +} diff --git a/redaktions-app/src/components/CategoryList.tsx b/redaktions-app/src/components/CategoryList.tsx index abd91e5..2689222 100644 --- a/redaktions-app/src/components/CategoryList.tsx +++ b/redaktions-app/src/components/CategoryList.tsx @@ -1,25 +1,11 @@ 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 { - BasicCategoryFragment, - BasicCategoryResponse, - GET_ALL_CATEGORIES, - GetAllCategoriesResponse -} from "../backend/queries/category"; -import DialogChangeCategory, {ChangeCategoryDialogContent} from "./DialogChangeCategory"; -import {useSnackbar} from "notistack"; -import { - ADD_CATEGORY, - AddCategoryResponse, - AddCategoryVariables, - EDIT_CATEGORY, - EditCategoryResponse, - EditCategoryVariables -} from "../backend/mutations/category"; +import {BasicCategoryResponse, GET_ALL_CATEGORIES, GetAllCategoriesResponse} from "../backend/queries/category"; +import DialogChangeCategory, {dialogChangeCategoryId, dialogChangeCategoryOpen} from "./DialogChangeCategory"; import DialogDeleteCategory, { dialogDeleteCategoryId, dialogDeleteCategoryOpen, @@ -34,80 +20,18 @@ const useStyles = makeStyles((theme) => ({ }, })); -const emptyChangeCategoryDialog: ChangeCategoryDialogContent = { - id: "", - title: "", - details: "", -} - export default function CategoryList() { - const [changeDialogOpen, setChangeDialogOpen] = useState(false); - const [dialogTitle, setDialogTitle] = useState(""); - const [dialogConfirmButtonText, setDialogConfirmButtonText] = useState(""); - const [changeDialogContent, setChangeDialogContent] = useState(emptyChangeCategoryDialog); - const { enqueueSnackbar } = useSnackbar(); const categories = useQuery(GET_ALL_CATEGORIES).data?.allCategories.nodes; - const [editCategory, {loading: editLoading}] = useMutation(EDIT_CATEGORY, { - onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, { variant: "error"}), - onCompleted: (response) => { - if (response.updateCategory) { - enqueueSnackbar("Kategorie erfolgreich geändert.", { variant: "success"}) - setChangeDialogOpen(false); - } else { - enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", { variant: "error"}) - } - } - }); - const [addCategory, {loading: addLoading}] = useMutation(ADD_CATEGORY, { - onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, { variant: "error"}), - onCompleted: (response) => { - if (response.createCategory) { - enqueueSnackbar("Kategorie erfolgreich hinzugefügt.", { variant: "success"}) - setChangeDialogOpen(false); - } else { - enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", { variant: "error"}) - } - }, - update: (cache, { data }) => { - cache.modify({ - fields: { - allCategories(existingCategories = { nodes: []}) { - const newCategoryRef = cache.writeFragment({ - data: data?.createCategory?.category, - fragment: BasicCategoryFragment, - fragmentName: "BasicCategoryFragment", - }); - return {nodes: [...existingCategories.nodes, newCategoryRef]}; - } - } - }); - } - }); - const classes = useStyles(); - const loading = editLoading || addLoading; - const handleAddClick = () => { - setDialogTitle("Neue Kategorie erstellen"); - setDialogConfirmButtonText("Erstellen"); - if (changeDialogContent.id !== "") { - setChangeDialogContent(emptyChangeCategoryDialog); - } - setChangeDialogOpen(true); + dialogChangeCategoryId("") + dialogChangeCategoryOpen(true) } const handleEditButtonClick = (category: BasicCategoryResponse) => { - setDialogTitle("Kategorie bearbeiten"); - setDialogConfirmButtonText("Speichern") - if (changeDialogContent.id !== category.id) { - setChangeDialogContent({ - id: category.id, - title: category.title, - details: category.description, - }) - } - setChangeDialogOpen(true); + dialogChangeCategoryId(category.id); + dialogChangeCategoryOpen(true) }; const handleDeleteButtonClick = (category: BasicCategoryResponse) => { @@ -116,29 +40,6 @@ export default function CategoryList() { dialogDeleteCategoryOpen(true); } - const handleDialogContentChange = (content: ChangeCategoryDialogContent) => { - setChangeDialogContent(content) - } - - const handleChangeConfirmButtonClick = () => { - if (changeDialogContent.id !== "") { - editCategory({ - variables: { - id: changeDialogContent.id, - title: changeDialogContent.title, - description: changeDialogContent.details, - } - }) - } else { - addCategory({ - variables: { - title: changeDialogContent.title, - description: changeDialogContent.details, - } - }) - } - }; - return ( Kategorien @@ -151,16 +52,7 @@ export default function CategoryList() { /> )} - setChangeDialogOpen(false)} - /> + ) diff --git a/redaktions-app/src/components/DialogChangeCategory.tsx b/redaktions-app/src/components/DialogChangeCategory.tsx index 840ec3e..26f10a8 100644 --- a/redaktions-app/src/components/DialogChangeCategory.tsx +++ b/redaktions-app/src/components/DialogChangeCategory.tsx @@ -1,50 +1,122 @@ -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 {DialogActionBar} from "./DialogActionBar"; import {DialogTitleAndDetails} from "./DialogTitleAndDetails"; -import {makeVar} from "@apollo/client"; +import {makeVar, useMutation, useQuery, useReactiveVar} from "@apollo/client"; +import {useSnackbar} from "notistack"; +import { + BasicCategoryFragment, + BasicCategoryResponse, + GET_CATEGORY_BY_ID, + GetCategoryByIdResponse, + GetCategoryByIdVariables +} from "../backend/queries/category"; +import { + ADD_CATEGORY, + AddCategoryResponse, + AddCategoryVariables, + EDIT_CATEGORY, + EditCategoryResponse, + EditCategoryVariables +} from "../backend/mutations/category"; -export interface ChangeCategoryDialogContent { - id: string - title: string, - details: string | null, -} +export const dialogChangeCategoryId = makeVar(""); +export const dialogChangeCategoryOpen = makeVar(false); -interface DialogChangeCategoryProps { - title: string, - confirmButtonText: string, - open: boolean, - content: ChangeCategoryDialogContent, - loading: boolean, +export default function DialogChangeCategory() { + const [addMode, setAddMode] = useState(true); + const [title, setTitle] = useState(""); + const [details, setDetails] = useState(""); + const categoryId = useReactiveVar(dialogChangeCategoryId); + const open = useReactiveVar(dialogChangeCategoryOpen); + const {enqueueSnackbar} = useSnackbar(); + useQuery(GET_CATEGORY_BY_ID, { + variables: { + id: categoryId, + }, + onCompleted: (data => { + setAddMode(!data.category && !categoryId) + 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) => { + if (response.updateCategory) { + enqueueSnackbar("Kategorie erfolgreich geändert.", {variant: "success"}) + dialogChangeCategoryOpen(false); + } else { + enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"}) + } + } + }); + const [addCategory, {loading: addLoading}] = useMutation(ADD_CATEGORY, { + onError: (e) => enqueueSnackbar(`Ein Fehler ist aufgetreten: ${e.message}`, {variant: "error"}), + onCompleted: (response) => { + if (response.createCategory) { + enqueueSnackbar("Kategorie erfolgreich hinzugefügt.", {variant: "success"}) + dialogChangeCategoryOpen(false); + } else { + enqueueSnackbar("Ein Fehler ist aufgetreten, versuche es erneut.", {variant: "error"}) + } + }, + update: (cache, {data}) => { + cache.modify({ + fields: { + allCategories(existingCategories = {nodes: []}) { + const newCategoryRef = cache.writeFragment({ + data: data?.createCategory?.category, + fragment: BasicCategoryFragment, + fragmentName: "BasicCategoryFragment", + }); + return {nodes: [...existingCategories.nodes, newCategoryRef]}; + } + } + }); + } + }); - handleContentChange(content: ChangeCategoryDialogContent): void + const handleConfirmButtonClick = () => { + if (addMode) { + addCategory({ + variables: { + title, + description: details, + } + }) + } else { + editCategory({ + variables: { + id: categoryId, + title: title, + description: details, + } + }) + } + } - handleConfirmButtonClick(): void, - - handleClose(): void, -} - - -export default function DialogChangeCategory(props: DialogChangeCategoryProps) { return ( - - {props.title} + dialogChangeCategoryOpen(false)} aria-labelledby="form-dialog-title"> + + {addMode ? "Neue Kategorie erstellen" : "Kategorie bearbeiten"} + props.handleContentChange({...props.content, title: newTitle})} - handleDetailsChange={newDetails => props.handleContentChange({...props.content, details: newDetails})} + title={title} + details={details} + onTitleChange={newTitle => setTitle(newTitle)} + onDetailsChange={newDetails => setDetails(newDetails)} /> dialogChangeCategoryOpen(false)} + onConfirmButtonClick={handleConfirmButtonClick} + confirmButtonText={addMode ? "Erstellen" : "Speichern"} + loading={addMode ? addLoading : editLoading} /> ); diff --git a/redaktions-app/src/components/DialogChangeQuestion.tsx b/redaktions-app/src/components/DialogChangeQuestion.tsx index 6857e35..1a2c642 100644 --- a/redaktions-app/src/components/DialogChangeQuestion.tsx +++ b/redaktions-app/src/components/DialogChangeQuestion.tsx @@ -39,8 +39,8 @@ export default function DialogChangeQuestion(props: DialogChangeQuestionProps) { props.handleContentChange({...props.content, title: newTitle})} - handleDetailsChange={newDetails => props.handleContentChange({...props.content, details: newDetails})} + onTitleChange={newTitle => props.handleContentChange({...props.content, title: newTitle})} + onDetailsChange={newDetails => props.handleContentChange({...props.content, details: newDetails})} /> props.handleTitleChange(e.target.value)} + onChange={e => props.onTitleChange(e.target.value)} /> props.handleDetailsChange(e.target.value)} + onChange={e => props.onDetailsChange(e.target.value)} /> ) diff --git a/redaktions-app/src/integration-tests/category-list.integration.test.tsx b/redaktions-app/src/integration-tests/category-list.integration.test.tsx index 711f721..1cf8848 100644 --- a/redaktions-app/src/integration-tests/category-list.integration.test.tsx +++ b/redaktions-app/src/integration-tests/category-list.integration.test.tsx @@ -4,7 +4,7 @@ import {MockedProvider, MockedResponse} from '@apollo/client/testing'; import {MemoryRouter} from 'react-router-dom'; import CategoryList from "../components/CategoryList"; import {SnackbarProvider} from "notistack"; -import {categoryNodesMock, getAllCategoriesMock} from "../backend/queries/category.mock"; +import {categoryNodesMock, getAllCategoriesMock, getCategoryByIdMock} from "../backend/queries/category.mock"; import {addCategoryMock, deleteCategoryMock, editCategoryMock} from "../backend/mutations/category.mock"; import {expandAccordionAndGetIconButtons, queryAllAddIconButtons, queryAllEditIconButtons} from "./test-helper"; @@ -119,7 +119,7 @@ describe('The CategoryList', () => { }); function renderCategoryList(additionalMocks?: Array) { - const initialMocks = [...getAllCategoriesMock]; + const initialMocks = [...getAllCategoriesMock, ...getCategoryByIdMock]; const allMocks = additionalMocks ? [...initialMocks, ...additionalMocks] : initialMocks return render(