#11 Add integration tests for displaying/deleting/editing and adding categories
This commit is contained in:
parent
acbe0a453c
commit
42dc7f285d
100
redaktions-app/src/backend/mutations/category.mock.ts
Normal file
100
redaktions-app/src/backend/mutations/category.mock.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
import {MockedResponse} from "@apollo/client/testing";
|
||||
import {
|
||||
ADD_CATEGORY, AddCategoryResponse,
|
||||
AddCategoryVariables, DELETE_CATEGORY, DeleteCategoryPayload, DeleteCategoryResponse, DeleteCategoryVariables,
|
||||
EDIT_CATEGORY, EditCategoryPayload,
|
||||
EditCategoryResponse,
|
||||
EditCategoryVariables
|
||||
} from "./category";
|
||||
import {BasicCategoryResponse} from "../queries/category";
|
||||
import {categoryNodesMock} from "../queries/category.mock";
|
||||
|
||||
|
||||
const editCategoryVariables: EditCategoryVariables = {
|
||||
id: 'c1',
|
||||
title: 'New title for Category 1',
|
||||
description: 'Further information for C1',
|
||||
};
|
||||
|
||||
const getEditedCategoryMock = (): EditCategoryPayload | null => {
|
||||
const originalCategory = categoryNodesMock.find(c => c.id === editCategoryVariables.id)
|
||||
return originalCategory ? {
|
||||
category: {
|
||||
...originalCategory,
|
||||
title: editCategoryVariables.title === undefined ? originalCategory.title : editCategoryVariables.title,
|
||||
description: editCategoryVariables.description === undefined ? originalCategory.description : null,
|
||||
},
|
||||
__typename: "UpdateCategoryPayload",
|
||||
} : null
|
||||
}
|
||||
|
||||
export const editCategoryMock: Array<MockedResponse<EditCategoryResponse>> = [
|
||||
{
|
||||
request: {
|
||||
query: EDIT_CATEGORY,
|
||||
variables: editCategoryVariables,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
updateCategory: getEditedCategoryMock(),
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const addCategoryVariables: AddCategoryVariables = {
|
||||
title: 'New category',
|
||||
description: "",
|
||||
};
|
||||
|
||||
const addedCategoryMock: BasicCategoryResponse = {
|
||||
id: `newC`,
|
||||
rowId: 3,
|
||||
title: addCategoryVariables.title as string,
|
||||
description: addCategoryVariables.description as string,
|
||||
__typename: "Category"
|
||||
}
|
||||
|
||||
export const addCategoryMock: Array<MockedResponse<AddCategoryResponse>> = [
|
||||
{
|
||||
request: {
|
||||
query: ADD_CATEGORY,
|
||||
variables: addCategoryVariables,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
createCategory: {
|
||||
category: addedCategoryMock,
|
||||
__typename: "CreateCategoryPayload",
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const deleteCategoryVariables: DeleteCategoryVariables = {
|
||||
id: 'c2'
|
||||
};
|
||||
|
||||
const getDeletedCategoryMock = (): DeleteCategoryPayload | null => {
|
||||
const categoryToBeDeleted = categoryNodesMock.find(q => q.id === deleteCategoryVariables.id)
|
||||
return categoryToBeDeleted ? {
|
||||
category: categoryToBeDeleted,
|
||||
__typename: "DeleteCategoryPayload",
|
||||
} : null
|
||||
}
|
||||
|
||||
export const deleteCategoryMock: Array<MockedResponse<DeleteCategoryResponse>> = [
|
||||
{
|
||||
request: {
|
||||
query: DELETE_CATEGORY,
|
||||
variables: deleteCategoryVariables,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
deleteCategory: getDeletedCategoryMock(),
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
@ -13,10 +13,12 @@ export const EDIT_CATEGORY = gql`
|
|||
`
|
||||
|
||||
export interface EditCategoryResponse {
|
||||
updateCategory?: {
|
||||
category: BasicCategoryResponse,
|
||||
__typename: "UpdateCategoryPayload",
|
||||
}
|
||||
updateCategory: EditCategoryPayload | null
|
||||
}
|
||||
|
||||
export interface EditCategoryPayload {
|
||||
category: BasicCategoryResponse,
|
||||
__typename: "UpdateCategoryPayload",
|
||||
}
|
||||
|
||||
export interface EditCategoryVariables {
|
||||
|
@ -37,10 +39,12 @@ export const ADD_CATEGORY = gql`
|
|||
`
|
||||
|
||||
export interface AddCategoryResponse {
|
||||
createCategory?: {
|
||||
category: BasicCategoryResponse,
|
||||
__typename: "CreateCategoryPayload",
|
||||
}
|
||||
createCategory: AddCategoryPayload | null,
|
||||
}
|
||||
|
||||
export interface AddCategoryPayload {
|
||||
category: BasicCategoryResponse,
|
||||
__typename: "CreateCategoryPayload",
|
||||
}
|
||||
|
||||
export interface AddCategoryVariables {
|
||||
|
@ -60,14 +64,14 @@ export const DELETE_CATEGORY = gql`
|
|||
`
|
||||
|
||||
export interface DeleteCategoryResponse {
|
||||
deleteCategory?: {
|
||||
category: BasicCategoryResponse,
|
||||
__typename: "DeleteCategoryPayload",
|
||||
}
|
||||
deleteCategory: DeleteCategoryPayload | null
|
||||
}
|
||||
|
||||
export interface DeleteCategoryPayload {
|
||||
category: BasicCategoryResponse,
|
||||
__typename: "DeleteCategoryPayload",
|
||||
}
|
||||
|
||||
export interface DeleteCategoryVariables {
|
||||
id: string,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ export const categoryNodesMock: Array<BasicCategoryResponse> = [
|
|||
id: "c1",
|
||||
rowId: 1,
|
||||
title: "Category 1",
|
||||
description: "Further information for Q1",
|
||||
description: "Further information for C1",
|
||||
__typename: "Category"
|
||||
}, {
|
||||
id: "c2",
|
||||
rowId: 2,
|
||||
title: "Category 2",
|
||||
description: "Further information for Q2",
|
||||
description: "Further information for C2",
|
||||
__typename: "Category"
|
||||
}];
|
||||
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
import React from 'react';
|
||||
import {fireEvent, render, screen, waitFor} from '@testing-library/react'
|
||||
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 {addCategoryMock, deleteCategoryMock, editCategoryMock} from "../backend/mutations/category.mock";
|
||||
import {expandAccordionAndGetIconButtons, queryAllAddIconButtons, queryAllEditIconButtons} from "./test-helper";
|
||||
|
||||
|
||||
describe('The CategoryList', () => {
|
||||
test('displays the existing categories, but not the details of it', async () => {
|
||||
renderCategoryList();
|
||||
|
||||
const categoryCards = await waitForInitialCategoriesToRender()
|
||||
categoryCards.forEach(card => {
|
||||
expect(card.innerHTML).toMatch(/Category [1-2]/)
|
||||
})
|
||||
expect(queryAllEditIconButtons()).toHaveLength(0)
|
||||
});
|
||||
|
||||
test('enables toggling details on each category', async () => {
|
||||
renderCategoryList();
|
||||
|
||||
// Initial state: Every category card is not expanded
|
||||
const categoryCards = await waitForInitialCategoriesToRender()
|
||||
|
||||
// Expand first category card
|
||||
await expandAccordionAndGetIconButtons(categoryCards[0])
|
||||
|
||||
// Shrink first category card again
|
||||
fireEvent.click(categoryCards[0])
|
||||
await waitFor(() => {
|
||||
expect(queryAllEditIconButtons()).toHaveLength(0)
|
||||
});
|
||||
});
|
||||
|
||||
test('enables editing a category title', async () => {
|
||||
renderCategoryList(editCategoryMock);
|
||||
|
||||
const categoryCards = await waitForInitialCategoriesToRender();
|
||||
const {editIconButton} = await expandAccordionAndGetIconButtons(categoryCards[0]);
|
||||
|
||||
// open edit dialog
|
||||
expect(screen.queryByText(/Kategorie bearbeiten/)).toBeNull();
|
||||
fireEvent.click(editIconButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/Kategorie bearbeiten/)).not.toBeNull();
|
||||
})
|
||||
|
||||
// change category title
|
||||
const categoryTitleField = screen.getByDisplayValue(/Category 1/);
|
||||
fireEvent.change(categoryTitleField, {target: {value: "New title for Category 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(/Kategorie bearbeiten/)).toBeNull();
|
||||
expect(screen.queryByText(/New title for Category 1/)).not.toBeNull()
|
||||
})
|
||||
});
|
||||
|
||||
test('enables adding a category', async () => {
|
||||
renderCategoryList(addCategoryMock);
|
||||
await waitForInitialCategoriesToRender();
|
||||
|
||||
// open add dialog
|
||||
const dialogIdentifier = /Neue Kategorie erstellen/;
|
||||
expect(screen.queryByText(dialogIdentifier)).toBeNull();
|
||||
const addButton = queryAllAddIconButtons()[0];
|
||||
fireEvent.click(addButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(dialogIdentifier)).not.toBeNull();
|
||||
})
|
||||
|
||||
// change category title
|
||||
const categoryTitleField = screen.getByLabelText(/Zusammenfassung/);
|
||||
fireEvent.change(categoryTitleField, {target: {value: "New category"}});
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByDisplayValue(/New category/)).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 category/)).not.toBeNull()
|
||||
})
|
||||
});
|
||||
|
||||
test('enables deleting a category', async () => {
|
||||
renderCategoryList(deleteCategoryMock);
|
||||
|
||||
const categoryCards = await waitForInitialCategoriesToRender();
|
||||
expect(screen.queryByText(/Category 2/)).not.toBeNull();
|
||||
const {deleteIconButton} = await expandAccordionAndGetIconButtons(categoryCards[1]);
|
||||
|
||||
// open delete confirmation dialog
|
||||
expect(screen.queryByText(/Kategorie löschen/)).toBeNull();
|
||||
fireEvent.click(deleteIconButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/Kategorie 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(/Kategorie löschen/)).toBeNull();
|
||||
expect(screen.queryByText(/Category 2/)).toBeNull();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
function renderCategoryList(additionalMocks?: Array<MockedResponse>) {
|
||||
const initialMocks = [...getAllCategoriesMock];
|
||||
const allMocks = additionalMocks ? [...initialMocks, ...additionalMocks] : initialMocks
|
||||
return render(
|
||||
<MockedProvider mocks={allMocks}>
|
||||
<MemoryRouter>
|
||||
<SnackbarProvider>
|
||||
<CategoryList/>
|
||||
</SnackbarProvider>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const waitForInitialCategoriesToRender = async (): Promise<Array<HTMLElement>> => {
|
||||
const numberOfCategoriesInMockQuery = categoryNodesMock.length;
|
||||
let categoryCards: Array<HTMLElement> = [];
|
||||
await waitFor(() => {
|
||||
categoryCards = screen.queryAllByRole("button", {name: /Category [1-2]/})
|
||||
expect(categoryCards.length).toEqual(numberOfCategoriesInMockQuery);
|
||||
});
|
||||
return categoryCards;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import {fireEvent, queryAllByRole, render, screen, waitFor} from '@testing-library/react'
|
||||
import {fireEvent, render, screen, waitFor} from '@testing-library/react'
|
||||
import {MockedProvider, MockedResponse} from '@apollo/client/testing';
|
||||
import {MemoryRouter} from 'react-router-dom';
|
||||
import QuestionList from "../components/QuestionList";
|
||||
|
@ -7,7 +7,11 @@ import {SnackbarProvider} from "notistack";
|
|||
import {getAllQuestionsMock, questionNodesMock} from "../backend/queries/question.mock";
|
||||
import {getAllCategoriesMock} from "../backend/queries/category.mock";
|
||||
import {addQuestionMock, deleteQuestionMock, editQuestionMock} from "../backend/mutations/question.mock";
|
||||
import {getAddIconPath, getDeleteIconPath, getEditIconPath} from "./test-helper";
|
||||
import {
|
||||
expandAccordionAndGetIconButtons,
|
||||
queryAllAddIconButtons,
|
||||
queryAllEditIconButtons
|
||||
} from "./test-helper";
|
||||
|
||||
|
||||
describe('The QuestionList', () => {
|
||||
|
@ -120,7 +124,6 @@ describe('The QuestionList', () => {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
function renderQuestionList(additionalMocks?: Array<MockedResponse>) {
|
||||
const initialMocks = [...getAllQuestionsMock, ...getAllCategoriesMock];
|
||||
const allMocks = additionalMocks ? [...initialMocks, ...additionalMocks] : initialMocks
|
||||
|
@ -144,39 +147,3 @@ const waitForInitialQuestionsToRender = async (): Promise<Array<HTMLElement>> =>
|
|||
});
|
||||
return questionCards;
|
||||
}
|
||||
|
||||
// sorry, I found no better way to find a specific icon button...
|
||||
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 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 = queryAllDeleteIconButtons();
|
||||
let deleteIconsButtons = queryAllEditIconButtons();
|
||||
expect(editIconsButtons).toHaveLength(0);
|
||||
expect(deleteIconsButtons).toHaveLength(0);
|
||||
fireEvent.click(accordion);
|
||||
await waitFor(() => {
|
||||
editIconsButtons = queryAllEditIconButtons();
|
||||
deleteIconsButtons = queryAllDeleteIconButtons();
|
||||
expect(editIconsButtons).toHaveLength(1);
|
||||
expect(deleteIconsButtons).toHaveLength(1);
|
||||
})
|
||||
return {
|
||||
editIconButton: editIconsButtons[0],
|
||||
deleteIconButton: deleteIconsButtons[0]
|
||||
};
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import {render} from '@testing-library/react'
|
||||
import {fireEvent, queryAllByRole, render, screen, waitFor} from '@testing-library/react'
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import AddIcon from '@material-ui/icons/Add';
|
||||
|
@ -22,7 +22,42 @@ const memoizedGetIconPath = (icon: JSX.Element) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const getEditIconPath = memoizedGetIconPath(<EditIcon/>)
|
||||
export const getDeleteIconPath = memoizedGetIconPath(<DeleteIcon/>)
|
||||
export const getAddIconPath = memoizedGetIconPath(<AddIcon/>)
|
||||
const getEditIconPath = memoizedGetIconPath(<EditIcon/>)
|
||||
const getDeleteIconPath = memoizedGetIconPath(<DeleteIcon/>)
|
||||
const getAddIconPath = memoizedGetIconPath(<AddIcon/>)
|
||||
|
||||
// sorry, I found no better way to find a specific icon button...
|
||||
export 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 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...
|
||||
export const queryAllAddIconButtons = (container?: HTMLElement): Array<HTMLElement> => {
|
||||
return (container ? queryAllByRole(container, "button") : screen.queryAllByRole("button"))
|
||||
.filter(button => button.innerHTML.includes("svg") && button.innerHTML.includes(getAddIconPath()));
|
||||
}
|
||||
|
||||
export const expandAccordionAndGetIconButtons = async (accordion: HTMLElement): Promise<{ editIconButton: HTMLElement, deleteIconButton: HTMLElement }> => {
|
||||
let editIconsButtons = queryAllDeleteIconButtons();
|
||||
let deleteIconsButtons = queryAllEditIconButtons();
|
||||
expect(editIconsButtons).toHaveLength(0);
|
||||
expect(deleteIconsButtons).toHaveLength(0);
|
||||
fireEvent.click(accordion);
|
||||
await waitFor(() => {
|
||||
editIconsButtons = queryAllEditIconButtons();
|
||||
deleteIconsButtons = queryAllDeleteIconButtons();
|
||||
expect(editIconsButtons).toHaveLength(1);
|
||||
expect(deleteIconsButtons).toHaveLength(1);
|
||||
})
|
||||
return {
|
||||
editIconButton: editIconsButtons[0],
|
||||
deleteIconButton: deleteIconsButtons[0]
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue