#11 Added integration tests for deleting and adding questions
This commit is contained in:
parent
4ed8927522
commit
acbe0a453c
|
@ -1,6 +1,14 @@
|
|||
import {MockedResponse} from "@apollo/client/testing";
|
||||
import {EDIT_QUESTION, EditQuestionResponse, EditQuestionVariables} from "./question";
|
||||
import {
|
||||
ADD_QUESTION, AddQuestionResponse,
|
||||
AddQuestionVariables, DELETE_QUESTION, DeleteQuestionPayload, DeleteQuestionResponse, DeleteQuestionVariables,
|
||||
EDIT_QUESTION, EditQuestionPayload,
|
||||
EditQuestionResponse,
|
||||
EditQuestionVariables
|
||||
} from "./question";
|
||||
import {BasicQuestionResponse} from "../queries/question";
|
||||
import {questionNodesMock} from "../queries/question.mock";
|
||||
import {categoryNodesMock} from "../queries/category.mock";
|
||||
|
||||
|
||||
const editQuestionVariables: EditQuestionVariables = {
|
||||
|
@ -10,17 +18,19 @@ const editQuestionVariables: EditQuestionVariables = {
|
|||
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"
|
||||
const getEditedQuestionMock = (): EditQuestionPayload | null => {
|
||||
const originalQuestion = questionNodesMock.find(q => q.id === editQuestionVariables.id)
|
||||
return originalQuestion ? {
|
||||
question: {
|
||||
...originalQuestion,
|
||||
title: editQuestionVariables.title === undefined ? originalQuestion.title : editQuestionVariables.title,
|
||||
description: editQuestionVariables.description === undefined ? originalQuestion.description : null,
|
||||
categoryByCategoryRowId: editQuestionVariables.categoryRowId === undefined
|
||||
? originalQuestion.categoryByCategoryRowId
|
||||
: categoryNodesMock.find(c => c.rowId === editQuestionVariables.categoryRowId) || null,
|
||||
},
|
||||
__typename: "UpdateQuestionPayload",
|
||||
} : null
|
||||
}
|
||||
|
||||
export const editQuestionMock: Array<MockedResponse<EditQuestionResponse>> = [
|
||||
|
@ -31,12 +41,66 @@ export const editQuestionMock: Array<MockedResponse<EditQuestionResponse>> = [
|
|||
},
|
||||
result: {
|
||||
data: {
|
||||
updateQuestion: {
|
||||
question: editedQuestionMock,
|
||||
__typename: "UpdateQuestionPayload",
|
||||
updateQuestion: getEditedQuestionMock(),
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const addQuestionVariables: AddQuestionVariables = {
|
||||
title: 'New question?',
|
||||
description: "",
|
||||
categoryRowId: null,
|
||||
};
|
||||
|
||||
const addedQuestionMock: BasicQuestionResponse = {
|
||||
id: `newQ`,
|
||||
title: addQuestionVariables.title as string,
|
||||
description: addQuestionVariables.description as string,
|
||||
categoryByCategoryRowId: categoryNodesMock.find(c => c.rowId === editQuestionVariables.categoryRowId) || null,
|
||||
__typename: "Question"
|
||||
}
|
||||
|
||||
export const addQuestionMock: Array<MockedResponse<AddQuestionResponse>> = [
|
||||
{
|
||||
request: {
|
||||
query: ADD_QUESTION,
|
||||
variables: addQuestionVariables,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
createQuestion: {
|
||||
question: addedQuestionMock,
|
||||
__typename: "CreateQuestionPayload",
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const deleteQuestionVariables: DeleteQuestionVariables = {
|
||||
id: 'q2'
|
||||
};
|
||||
|
||||
const getDeletedQuestionMock = (): DeleteQuestionPayload | null => {
|
||||
const questionToBeDeleted = questionNodesMock.find(q => q.id === deleteQuestionVariables.id)
|
||||
return questionToBeDeleted ? {
|
||||
question: questionToBeDeleted,
|
||||
__typename: "DeleteQuestionPayload",
|
||||
} : null
|
||||
}
|
||||
|
||||
export const deleteQuestionMock: Array<MockedResponse<DeleteQuestionResponse>> = [
|
||||
{
|
||||
request: {
|
||||
query: DELETE_QUESTION,
|
||||
variables: deleteQuestionVariables,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
deleteQuestion: getDeletedQuestionMock(),
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -13,10 +13,12 @@ export const EDIT_QUESTION = gql`
|
|||
`
|
||||
|
||||
export interface EditQuestionResponse {
|
||||
updateQuestion?: {
|
||||
question: BasicQuestionResponse,
|
||||
__typename: "UpdateQuestionPayload",
|
||||
},
|
||||
updateQuestion: EditQuestionPayload | null,
|
||||
}
|
||||
|
||||
export interface EditQuestionPayload {
|
||||
question: BasicQuestionResponse,
|
||||
__typename: "UpdateQuestionPayload",
|
||||
}
|
||||
|
||||
export interface EditQuestionVariables {
|
||||
|
@ -38,10 +40,12 @@ export const ADD_QUESTION = gql`
|
|||
`
|
||||
|
||||
export interface AddQuestionResponse {
|
||||
createQuestion?: {
|
||||
question: BasicQuestionResponse,
|
||||
__typename: "CreateQuestionPayload",
|
||||
}
|
||||
createQuestion: AddQuestionPayload | null
|
||||
}
|
||||
|
||||
export interface AddQuestionPayload {
|
||||
question: BasicQuestionResponse,
|
||||
__typename: "CreateQuestionPayload",
|
||||
}
|
||||
|
||||
export interface AddQuestionVariables {
|
||||
|
@ -62,10 +66,12 @@ export const DELETE_QUESTION = gql`
|
|||
`
|
||||
|
||||
export interface DeleteQuestionResponse {
|
||||
deleteQuestion?: {
|
||||
question: BasicQuestionResponse
|
||||
__typename: "DeleteQuestionPayload",
|
||||
}
|
||||
deleteQuestion: DeleteQuestionPayload | null,
|
||||
}
|
||||
|
||||
export interface DeleteQuestionPayload {
|
||||
question: BasicQuestionResponse,
|
||||
__typename: "DeleteQuestionPayload",
|
||||
}
|
||||
|
||||
export interface DeleteQuestionVariables {
|
||||
|
|
|
@ -2,7 +2,7 @@ import {MockedResponse} from "@apollo/client/testing";
|
|||
import {BasicCategoryResponse, GET_ALL_CATEGORIES, GetAllCategoriesResponse} from "./category";
|
||||
|
||||
|
||||
const categoryNodesMock: Array<BasicCategoryResponse> = [
|
||||
export const categoryNodesMock: Array<BasicCategoryResponse> = [
|
||||
{
|
||||
id: "c1",
|
||||
rowId: 1,
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import {render} from '@testing-library/react'
|
||||
import AccordionWithEdit from "./AccordionWithEdit";
|
||||
|
||||
|
||||
describe('The Question component', () => {
|
||||
test('displays title, category and description', () => {
|
||||
const {queryByText} = render(<AccordionWithEdit key={"test"} title={"Test Title"} subTitle={"Test Category"}
|
||||
description="Test Description"/>);
|
||||
expect(queryByText(/Test Title/)?.textContent).toBe("Test Title");
|
||||
expect(queryByText(/Test Category/)?.textContent).toBe("Test Category");
|
||||
expect(queryByText(/Test Description/)?.textContent).toBe("Test Description");
|
||||
});
|
||||
});
|
|
@ -108,7 +108,6 @@ export default function QuestionList() {
|
|||
cache.modify({
|
||||
fields: {
|
||||
allQuestions(existingQuestionsRef: { nodes: Array<Reference>} = { nodes: []}, {readField}) {
|
||||
console.log("existingQuestion: ", existingQuestionsRef)
|
||||
return {nodes: existingQuestionsRef.nodes.filter(questionRef => readField('id', questionRef) !== idToRemove)};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,131 @@
|
|||
import React from 'react';
|
||||
import {fireEvent, queryAllByRole, render, screen, waitFor} from '@testing-library/react'
|
||||
import {MockedProvider} from '@apollo/client/testing';
|
||||
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 {getAllCategoriesMock} from "../backend/queries/category.mock";
|
||||
import {editQuestionMock} from "../backend/mutations/question.mock";
|
||||
import {getDeleteIconPath, getEditIconPath} from "./test-helper";
|
||||
import {addQuestionMock, deleteQuestionMock, editQuestionMock} from "../backend/mutations/question.mock";
|
||||
import {getAddIconPath, getDeleteIconPath, getEditIconPath} from "./test-helper";
|
||||
|
||||
|
||||
function renderQuestionList() {
|
||||
describe('The QuestionList', () => {
|
||||
test('displays the existing questions, but not the details of it', async () => {
|
||||
renderQuestionList();
|
||||
|
||||
const questionCards = await waitForInitialQuestionsToRender()
|
||||
questionCards.forEach(card => {
|
||||
expect(card.innerHTML).toMatch(/Question [1-3]\?/)
|
||||
})
|
||||
expect(questionCards[0].innerHTML).toMatch(/Category 1/);
|
||||
expect(queryAllEditIconButtons()).toHaveLength(0)
|
||||
});
|
||||
|
||||
test('enables toggling details on each question', async () => {
|
||||
renderQuestionList();
|
||||
|
||||
// Initial state: Every question card is not expanded
|
||||
const questionCards = await waitForInitialQuestionsToRender()
|
||||
|
||||
// Expand first question card
|
||||
await expandAccordionAndGetIconButtons(questionCards[0])
|
||||
|
||||
// Shrink first question card again
|
||||
fireEvent.click(questionCards[0])
|
||||
await waitFor(() => {
|
||||
expect(queryAllEditIconButtons()).toHaveLength(0)
|
||||
});
|
||||
});
|
||||
|
||||
test('enables editing a question title', async () => {
|
||||
renderQuestionList(editQuestionMock);
|
||||
|
||||
const questionCards = await waitForInitialQuestionsToRender();
|
||||
const {editIconButton} = await expandAccordionAndGetIconButtons(questionCards[0]);
|
||||
|
||||
// open edit dialog
|
||||
expect(screen.queryByText(/Frage bearbeiten/)).toBeNull();
|
||||
fireEvent.click(editIconButton);
|
||||
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();
|
||||
})
|
||||
|
||||
// call backend and assert apollo cache update
|
||||
const confirmButton = screen.getByRole("button", {name: /Speichern/});
|
||||
fireEvent.click(confirmButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/Frage bearbeiten/)).toBeNull();
|
||||
expect(screen.queryByText(/New title for Question 1/)).not.toBeNull()
|
||||
})
|
||||
});
|
||||
|
||||
test('enables adding a question', async () => {
|
||||
renderQuestionList(addQuestionMock);
|
||||
await waitForInitialQuestionsToRender();
|
||||
|
||||
// open add dialog
|
||||
const dialogIdentifier = /Neue Frage erstellen/;
|
||||
expect(screen.queryByText(dialogIdentifier)).toBeNull();
|
||||
const addButton = queryAllAddIconButtons()[0];
|
||||
fireEvent.click(addButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(dialogIdentifier)).not.toBeNull();
|
||||
})
|
||||
|
||||
// change question title
|
||||
const questionTitleField = screen.getByLabelText(/Zusammenfassung/);
|
||||
fireEvent.change(questionTitleField, {target: {value: "New question?"}});
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByDisplayValue(/New question/)).not.toBeNull();
|
||||
})
|
||||
|
||||
// call backend and assert apollo cache update
|
||||
const confirmButton = screen.getByRole("button", {name: /Erstellen/});
|
||||
fireEvent.click(confirmButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(dialogIdentifier)).toBeNull();
|
||||
expect(screen.queryByText(/New question/)).not.toBeNull()
|
||||
})
|
||||
});
|
||||
|
||||
test('enables deleting a question', async () => {
|
||||
renderQuestionList(deleteQuestionMock);
|
||||
|
||||
const questionCards = await waitForInitialQuestionsToRender();
|
||||
expect(screen.queryByText(/Question 2/)).not.toBeNull();
|
||||
const {deleteIconButton} = await expandAccordionAndGetIconButtons(questionCards[1]);
|
||||
|
||||
// open delete confirmation dialog
|
||||
expect(screen.queryByText(/Frage löschen/)).toBeNull();
|
||||
fireEvent.click(deleteIconButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/Frage löschen/)).not.toBeNull();
|
||||
})
|
||||
|
||||
// call backend and assert apollo cache update
|
||||
const confirmButton = screen.getByRole("button", {name: /Löschen/});
|
||||
fireEvent.click(confirmButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/Frage löschen/)).toBeNull();
|
||||
expect(screen.queryByText(/Question 2/)).toBeNull();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function renderQuestionList(additionalMocks?: Array<MockedResponse>) {
|
||||
const initialMocks = [...getAllQuestionsMock, ...getAllCategoriesMock];
|
||||
const allMocks = additionalMocks ? [...initialMocks, ...additionalMocks] : initialMocks
|
||||
return render(
|
||||
<MockedProvider mocks={[...getAllQuestionsMock, ...getAllCategoriesMock, ...editQuestionMock]}>
|
||||
<MockedProvider mocks={allMocks}>
|
||||
<MemoryRouter>
|
||||
<SnackbarProvider>
|
||||
<QuestionList/>
|
||||
|
@ -33,26 +146,32 @@ const waitForInitialQuestionsToRender = async (): Promise<Array<HTMLElement>> =>
|
|||
}
|
||||
|
||||
// sorry, I found no better way to find a specific icon button...
|
||||
const queryAllEditIconsButtons = (container?: HTMLElement): Array<HTMLElement> => {
|
||||
const queryAllEditIconButtons = (container?: HTMLElement): Array<HTMLElement> => {
|
||||
return (container ? queryAllByRole(container, "button") : screen.queryAllByRole("button"))
|
||||
.filter(button => button.innerHTML.includes("svg") && button.innerHTML.includes(getEditIconPath()));
|
||||
}
|
||||
|
||||
// sorry, I found no better way to find a specific icon button...
|
||||
const queryAllDeleteIconsButtons = (container?: HTMLElement): Array<HTMLElement> => {
|
||||
const queryAllDeleteIconButtons = (container?: HTMLElement): Array<HTMLElement> => {
|
||||
return (container ? queryAllByRole(container, "button") : screen.queryAllByRole("button"))
|
||||
.filter(button => button.innerHTML.includes("svg") && button.innerHTML.includes(getDeleteIconPath()));
|
||||
}
|
||||
|
||||
// sorry, I found no better way to find a specific icon button...
|
||||
const queryAllAddIconButtons = (container?: HTMLElement): Array<HTMLElement> => {
|
||||
return (container ? queryAllByRole(container, "button") : screen.queryAllByRole("button"))
|
||||
.filter(button => button.innerHTML.includes("svg") && button.innerHTML.includes(getAddIconPath()));
|
||||
}
|
||||
|
||||
const expandAccordionAndGetIconButtons = async (accordion: HTMLElement): Promise<{ editIconButton: HTMLElement, deleteIconButton: HTMLElement }> => {
|
||||
let editIconsButtons = queryAllDeleteIconsButtons();
|
||||
let deleteIconsButtons = queryAllEditIconsButtons();
|
||||
let editIconsButtons = queryAllDeleteIconButtons();
|
||||
let deleteIconsButtons = queryAllEditIconButtons();
|
||||
expect(editIconsButtons).toHaveLength(0);
|
||||
expect(deleteIconsButtons).toHaveLength(0);
|
||||
fireEvent.click(accordion);
|
||||
await waitFor(() => {
|
||||
editIconsButtons = queryAllEditIconsButtons();
|
||||
deleteIconsButtons = queryAllDeleteIconsButtons();
|
||||
editIconsButtons = queryAllEditIconButtons();
|
||||
deleteIconsButtons = queryAllDeleteIconButtons();
|
||||
expect(editIconsButtons).toHaveLength(1);
|
||||
expect(deleteIconsButtons).toHaveLength(1);
|
||||
})
|
||||
|
@ -61,61 +180,3 @@ const expandAccordionAndGetIconButtons = async (accordion: HTMLElement): Promise
|
|||
deleteIconButton: deleteIconsButtons[0]
|
||||
};
|
||||
}
|
||||
|
||||
describe('The QuestionList', () => {
|
||||
test('displays the existing questions, but not the details of it', async () => {
|
||||
renderQuestionList();
|
||||
|
||||
const questionCards = await waitForInitialQuestionsToRender()
|
||||
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 waitForInitialQuestionsToRender()
|
||||
|
||||
// Expand first question card
|
||||
await expandAccordionAndGetIconButtons(questionCards[0])
|
||||
|
||||
// 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 waitForInitialQuestionsToRender();
|
||||
const {editIconButton} = await expandAccordionAndGetIconButtons(questionCards[0]);
|
||||
|
||||
// open edit dialog
|
||||
expect(screen.queryByText(/Frage bearbeiten/)).toBeNull();
|
||||
fireEvent.click(editIconButton);
|
||||
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()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import {render} from '@testing-library/react'
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import AddIcon from '@material-ui/icons/Add';
|
||||
|
||||
|
||||
const memoizedGetIconPath = (icon: JSX.Element) => {
|
||||
|
@ -21,6 +22,7 @@ const memoizedGetIconPath = (icon: JSX.Element) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const getDeleteIconPath = memoizedGetIconPath(<DeleteIcon/>)
|
||||
export const getEditIconPath = memoizedGetIconPath(<EditIcon/>)
|
||||
export const getDeleteIconPath = memoizedGetIconPath(<DeleteIcon/>)
|
||||
export const getAddIconPath = memoizedGetIconPath(<AddIcon/>)
|
||||
|
||||
|
|
Loading…
Reference in a new issue