#11 Add integration test for editing a question
Also: * introduce __typename to all response types
This commit is contained in:
parent
a07eb576eb
commit
587c0cdeba
|
@ -13,7 +13,10 @@ export const EDIT_CATEGORY = gql`
|
||||||
`
|
`
|
||||||
|
|
||||||
export interface EditCategoryResponse {
|
export interface EditCategoryResponse {
|
||||||
updateCategory?: BasicCategoryResponse
|
updateCategory?: {
|
||||||
|
category: BasicCategoryResponse,
|
||||||
|
__typename: "UpdateCategoryPayload",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditCategoryVariables {
|
export interface EditCategoryVariables {
|
||||||
|
@ -35,7 +38,8 @@ export const ADD_CATEGORY = gql`
|
||||||
|
|
||||||
export interface AddCategoryResponse {
|
export interface AddCategoryResponse {
|
||||||
createCategory?: {
|
createCategory?: {
|
||||||
category: BasicCategoryResponse
|
category: BasicCategoryResponse,
|
||||||
|
__typename: "CreateCategoryPayload",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +61,8 @@ export const DELETE_CATEGORY = gql`
|
||||||
|
|
||||||
export interface DeleteCategoryResponse {
|
export interface DeleteCategoryResponse {
|
||||||
deleteCategory?: {
|
deleteCategory?: {
|
||||||
category: BasicCategoryResponse
|
category: BasicCategoryResponse,
|
||||||
|
__typename: "DeleteCategoryPayload",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,41 @@
|
||||||
import {MockedResponse} from "@apollo/client/testing";
|
import {MockedResponse} from "@apollo/client/testing";
|
||||||
import {LoginResponse} from "./login";
|
import {EDIT_QUESTION, EditQuestionResponse, EditQuestionVariables} from "./question";
|
||||||
import {EDIT_QUESTION} from "./question";
|
import {BasicQuestionResponse} from "../queries/question";
|
||||||
|
|
||||||
export const loginMock: Array<MockedResponse<LoginResponse>> = [
|
|
||||||
|
const editQuestionVariables: EditQuestionVariables = {
|
||||||
|
id: 'q1',
|
||||||
|
title: 'New title for Question 1?',
|
||||||
|
description: 'Further information for Q1',
|
||||||
|
categoryRowId: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const editedQuestionMock: BasicQuestionResponse = {
|
||||||
|
id: editQuestionVariables.id,
|
||||||
|
title: editQuestionVariables.title as string,
|
||||||
|
description: editQuestionVariables.description as string,
|
||||||
|
categoryByCategoryRowId: {
|
||||||
|
id: "c1",
|
||||||
|
rowId: editQuestionVariables.categoryRowId as number,
|
||||||
|
title: "Category 1",
|
||||||
|
__typename: "Category"
|
||||||
|
},
|
||||||
|
__typename: "Question"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const editQuestionMock: Array<MockedResponse<EditQuestionResponse>> = [
|
||||||
{
|
{
|
||||||
request: {
|
request: {
|
||||||
query: EDIT_QUESTION,
|
query: EDIT_QUESTION,
|
||||||
variables: {
|
variables: editQuestionVariables,
|
||||||
email: "test@email.com",
|
|
||||||
password: "password",
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
errors: [
|
data: {
|
||||||
// {
|
updateQuestion: {
|
||||||
// message: "Authorization header is not of the correct bearer scheme format."
|
question: editedQuestionMock,
|
||||||
// }
|
__typename: "UpdateQuestionPayload",
|
||||||
]
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -13,7 +13,10 @@ export const EDIT_QUESTION = gql`
|
||||||
`
|
`
|
||||||
|
|
||||||
export interface EditQuestionResponse {
|
export interface EditQuestionResponse {
|
||||||
updateQuestion?: BasicQuestionResponse
|
updateQuestion?: {
|
||||||
|
question: BasicQuestionResponse,
|
||||||
|
__typename: "UpdateQuestionPayload",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditQuestionVariables {
|
export interface EditQuestionVariables {
|
||||||
|
@ -36,7 +39,8 @@ export const ADD_QUESTION = gql`
|
||||||
|
|
||||||
export interface AddQuestionResponse {
|
export interface AddQuestionResponse {
|
||||||
createQuestion?: {
|
createQuestion?: {
|
||||||
question: BasicQuestionResponse
|
question: BasicQuestionResponse,
|
||||||
|
__typename: "CreateQuestionPayload",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +64,7 @@ export const DELETE_QUESTION = gql`
|
||||||
export interface DeleteQuestionResponse {
|
export interface DeleteQuestionResponse {
|
||||||
deleteQuestion?: {
|
deleteQuestion?: {
|
||||||
question: BasicQuestionResponse
|
question: BasicQuestionResponse
|
||||||
|
__typename: "DeleteQuestionPayload",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
redaktions-app/src/backend/queries/category.mock.ts
Normal file
35
redaktions-app/src/backend/queries/category.mock.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import {MockedResponse} from "@apollo/client/testing";
|
||||||
|
import {BasicCategoryResponse, GET_ALL_CATEGORIES, GetAllCategoriesResponse} from "./category";
|
||||||
|
|
||||||
|
|
||||||
|
const categoryNodesMock: Array<BasicCategoryResponse> = [
|
||||||
|
{
|
||||||
|
id: "c1",
|
||||||
|
rowId: 1,
|
||||||
|
title: "Category 1",
|
||||||
|
description: "Further information for Q1",
|
||||||
|
__typename: "Category"
|
||||||
|
}, {
|
||||||
|
id: "c2",
|
||||||
|
rowId: 2,
|
||||||
|
title: "Category 2",
|
||||||
|
description: "Further information for Q2",
|
||||||
|
__typename: "Category"
|
||||||
|
}];
|
||||||
|
|
||||||
|
export const getAllCategoriesMock: Array<MockedResponse<GetAllCategoriesResponse>> = [
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
query: GET_ALL_CATEGORIES,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
allCategories: {
|
||||||
|
nodes: categoryNodesMock,
|
||||||
|
__typename: "CategoriesConnection",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import {gql} from "@apollo/client";
|
import {gql} from "@apollo/client";
|
||||||
|
|
||||||
export const BasicCategoryFragment = gql`
|
export const BasicCategoryFragment = gql`
|
||||||
fragment BasicCategoryFragment on Category {
|
fragment BasicCategoryFragment on Category {
|
||||||
id
|
id
|
||||||
rowId
|
rowId
|
||||||
title
|
title
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export interface BasicCategoryResponse {
|
export interface BasicCategoryResponse {
|
||||||
id: string,
|
id: string,
|
||||||
rowId: number,
|
rowId: number,
|
||||||
title: string,
|
title: string,
|
||||||
description: string | null,
|
description: string | null,
|
||||||
|
__typename: "Category",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GET_ALL_CATEGORIES = gql`
|
export const GET_ALL_CATEGORIES = gql`
|
||||||
|
@ -29,7 +30,8 @@ export const GET_ALL_CATEGORIES = gql`
|
||||||
|
|
||||||
export interface GetAllCategoriesResponse {
|
export interface GetAllCategoriesResponse {
|
||||||
allCategories: {
|
allCategories: {
|
||||||
nodes: Array<BasicCategoryResponse>
|
nodes: Array<BasicCategoryResponse>,
|
||||||
|
__typename: "CategoriesConnection",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
48
redaktions-app/src/backend/queries/question.mock.ts
Normal file
48
redaktions-app/src/backend/queries/question.mock.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import {MockedResponse} from "@apollo/client/testing";
|
||||||
|
import {BasicQuestionResponse, GET_ALL_QUESTIONS, GetAllQuestionsResponse} from "./question";
|
||||||
|
|
||||||
|
|
||||||
|
export const questionNodesMock: Array<BasicQuestionResponse> = [{
|
||||||
|
id: "q1",
|
||||||
|
title: "Question 1?",
|
||||||
|
description: "Further information for Q1",
|
||||||
|
categoryByCategoryRowId: {
|
||||||
|
id: "c1",
|
||||||
|
rowId: 1,
|
||||||
|
title: "Category 1",
|
||||||
|
__typename: "Category"
|
||||||
|
},
|
||||||
|
__typename: "Question",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q2",
|
||||||
|
title: "Question 2?",
|
||||||
|
description: "Further information for Q2",
|
||||||
|
categoryByCategoryRowId: null,
|
||||||
|
__typename: "Question",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q3",
|
||||||
|
title: "Question 3?",
|
||||||
|
description: null,
|
||||||
|
categoryByCategoryRowId: null,
|
||||||
|
__typename: "Question",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getAllQuestionsMock: Array<MockedResponse<GetAllQuestionsResponse>> = [
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
query: GET_ALL_QUESTIONS,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
allQuestions: {
|
||||||
|
nodes: questionNodesMock,
|
||||||
|
__typename: "QuestionsConnection",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
|
@ -12,6 +12,7 @@ interface GetQuestionsCategoryResponse {
|
||||||
id: string,
|
id: string,
|
||||||
rowId: number,
|
rowId: number,
|
||||||
title: string,
|
title: string,
|
||||||
|
__typename: "Category",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BasicQuestionFragment = gql`
|
export const BasicQuestionFragment = gql`
|
||||||
|
@ -30,7 +31,8 @@ export interface BasicQuestionResponse {
|
||||||
id: string,
|
id: string,
|
||||||
title: string,
|
title: string,
|
||||||
description: string | null,
|
description: string | null,
|
||||||
categoryByCategoryRowId: GetQuestionsCategoryResponse | null
|
categoryByCategoryRowId: GetQuestionsCategoryResponse | null,
|
||||||
|
__typename: "Question",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GET_ALL_QUESTIONS = gql`
|
export const GET_ALL_QUESTIONS = gql`
|
||||||
|
@ -46,6 +48,7 @@ export const GET_ALL_QUESTIONS = gql`
|
||||||
|
|
||||||
export interface GetAllQuestionsResponse {
|
export interface GetAllQuestionsResponse {
|
||||||
allQuestions: {
|
allQuestions: {
|
||||||
nodes: Array<BasicQuestionResponse>
|
nodes: Array<BasicQuestionResponse>,
|
||||||
|
__typename: "QuestionsConnection",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,11 +65,11 @@ export default function AccordionWithEdit(props: AccordionWithEditProps) {
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
<AccordionActions>
|
<AccordionActions>
|
||||||
<IconButton size={"small"} aria-label="edit" onClick={props.onEditButtonClick}>
|
<IconButton data-testid="edit-icon-button" size={"small"} aria-label="edit" onClick={props.onEditButtonClick}>
|
||||||
<EditIcon/>
|
<EditIcon titleAccess="Anpassen"/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton size={"small"} aria-label="delete" onClick={props.onDeleteButtonClick}>
|
<IconButton data-testid="delete-icon-button" size={"small"} aria-label="delete" onClick={props.onDeleteButtonClick}>
|
||||||
<DeleteIcon/>
|
<DeleteIcon titleAccess="Löschen"/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</AccordionActions>
|
</AccordionActions>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {fireEvent, queryAllByRole, render, screen, waitFor} from '@testing-library/react'
|
||||||
|
import {MockedProvider} 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 {getAllCategoriesMock} from "../backend/queries/category.mock";
|
||||||
|
import {editQuestionMock} from "../backend/mutations/question.mock";
|
||||||
|
|
||||||
|
|
||||||
|
function renderQuestionList() {
|
||||||
|
return render(
|
||||||
|
<MockedProvider mocks={[...getAllQuestionsMock, ...getAllCategoriesMock, ...editQuestionMock]}>
|
||||||
|
<MemoryRouter>
|
||||||
|
<SnackbarProvider>
|
||||||
|
<QuestionList/>
|
||||||
|
</SnackbarProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInitialQuestionCards = async (): Promise<Array<HTMLElement>> => {
|
||||||
|
const numberOfQuestionsInMockQuery = questionNodesMock.length;
|
||||||
|
let questionCards: Array<HTMLElement> = [];
|
||||||
|
await waitFor(() => {
|
||||||
|
questionCards = screen.queryAllByRole("button", {name: /Question [1-3]\?/})
|
||||||
|
expect(questionCards.length).toEqual(numberOfQuestionsInMockQuery);
|
||||||
|
});
|
||||||
|
return questionCards;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sorry, I found no better way to find a specific icon button...
|
||||||
|
const queryAllEditIconsButtons = (container?: HTMLElement): Array<HTMLElement> => {
|
||||||
|
return (container ? queryAllByRole(container, "button") : screen.queryAllByRole("button"))
|
||||||
|
.filter(button => button.innerHTML.includes("svg") && button.innerHTML.includes("<title>Anpassen</title>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('The QuestionList', () => {
|
||||||
|
test('displays the existing questions, but not the details of it', async () => {
|
||||||
|
renderQuestionList();
|
||||||
|
|
||||||
|
const questionCards = await getInitialQuestionCards()
|
||||||
|
questionCards.forEach(card => {
|
||||||
|
expect(card.innerHTML).toMatch(/Question [1-3]\?/)
|
||||||
|
})
|
||||||
|
expect(questionCards[0].innerHTML).toMatch(/Category 1/);
|
||||||
|
expect(queryAllEditIconsButtons()).toHaveLength(0)
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enables toggling details on each question', async () => {
|
||||||
|
renderQuestionList();
|
||||||
|
|
||||||
|
// Initial state: Every question card is not expanded
|
||||||
|
const questionCards = await getInitialQuestionCards()
|
||||||
|
expect(queryAllEditIconsButtons()).toHaveLength(0)
|
||||||
|
|
||||||
|
// Expand first question card
|
||||||
|
fireEvent.click(questionCards[0])
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(queryAllEditIconsButtons()).toHaveLength(1)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shrink first question card again
|
||||||
|
fireEvent.click(questionCards[0])
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(queryAllEditIconsButtons()).toHaveLength(0)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enables editing a question title', async () => {
|
||||||
|
renderQuestionList();
|
||||||
|
|
||||||
|
const questionCards = await getInitialQuestionCards();
|
||||||
|
|
||||||
|
// Expand first card
|
||||||
|
fireEvent.click(questionCards[0]);
|
||||||
|
let editIcons: Array<HTMLElement> = [];
|
||||||
|
await waitFor(() => {
|
||||||
|
editIcons = queryAllEditIconsButtons();
|
||||||
|
expect(editIcons).toHaveLength(1);
|
||||||
|
})
|
||||||
|
|
||||||
|
// open edit dialog
|
||||||
|
expect(screen.queryByText(/Frage bearbeiten/)).toBeNull();
|
||||||
|
fireEvent.click(editIcons[0]);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(/Frage bearbeiten/)).not.toBeNull();
|
||||||
|
})
|
||||||
|
|
||||||
|
// change question title
|
||||||
|
const questionTitleField = screen.getByDisplayValue(/Question 1/);
|
||||||
|
fireEvent.change(questionTitleField, {target: {value: "New title for Question 1?"}});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByDisplayValue(/New title for /)).not.toBeNull();
|
||||||
|
})
|
||||||
|
const confirmButton = screen.getByRole("button", {name: /Speichern/});
|
||||||
|
|
||||||
|
// call backend and assert apollo cache update
|
||||||
|
fireEvent.click(confirmButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(/Frage bearbeiten/)).toBeNull();
|
||||||
|
expect(screen.queryByText(/New title for Question 1/)).not.toBeNull()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue