diff --git a/src/components/Buttons/CloseButton.tsx b/src/components/Buttons/CloseButton.tsx new file mode 100644 index 0000000000..073d95552c --- /dev/null +++ b/src/components/Buttons/CloseButton.tsx @@ -0,0 +1,25 @@ +import { Close } from "@mui/icons-material"; +import { IconButton } from "@mui/material"; +import { CSSProperties, ReactElement } from "react"; + +interface CloseButtonProps { + close: () => void; +} + +export default function CloseButton(props: CloseButtonProps): ReactElement { + const closeButtonStyle: CSSProperties = { + position: "absolute", + top: 0, + ...(document.body.dir === "rtl" ? { left: 0 } : { right: 0 }), + }; + + return ( + + + + ); +} diff --git a/src/components/Buttons/DeleteButtonWithDialog.tsx b/src/components/Buttons/DeleteButtonWithDialog.tsx new file mode 100644 index 0000000000..472e1c1bb4 --- /dev/null +++ b/src/components/Buttons/DeleteButtonWithDialog.tsx @@ -0,0 +1,45 @@ +import { Delete } from "@mui/icons-material"; +import { ReactElement, useState } from "react"; + +import { IconButtonWithTooltip } from "components/Buttons"; +import { CancelConfirmDialog } from "components/Dialogs"; + +interface DeleteButtonWithDialogProps { + buttonId: string; + buttonIdCancel?: string; + buttonIdConfirm?: string; + delete: () => void | Promise; + disabled?: boolean; + textId: string; + tooltipTextId?: string; +} + +export default function DeleteButtonWithDialog( + props: DeleteButtonWithDialogProps +): ReactElement { + const [open, setOpen] = useState(false); + + const handleConfirm = async (): Promise => { + await props.delete(); + setOpen(false); + }; + + return ( + <> + } + onClick={props.disabled ? undefined : () => setOpen(true)} + textId={props.tooltipTextId || props.textId} + /> + setOpen(false)} + handleConfirm={handleConfirm} + open={open} + textId={props.textId} + /> + + ); +} diff --git a/src/components/Buttons/FlagButton.tsx b/src/components/Buttons/FlagButton.tsx index 5dd801c6d1..941b13c544 100644 --- a/src/components/Buttons/FlagButton.tsx +++ b/src/components/Buttons/FlagButton.tsx @@ -52,7 +52,7 @@ export default function FlagButton(props: FlagButtonProps): ReactElement { } text={text} textId={active ? "flags.edit" : "flags.add"} - small + size="small" onClick={ props.updateFlag ? () => setOpen(true) : active ? () => {} : undefined } diff --git a/src/components/Buttons/IconButtonWithTooltip.tsx b/src/components/Buttons/IconButtonWithTooltip.tsx index b717e8f467..51527916db 100644 --- a/src/components/Buttons/IconButtonWithTooltip.tsx +++ b/src/components/Buttons/IconButtonWithTooltip.tsx @@ -1,13 +1,13 @@ import { Tooltip, IconButton } from "@mui/material"; -import { ReactElement, ReactNode } from "react"; +import { MouseEventHandler, ReactElement, ReactNode } from "react"; import { useTranslation } from "react-i18next"; interface IconButtonWithTooltipProps { icon: ReactElement; text?: ReactNode; textId?: string; - small?: boolean; - onClick?: () => void; + size?: "large" | "medium" | "small"; + onClick?: MouseEventHandler; buttonId: string; side?: "bottom" | "left" | "right" | "top"; } @@ -25,7 +25,7 @@ export default function IconButtonWithTooltip( diff --git a/src/components/Buttons/PartOfSpeechButton.tsx b/src/components/Buttons/PartOfSpeechButton.tsx index 05db4aa1fb..b7eac606c9 100644 --- a/src/components/Buttons/PartOfSpeechButton.tsx +++ b/src/components/Buttons/PartOfSpeechButton.tsx @@ -38,7 +38,7 @@ export default function PartOfSpeech(props: PartOfSpeechProps): ReactElement { icon={} onClick={props.onClick} side="top" - small + size="small" text={hoverText} /> ); diff --git a/src/components/Buttons/index.ts b/src/components/Buttons/index.ts index 3889259e5f..4f50683ce9 100644 --- a/src/components/Buttons/index.ts +++ b/src/components/Buttons/index.ts @@ -1,3 +1,5 @@ +import CloseButton from "components/Buttons/CloseButton"; +import DeleteButtonWithDialog from "components/Buttons/DeleteButtonWithDialog"; import FileInputButton from "components/Buttons/FileInputButton"; import FlagButton from "components/Buttons/FlagButton"; import IconButtonWithTooltip from "components/Buttons/IconButtonWithTooltip"; @@ -7,6 +9,8 @@ import PartOfSpeechButton from "components/Buttons/PartOfSpeechButton"; import UndoButton from "components/Buttons/UndoButton"; export { + CloseButton, + DeleteButtonWithDialog, FileInputButton, FlagButton, IconButtonWithTooltip, diff --git a/src/components/Buttons/tests/DeleteButtonWithDialog.test.tsx b/src/components/Buttons/tests/DeleteButtonWithDialog.test.tsx new file mode 100644 index 0000000000..07213d5706 --- /dev/null +++ b/src/components/Buttons/tests/DeleteButtonWithDialog.test.tsx @@ -0,0 +1,85 @@ +import { ReactTestRenderer, act, create } from "react-test-renderer"; + +import "tests/reactI18nextMock"; + +import DeleteButtonWithDialog from "components/Buttons/DeleteButtonWithDialog"; +import { CancelConfirmDialog } from "components/Dialogs"; + +// Dialog uses portals, which are not supported in react-test-renderer. +jest.mock("@mui/material", () => { + const materialUiCore = jest.requireActual("@mui/material"); + return { + ...jest.requireActual("@mui/material"), + Dialog: materialUiCore.Container, + }; +}); + +const mockDelete = jest.fn(); +const buttonId = "button-id"; +const buttonIdCancel = "button-id-cancel"; +const buttonIdConfirm = "button-id-confirm"; +const textId = "text-id"; + +let cellHandle: ReactTestRenderer; + +const renderDeleteCell = async (): Promise => { + await act(async () => { + cellHandle = create( + + ); + }); +}; + +beforeEach(async () => { + jest.clearAllMocks(); + await renderDeleteCell(); +}); + +describe("DeleteCell", () => { + it("has working dialog buttons", async () => { + const dialog = cellHandle.root.findByType(CancelConfirmDialog); + const deleteButton = cellHandle.root.findByProps({ id: buttonId }); + + expect(dialog.props.open).toBeFalsy(); + await act(async () => { + deleteButton.props.onClick(); + }); + expect(dialog.props.open).toBeTruthy(); + const cancelButton = cellHandle.root.findByProps({ id: buttonIdCancel }); + await act(async () => { + cancelButton.props.onClick(); + }); + expect(dialog.props.open).toBeFalsy(); + await act(async () => { + deleteButton.props.onClick(); + }); + expect(dialog.props.open).toBeTruthy(); + const confButton = cellHandle.root.findByProps({ id: buttonIdConfirm }); + await act(async () => { + await confButton.props.onClick(); + }); + expect(dialog.props.open).toBeFalsy(); + }); + + it("only deletes after confirmation", async () => { + const deleteButton = cellHandle.root.findByProps({ id: buttonId }); + + await act(async () => { + deleteButton.props.onClick(); + cellHandle.root.findByProps({ id: buttonIdCancel }).props.onClick(); + deleteButton.props.onClick(); + }); + expect(mockDelete).not.toHaveBeenCalled(); + const confButton = cellHandle.root.findByProps({ id: buttonIdConfirm }); + await act(async () => { + await confButton.props.onClick(); + }); + expect(mockDelete).toHaveBeenCalled(); + }); +}); diff --git a/src/components/DataEntry/DataEntryTable/NewEntry/SenseDialog.tsx b/src/components/DataEntry/DataEntryTable/NewEntry/SenseDialog.tsx index dd800185ec..121cbff0f7 100644 --- a/src/components/DataEntry/DataEntryTable/NewEntry/SenseDialog.tsx +++ b/src/components/DataEntry/DataEntryTable/NewEntry/SenseDialog.tsx @@ -1,9 +1,7 @@ -import { Close } from "@mui/icons-material"; import { Dialog, DialogContent, Grid, - IconButton, MenuList, Typography, } from "@mui/material"; @@ -11,6 +9,7 @@ import { ReactElement } from "react"; import { useTranslation } from "react-i18next"; import { GramCatGroup, Sense, Word } from "api/models"; +import { CloseButton } from "components/Buttons"; import StyledMenuItem from "components/DataEntry/DataEntryTable/NewEntry/StyledMenuItem"; import { DomainCell, @@ -107,12 +106,7 @@ export function SenseList(props: SenseListProps): ReactElement { return ( <> {/* Cancel button */} - props.closeDialog()} - style={{ position: "absolute", right: 0, top: 0 }} - > - - + props.closeDialog()} /> {/* Header */} {t("addWords.selectSense")} {/* Sense options */} diff --git a/src/components/DataEntry/DataEntryTable/NewEntry/VernDialog.tsx b/src/components/DataEntry/DataEntryTable/NewEntry/VernDialog.tsx index 3baa246109..cfb5b89de0 100644 --- a/src/components/DataEntry/DataEntryTable/NewEntry/VernDialog.tsx +++ b/src/components/DataEntry/DataEntryTable/NewEntry/VernDialog.tsx @@ -1,9 +1,7 @@ -import { Close } from "@mui/icons-material"; import { Dialog, DialogContent, Grid, - IconButton, MenuList, Typography, } from "@mui/material"; @@ -11,6 +9,7 @@ import { ReactElement } from "react"; import { useTranslation } from "react-i18next"; import { GramCatGroup, Word } from "api/models"; +import { CloseButton } from "components/Buttons"; import StyledMenuItem from "components/DataEntry/DataEntryTable/NewEntry/StyledMenuItem"; import { DomainCell, @@ -108,12 +107,7 @@ export function VernList(props: VernListProps): ReactElement { return ( <> {/* Cancel button */} - props.closeDialog()} - style={{ position: "absolute", right: 0, top: 0 }} - > - - + props.closeDialog()} /> {/* Header */} {t("addWords.selectEntry")} {/* Entry options */} diff --git a/src/components/Dialogs/DeleteEditTextDialog.tsx b/src/components/Dialogs/DeleteEditTextDialog.tsx index afbc9c8a87..ab27fe933c 100644 --- a/src/components/Dialogs/DeleteEditTextDialog.tsx +++ b/src/components/Dialogs/DeleteEditTextDialog.tsx @@ -97,7 +97,7 @@ export default function DeleteEditTextDialog( diff --git a/src/components/Dialogs/UploadImage.tsx b/src/components/Dialogs/UploadImage.tsx new file mode 100644 index 0000000000..92c232f67f --- /dev/null +++ b/src/components/Dialogs/UploadImage.tsx @@ -0,0 +1,77 @@ +import { Grid, Typography } from "@mui/material"; +import { FormEvent, ReactElement, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { FileInputButton, LoadingDoneButton } from "components/Buttons"; + +interface ImageUploadProps { + doneCallback?: () => void; + uploadImage: (imgFile: File) => Promise; +} + +/** + * Allows the current user to select an image and upload it + */ +export default function ImageUpload(props: ImageUploadProps): ReactElement { + const [file, setFile] = useState(); + const [filename, setFilename] = useState(); + const [loading, setLoading] = useState(false); + const [done, setDone] = useState(false); + const { t } = useTranslation(); + + function updateFile(file: File): void { + if (file) { + setFile(file); + setFilename(file.name); + } + } + + async function upload(e: FormEvent): Promise { + e.preventDefault(); + e.stopPropagation(); + if (file) { + setLoading(true); + await props + .uploadImage(file) + .then(onDone) + .catch(() => setLoading(false)); + } + } + + async function onDone(): Promise { + setDone(true); + if (props.doneCallback) { + setTimeout(props.doneCallback, 500); + } + } + + return ( +
upload(e)}> + {/* Displays the name of the selected file */} + {filename && ( + + {t("createProject.fileSelected")}: {filename} + + )} + + + updateFile(file)} + accept="image/*" + > + {t("buttons.browse")} + + + + + {t("buttons.save")} + + + +
+ ); +} diff --git a/src/components/Dialogs/UploadImageDialog.tsx b/src/components/Dialogs/UploadImageDialog.tsx new file mode 100644 index 0000000000..226402b45d --- /dev/null +++ b/src/components/Dialogs/UploadImageDialog.tsx @@ -0,0 +1,30 @@ +import { Dialog, DialogContent, DialogTitle } from "@mui/material"; +import { ReactElement } from "react"; +import { useTranslation } from "react-i18next"; + +import UploadImage from "components/Dialogs/UploadImage"; + +interface UploadImageDialogProps { + close: () => void; + open: boolean; + titleId: string; + uploadImage: (imageFile: File) => Promise; +} + +export default function UploadImageDialog( + props: UploadImageDialogProps +): ReactElement { + const { t } = useTranslation(); + + return ( + + {t(props.titleId)} + + + + + ); +} diff --git a/src/components/Dialogs/index.ts b/src/components/Dialogs/index.ts index f501a59597..7913acbec1 100644 --- a/src/components/Dialogs/index.ts +++ b/src/components/Dialogs/index.ts @@ -2,10 +2,12 @@ import ButtonConfirmation from "components/Dialogs/ButtonConfirmation"; import CancelConfirmDialog from "components/Dialogs/CancelConfirmDialog"; import DeleteEditTextDialog from "components/Dialogs/DeleteEditTextDialog"; import EditTextDialog from "components/Dialogs/EditTextDialog"; +import UploadImageDialog from "components/Dialogs/UploadImageDialog"; export { ButtonConfirmation, CancelConfirmDialog, DeleteEditTextDialog, EditTextDialog, + UploadImageDialog, }; diff --git a/src/components/ProjectSettings/ProjectLanguages.tsx b/src/components/ProjectSettings/ProjectLanguages.tsx index 94466860b2..db6da1a401 100644 --- a/src/components/ProjectSettings/ProjectLanguages.tsx +++ b/src/components/ProjectSettings/ProjectLanguages.tsx @@ -115,14 +115,14 @@ export default function ProjectLanguages( } textId="projectSettings.language.makeDefaultAnalysisLanguage" - small + size="small" onClick={() => setNewAnalysisDefault(index)} buttonId={`analysis-language-${index}-bump`} /> } textId="projectSettings.language.deleteAnalysisLanguage" - small + size="small" onClick={() => deleteAnalysisWritingSystem(index)} buttonId={`analysis-language-${index}-delete`} /> @@ -282,8 +282,8 @@ export default function ProjectLanguages( ) : ( } - textId={"projectSettings.language.changeName"} - small + size="small" + textId="projectSettings.language.changeName" onClick={() => setChangeVernName(true)} buttonId={editVernacularNameButtonId} /> diff --git a/src/components/Pronunciations/AudioPlayer.tsx b/src/components/Pronunciations/AudioPlayer.tsx index a2a0dcbc99..7e52a549af 100644 --- a/src/components/Pronunciations/AudioPlayer.tsx +++ b/src/components/Pronunciations/AudioPlayer.tsx @@ -22,9 +22,10 @@ import { themeColors } from "types/theme"; interface PlayerProps { deleteAudio: (fileName: string) => void; fileName: string; - isPlaying?: boolean; onClick?: () => void; pronunciationUrl: string; + size?: "large" | "medium" | "small"; + warningTextId?: string; } const iconStyle: CSSProperties = { color: themeColors.success }; @@ -106,7 +107,7 @@ export default function AudioPlayer(props: PlayerProps): ReactElement { onTouchEnd={enableContextMenu} aria-label="play" id={`audio-${props.fileName}`} - size="large" + size={props.size || "large"} > {isPlaying ? : }
@@ -141,7 +142,7 @@ export default function AudioPlayer(props: PlayerProps): ReactElement { setDeleteConf(false)} onConfirm={() => props.deleteAudio(props.fileName)} diff --git a/src/components/UserSettings/ClickableAvatar.tsx b/src/components/UserSettings/ClickableAvatar.tsx index 62939421e4..6ba5ae9805 100644 --- a/src/components/UserSettings/ClickableAvatar.tsx +++ b/src/components/UserSettings/ClickableAvatar.tsx @@ -1,9 +1,10 @@ import { CameraAlt, Person } from "@mui/icons-material"; -import { Avatar, Dialog, DialogContent, DialogTitle } from "@mui/material"; +import { Avatar } from "@mui/material"; import { ReactElement, useState } from "react"; -import { getAvatar } from "backend/localStorage"; -import AvatarUpload from "components/UserSettings/AvatarUpload"; +import { uploadAvatar } from "backend"; +import { getAvatar, getUserId } from "backend/localStorage"; +import { UploadImageDialog } from "components/Dialogs"; const avatarStyle = { height: 60, width: 60 }; const avatarOverlayStyle = { @@ -48,12 +49,12 @@ export default function ClickableAvatar( - - Set user avatar - - - - + uploadAvatar(getUserId(), imgFile)} + /> ); } diff --git a/src/components/UserSettings/tests/AvatarUpload.test.tsx b/src/components/UserSettings/tests/AvatarUpload.test.tsx deleted file mode 100644 index 5bf8f4c4f9..0000000000 --- a/src/components/UserSettings/tests/AvatarUpload.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import renderer from "react-test-renderer"; - -import "tests/reactI18nextMock"; - -import AvatarUpload from "components/UserSettings/AvatarUpload"; - -let testRenderer: renderer.ReactTestRenderer; - -describe("AvatarUpload", () => { - it("renders", () => { - renderer.act(() => { - testRenderer = renderer.create(); - }); - expect(testRenderer.root.findAllByType(AvatarUpload)).toHaveLength(1); - }); -}); diff --git a/src/goals/MergeDuplicates/MergeDupsStep/MergeDragDrop/DropWord.tsx b/src/goals/MergeDuplicates/MergeDupsStep/MergeDragDrop/DropWord.tsx index 07aeddae21..ece2875cdf 100644 --- a/src/goals/MergeDuplicates/MergeDupsStep/MergeDragDrop/DropWord.tsx +++ b/src/goals/MergeDuplicates/MergeDupsStep/MergeDragDrop/DropWord.tsx @@ -90,9 +90,9 @@ export default function DropWord(props: DropWordProps): ReactElement { {protectedWithOneChild && ( } - textId={"mergeDups.helpText.protectedWord"} - side={"top"} - small + side="top" + size="small" + textId="mergeDups.helpText.protectedWord" buttonId={`word-${props.wordId}-protected`} /> )} diff --git a/src/goals/MergeDuplicates/MergeDupsStep/SenseCardContent.tsx b/src/goals/MergeDuplicates/MergeDupsStep/SenseCardContent.tsx index 5bf24d91b6..6b5d01c05d 100644 --- a/src/goals/MergeDuplicates/MergeDupsStep/SenseCardContent.tsx +++ b/src/goals/MergeDuplicates/MergeDupsStep/SenseCardContent.tsx @@ -50,9 +50,9 @@ export default function SenseCardContent( {protectedWarning && ( } - textId={"mergeDups.helpText.protectedSense"} - side={"top"} - small + side="top" + size="small" + textId="mergeDups.helpText.protectedSense" buttonId={`sense-${props.senses[0].guid}-protected`} /> )} diff --git a/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/DeleteCell.tsx b/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/DeleteCell.tsx index dce5f70664..de7c0a42bc 100644 --- a/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/DeleteCell.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/DeleteCell.tsx @@ -1,26 +1,21 @@ -import { Delete } from "@mui/icons-material"; -import { IconButton, Tooltip } from "@mui/material"; -import { ReactElement, useState } from "react"; -import { useTranslation } from "react-i18next"; +import { ReactElement } from "react"; import { deleteFrontierWord as deleteFromBackend } from "backend"; -import { CancelConfirmDialog } from "components/Dialogs"; +import { DeleteButtonWithDialog } from "components/Buttons"; import { deleteWord } from "goals/ReviewEntries/Redux/ReviewEntriesActions"; import { ReviewEntriesWord } from "goals/ReviewEntries/ReviewEntriesTypes"; import { useAppDispatch } from "types/hooks"; -export const buttonId = (wordId: string): string => `row-${wordId}-delete`; -export const buttonIdCancel = "delete-cancel"; -export const buttonIdConfirm = "delete-confirm"; +const buttonId = (wordId: string): string => `row-${wordId}-delete`; +const buttonIdCancel = "delete-cancel"; +const buttonIdConfirm = "delete-confirm"; interface DeleteCellProps { rowData: ReviewEntriesWord; } export default function DeleteCell(props: DeleteCellProps): ReactElement { - const [dialogOpen, setDialogOpen] = useState(false); const dispatch = useAppDispatch(); - const { t } = useTranslation(); const word = props.rowData; const disabled = word.protected || !!word.senses.find((s) => s.protected); @@ -28,41 +23,17 @@ export default function DeleteCell(props: DeleteCellProps): ReactElement { async function deleteFrontierWord(): Promise { await deleteFromBackend(word.id); dispatch(deleteWord(word.id)); - handleClose(); - } - - function handleOpen(): void { - setDialogOpen(true); - } - function handleClose(): void { - setDialogOpen(false); } return ( - <> - - - - - - - - - + ); } diff --git a/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/SenseCell.tsx b/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/SenseCell.tsx index b3b9fe5f24..b823fd07e2 100644 --- a/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/SenseCell.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/SenseCell.tsx @@ -1,8 +1,8 @@ import { Add, Delete, RestoreFromTrash } from "@mui/icons-material"; -import { Chip, IconButton, Tooltip } from "@mui/material"; +import { Chip } from "@mui/material"; import { ReactElement } from "react"; -import { useTranslation } from "react-i18next"; +import { IconButtonWithTooltip } from "components/Buttons"; import { FieldParameterStandard } from "goals/ReviewEntries/ReviewEntriesTable/CellColumns"; import AlignedList from "goals/ReviewEntries/ReviewEntriesTable/CellComponents/AlignedList"; import { ReviewEntriesSense } from "goals/ReviewEntries/ReviewEntriesTypes"; @@ -12,8 +12,6 @@ interface SenseCellProps extends FieldParameterStandard { } export default function SenseCell(props: SenseCellProps): ReactElement { - const { t } = useTranslation(); - function addSense(): ReactElement { const senses = [...props.rowData.senses, new ReviewEntriesSense()]; return ( @@ -32,22 +30,16 @@ export default function SenseCell(props: SenseCellProps): ReactElement { ( - : } key={sense.guid} - > - - props.delete!(sense.guid)} - id={`sense-${sense.guid}-delete`} - disabled={sense.protected} - > - {sense.deleted ? : } - - - + onClick={ + sense.protected ? undefined : () => props.delete!(sense.guid) + } + size="small" + textId={sense.protected ? "reviewEntries.deleteDisabled" : undefined} + /> ))} bottomCell={addSense()} /> diff --git a/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/tests/DeleteCell.test.tsx b/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/tests/DeleteCell.test.tsx index ad471603da..80fd1e707b 100644 --- a/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/tests/DeleteCell.test.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesTable/CellComponents/tests/DeleteCell.test.tsx @@ -4,13 +4,9 @@ import configureMockStore from "redux-mock-store"; import "tests/reactI18nextMock"; -import { CancelConfirmDialog } from "components/Dialogs"; +import { DeleteButtonWithDialog } from "components/Buttons"; import { defaultState as reviewEntriesState } from "goals/ReviewEntries/Redux/ReviewEntriesReduxTypes"; -import DeleteCell, { - buttonId, - buttonIdCancel, - buttonIdConfirm, -} from "goals/ReviewEntries/ReviewEntriesTable/CellComponents/DeleteCell"; +import DeleteCell from "goals/ReviewEntries/ReviewEntriesTable/CellComponents/DeleteCell"; import mockWords from "goals/ReviewEntries/tests/WordsMock"; // Dialog uses portals, which are not supported in react-test-renderer. @@ -23,7 +19,7 @@ jest.mock("@mui/material", () => { }); jest.mock("backend", () => ({ - deleteFrontierWord: () => mockDeleteFrontierWord(), + deleteFrontierWord: () => jest.fn(), })); jest.mock("types/hooks", () => { return { @@ -35,11 +31,8 @@ jest.mock("types/hooks", () => { }; }); -const mockDeleteFrontierWord = jest.fn(); - const mockStore = configureMockStore()({ reviewEntriesState }); const mockWord = mockWords()[0]; -const buttonIdDelete = buttonId(mockWord.id); let cellHandle: ReactTestRenderer; @@ -59,45 +52,7 @@ beforeEach(async () => { }); describe("DeleteCell", () => { - it("has working dialog buttons", async () => { - const dialog = cellHandle.root.findByType(CancelConfirmDialog); - const deleteButton = cellHandle.root.findByProps({ id: buttonIdDelete }); - const cancelButton = cellHandle.root.findByProps({ id: buttonIdCancel }); - const confButton = cellHandle.root.findByProps({ id: buttonIdConfirm }); - - expect(dialog.props.open).toBeFalsy(); - await act(async () => { - deleteButton.props.onClick(); - }); - expect(dialog.props.open).toBeTruthy(); - await act(async () => { - cancelButton.props.onClick(); - }); - expect(dialog.props.open).toBeFalsy(); - await act(async () => { - deleteButton.props.onClick(); - }); - expect(dialog.props.open).toBeTruthy(); - await act(async () => { - await confButton.props.onClick(); - }); - expect(dialog.props.open).toBeFalsy(); - }); - - it("only deletes after confirmation", async () => { - const deleteButton = cellHandle.root.findByProps({ id: buttonIdDelete }); - const cancelButton = cellHandle.root.findByProps({ id: buttonIdCancel }); - const confButton = cellHandle.root.findByProps({ id: buttonIdConfirm }); - - await act(async () => { - deleteButton.props.onClick(); - cancelButton.props.onClick(); - deleteButton.props.onClick(); - }); - expect(mockDeleteFrontierWord).not.toBeCalled(); - await act(async () => { - await confButton.props.onClick(); - }); - expect(mockDeleteFrontierWord).toBeCalled(); + it("renders", () => { + cellHandle.root.findByType(DeleteButtonWithDialog); }); });