From b44187d62d7136b3b1332f5479b6598aa212f83d Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Mon, 11 Apr 2022 22:41:45 +0200 Subject: [PATCH 01/11] chore: migrate to user-event and latest testing-lib Also fixes a few bugs uncovered whilst doing that upgrade where e.preventDefault() wasn't called on events from Button elements. --- package-lock.json | 175 ++++++++++++---- package.json | 3 +- src/components/file/index.test.tsx | 54 ++--- src/components/file/index.tsx | 5 +- src/components/image/index.test.tsx | 189 ++++++++++-------- src/components/image/index.tsx | 4 +- src/components/logIn/index.test.tsx | 58 ++++-- src/components/logIn/index.tsx | 2 + src/components/logOut/index.test.tsx | 59 ++++-- src/components/logOut/index.tsx | 2 + src/components/table/index.test.tsx | 34 +++- src/components/text/index.test.tsx | 101 +++++++--- src/components/value/boolean/index.test.tsx | 91 ++++++--- src/components/value/datetime/index.test.tsx | 136 +++++++++---- src/components/value/decimal/index.test.tsx | 98 ++++++--- src/components/value/integer/index.test.tsx | 100 ++++++--- src/components/value/string/index.test.tsx | 117 ++++++++--- src/components/value/url/index.test.tsx | 102 +++++++--- src/components/video/index.test.tsx | 95 ++++----- src/context/sessionContext/index.test.tsx | 38 ++-- src/hooks/useDataset/index.test.tsx | 39 ++-- .../sessionProvider.stories.tsx | 56 +++--- 22 files changed, 1048 insertions(+), 510 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7474be2d..a5d6f6cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,8 +32,9 @@ "@storybook/manager-webpack5": "^6.3.12", "@storybook/preset-typescript": "^3.0.0", "@storybook/react": "^6.4.0-beta.19", - "@testing-library/react": "^11.2.6", + "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", + "@testing-library/user-event": "^14.1.0", "@types/jest": "^27.4.0", "@types/react": "^17.0.31", "@types/react-table": "^7.7.7", @@ -12187,22 +12188,22 @@ } }, "node_modules/@testing-library/dom": { - "version": "7.31.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.0.tgz", - "integrity": "sha512-0X7ACg4YvTRDFMIuTOEj6B4NpN7i3F/4j5igOcTI5NC5J+N4TribNdErCHOZF1LBWhhcyfwxelVwvoYNMUXTOA==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.13.0.tgz", + "integrity": "sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", + "aria-query": "^5.0.0", "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.4", + "dom-accessibility-api": "^0.5.9", "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" + "pretty-format": "^27.0.2" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/@testing-library/dom/node_modules/ansi-styles": { @@ -12215,12 +12216,24 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true, + "engines": { + "node": ">=6.0" } }, "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -12228,6 +12241,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/@testing-library/dom/node_modules/color-convert": { @@ -12257,6 +12273,32 @@ "node": ">=8" } }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@testing-library/dom/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12270,16 +12312,21 @@ } }, "node_modules/@testing-library/react": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz", - "integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==", + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", + "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^7.28.1" + "@testing-library/dom": "^8.0.0", + "@types/react-dom": "<18.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" + }, + "peerDependencies": { + "react": "<18.0.0", + "react-dom": "<18.0.0" } }, "node_modules/@testing-library/react-hooks": { @@ -12298,6 +12345,19 @@ "node": ">=12" } }, + "node_modules/@testing-library/user-event": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.1.0.tgz", + "integrity": "sha512-+CGfMXlVM+OwREHDEsfTGsXIMI+rjr3a7YBUSutq7soELht+8kQrM5k46xa/WLfHdtX/wqsDIleL6bi4i+xz0w==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -12314,9 +12374,9 @@ "dev": true }, "node_modules/@types/aria-query": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz", - "integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", "dev": true }, "node_modules/@types/babel__core": { @@ -17012,9 +17072,9 @@ } }, "node_modules/dom-accessibility-api": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz", + "integrity": "sha512-R305kwb5CcMDIpSHUnLyIAp7SrSPBx6F0VfQFB3M75xVMHhXJJIdePYgbPPh1o57vCHNu5QztokWUPsLjWzFqw==", "dev": true }, "node_modules/dom-converter": { @@ -40546,19 +40606,19 @@ } }, "@testing-library/dom": { - "version": "7.31.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.0.tgz", - "integrity": "sha512-0X7ACg4YvTRDFMIuTOEj6B4NpN7i3F/4j5igOcTI5NC5J+N4TribNdErCHOZF1LBWhhcyfwxelVwvoYNMUXTOA==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.13.0.tgz", + "integrity": "sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", + "aria-query": "^5.0.0", "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.4", + "dom-accessibility-api": "^0.5.9", "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" + "pretty-format": "^27.0.2" }, "dependencies": { "ansi-styles": { @@ -40570,10 +40630,16 @@ "color-convert": "^2.0.1" } }, + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true + }, "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -40601,6 +40667,25 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -40613,13 +40698,14 @@ } }, "@testing-library/react": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz", - "integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==", + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", + "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^7.28.1" + "@testing-library/dom": "^8.0.0", + "@types/react-dom": "<18.0.0" } }, "@testing-library/react-hooks": { @@ -40635,6 +40721,13 @@ "react-error-boundary": "^3.1.0" } }, + "@testing-library/user-event": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.1.0.tgz", + "integrity": "sha512-+CGfMXlVM+OwREHDEsfTGsXIMI+rjr3a7YBUSutq7soELht+8kQrM5k46xa/WLfHdtX/wqsDIleL6bi4i+xz0w==", + "dev": true, + "requires": {} + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -40648,9 +40741,9 @@ "dev": true }, "@types/aria-query": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz", - "integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", "dev": true }, "@types/babel__core": { @@ -44711,9 +44804,9 @@ } }, "dom-accessibility-api": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz", + "integrity": "sha512-R305kwb5CcMDIpSHUnLyIAp7SrSPBx6F0VfQFB3M75xVMHhXJJIdePYgbPPh1o57vCHNu5QztokWUPsLjWzFqw==", "dev": true }, "dom-converter": { diff --git a/package.json b/package.json index e2c6a24d..848b8bdf 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,9 @@ "@storybook/manager-webpack5": "^6.3.12", "@storybook/preset-typescript": "^3.0.0", "@storybook/react": "^6.4.0-beta.19", - "@testing-library/react": "^11.2.6", + "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", + "@testing-library/user-event": "^14.1.0", "@types/jest": "^27.4.0", "@types/react": "^17.0.31", "@types/react-table": "^7.7.7", diff --git a/src/components/file/index.test.tsx b/src/components/file/index.test.tsx index d592f552..7b14b6c7 100644 --- a/src/components/file/index.test.tsx +++ b/src/components/file/index.test.tsx @@ -21,7 +21,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; -import { render, fireEvent, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import * as SolidFns from "@inrupt/solid-client"; import { FileUpload } from "./index"; @@ -30,17 +32,6 @@ const inputOptions = { type: "url", }; -/** The File interface provides information about files and allows JavaScript in a web page to access their content. */ -interface File extends Blob { - readonly lastModified: number; - readonly name: string; -} - -declare let File: { - prototype: File; - new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; -}; - const savedDataset = SolidFns.createSolidDataset() as any; jest .spyOn(SolidFns, "saveSolidDatasetAt") @@ -72,11 +63,11 @@ describe(" component functional testing", () => { const onSave = jest.fn(); const onError = jest.fn(); + const user = userEvent.setup(); + const file = new File(["foo"], "foo.txt", { type: "text/plain", }); - // spoof FileList to match input. - const fileList: Array = [file]; const { getByTestId } = render( component functional testing", () => { /> ); const input = getByTestId("form-input"); - fireEvent.change(input, { target: { files: fileList } }); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + await user.upload(input, [file]); + + expect(onSave).toHaveBeenCalled(); }); - it("Should call onSave if it is passed with a Blob", async () => { + // We cannot currently test this because testing-library's upload does not accept Blobs. + // see: https://github.com/testing-library/user-event/issues/923 + // eslint-disable-next-line jest/no-disabled-tests + it.skip("Should call onSave if it is passed with a Blob", async () => { jest .spyOn(SolidFns, "saveFileInContainer") .mockResolvedValueOnce( @@ -103,11 +98,11 @@ describe(" component functional testing", () => { const onSave = jest.fn(); const onError = jest.fn(); - const blob = new Blob(["foo"], { - type: "text/plain", - }); + // const user = userEvent.setup(); - const fileList: Array = [blob]; + // const blob = new Blob(["foo"], { + // type: "text/plain", + // }); const { getByTestId } = render( component functional testing", () => { /> ); const input = getByTestId("form-input"); - fireEvent.change(input, { target: { files: fileList } }); + // FIXME: Support blobs: + // await user.upload(input, [blob]); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + expect(onSave).toHaveBeenCalled(); }); it("Should call onError if saving file to custom location fails", async () => { @@ -128,6 +124,8 @@ describe(" component functional testing", () => { const onError = jest.fn(); const onSave = jest.fn(); + const user = userEvent.setup(); + const file = new File(["foo"], "foo.txt", { type: "text/plain", }); @@ -142,9 +140,9 @@ describe(" component functional testing", () => { ); const input = getByTestId("form-input"); - fireEvent.change(input, { target: { files: file } }); + await user.upload(input, file); - await waitFor(() => expect(onError).toHaveBeenCalled()); + expect(onError).toHaveBeenCalled(); }); it("Should not call saveFileInContainer if autosave is not true", async () => { @@ -152,6 +150,8 @@ describe(" component functional testing", () => { const onError = jest.fn(); const onSave = jest.fn(); + const user = userEvent.setup(); + const file = new File(["foo"], "foo.txt", { type: "text/plain", }); @@ -165,7 +165,9 @@ describe(" component functional testing", () => { ); const input = getByTestId("form-input"); - fireEvent.change(input, { target: { files: file } }); + + await user.upload(input, file); + expect(SolidFns.saveFileInContainer).toHaveBeenCalledTimes(0); }); }); diff --git a/src/components/file/index.tsx b/src/components/file/index.tsx index 18a557f4..c142f3cf 100644 --- a/src/components/file/index.tsx +++ b/src/components/file/index.tsx @@ -30,7 +30,7 @@ import { SessionContext } from "../../context/sessionContext"; export type Props = { saveLocation: Url | UrlString; - inputProps?: React.InputHTMLAttributes; + inputProps?: Omit, "multiple">; autosave?: boolean; onSave?: (savedFile?: File & WithResourceInfo) => void; onError?: (error: Error) => void; @@ -47,9 +47,12 @@ export function FileUpload({ const handleChange = async (e: React.FormEvent) => { const target = e.target as HTMLInputElement; + + // This is a typescript bug, as target.files should always be a FileList: if (!target.files) { return; } + if (!autosave) { return; } diff --git a/src/components/image/index.test.tsx b/src/components/image/index.test.tsx index 1a0fc0b0..b128a978 100644 --- a/src/components/image/index.test.tsx +++ b/src/components/image/index.test.tsx @@ -20,7 +20,9 @@ */ import React from "react"; -import { render, waitFor, fireEvent } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import * as SolidFns from "@inrupt/solid-client"; import type { SolidDataset, @@ -30,23 +32,38 @@ import type { import { Image } from "."; import * as helpers from "../../helpers"; -const mockAlt = "test img"; -const mockUrl = "http://test.url/image.png"; -const mockProperty = `http://www.w3.org/2006/vcard/ns#hasPhoto`; -const mockThing = SolidFns.addUrl( - SolidFns.createThing(), - mockProperty, - mockUrl -); -const mockThingWithoutPhoto = SolidFns.createThing(); -const datasetIri = "https://example.org/dataset/"; -const mockDataset = SolidFns.mockSolidDatasetFrom(datasetIri); - -const mockObjectUrl = "mock object url"; -const mockFile = SolidFns.mockFileFrom(mockUrl); -window.URL.createObjectURL = jest.fn(() => mockObjectUrl); - describe("Image component", () => { + let mockAlt: string; + let mockUrl: string; + let mockProperty: string; + let mockThing: SolidFns.ThingLocal; + let mockThingWithoutPhoto: SolidFns.ThingLocal; + let datasetIri: string; + let mockDataset: SolidFns.SolidDataset; + let mockObjectUrl: string; + // Cannot correctly set type due to the return type of mockFileFrom not being typed strictly + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let mockFile: any; + let mockFileForUpload: File; + + beforeEach(() => { + mockAlt = "test img"; + mockUrl = "http://test.url/image.png"; + mockProperty = `http://www.w3.org/2006/vcard/ns#hasPhoto`; + mockThing = SolidFns.addUrl(SolidFns.createThing(), mockProperty, mockUrl); + mockThingWithoutPhoto = SolidFns.createThing(); + datasetIri = "https://example.org/dataset/"; + mockDataset = SolidFns.mockSolidDatasetFrom(datasetIri); + + mockObjectUrl = "mock object url"; + mockFile = SolidFns.mockFileFrom(mockUrl); + mockFileForUpload = new File(["aaaaaa"], "picture.png", { + type: "image/png", + }); + + window.URL.createObjectURL = jest.fn(() => mockObjectUrl); + }); + describe("Image snapshots", () => { it("matches snapshot with standard props", async () => { jest.spyOn(SolidFns, "getUrl").mockImplementationOnce(() => mockUrl); @@ -244,6 +261,8 @@ describe("Image component", () => { }); it("Should not call overwriteFile on change if autosave is false", async () => { + const user = userEvent.setup(); + const { getByAltText } = render( { await waitFor(() => expect(getByAltText(mockAlt).getAttribute("src")).toBe(mockObjectUrl) ); - fireEvent.change(getByAltText("test-input"), { - target: { - files: [mockFile], - }, - }); + + await user.upload(getByAltText("test-input"), mockFileForUpload); + expect(SolidFns.overwriteFile).not.toHaveBeenCalled(); }); it("Should call overwriteFile on change if autosave is true", async () => { + (SolidFns.overwriteFile as jest.Mock).mockResolvedValue({}); + + const user = userEvent.setup(); + const mockUpdatedObjectUrl = "updated mock object url"; const { getByAltText } = render( { await waitFor(() => expect(getByAltText(mockAlt).getAttribute("src")).toBe(mockObjectUrl) ); - fireEvent.change(getByAltText("test-input"), { - target: { - files: [mockFile], - }, - }); - (window.URL.createObjectURL as jest.Mock).mockReturnValueOnce( + + // Mock the return value for the new src attribute: + (window.URL.createObjectURL as jest.Mock).mockReturnValue( mockUpdatedObjectUrl ); - await waitFor(() => - expect(getByAltText(mockAlt).getAttribute("src")).toBe( - mockUpdatedObjectUrl - ) + + await user.upload(getByAltText("test-input"), mockFileForUpload); + + expect(SolidFns.overwriteFile).toHaveBeenCalledWith( + mockUrl, + mockFileForUpload, + expect.anything() + ); + + expect(getByAltText(mockAlt).getAttribute("src")).toBe( + mockUpdatedObjectUrl ); - expect(SolidFns.overwriteFile).toHaveBeenCalled(); }); + it("Should call saveFileInContainer and update dataset on change if value is not available and saveLocation is passed", async () => { + const user = userEvent.setup(); + const saveLocation = "https://example.org/container/"; jest.spyOn(SolidFns, "getUrl").mockImplementationOnce(() => null); jest @@ -322,33 +349,36 @@ describe("Image component", () => { const input = getByAltText("test-input"); expect(input).not.toBeNull(); }); - fireEvent.change(getByAltText("test-input"), { - target: { - files: [mockFile], - }, - }); + + // Mock the new url for the image: (window.URL.createObjectURL as jest.Mock).mockReturnValueOnce( mockUpdatedObjectUrl ); + + await user.upload(getByAltText("test-input"), mockFileForUpload); + expect(SolidFns.saveFileInContainer).toHaveBeenCalledWith( saveLocation, - mockFile, + mockFileForUpload, + { fetch: expect.any(Function) } + ); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalledWith( + datasetIri, + expect.anything(), { fetch: expect.any(Function) } ); - await waitFor(() => { - expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalledWith( - datasetIri, - expect.anything(), - { fetch: expect.any(Function) } - ); - }); }); + it("Should call saveSolidDatasetAt when clicking delete button", async () => { + const user = userEvent.setup(); + jest .spyOn(SolidFns, "saveSolidDatasetAt") .mockResolvedValue( mockDataset as SolidDataset & WithServerResourceInfo & WithChangeLog ); + const { getByAltText, getByText } = render( { await waitFor(() => expect(getByAltText(mockAlt).getAttribute("src")).toBe(mockObjectUrl) ); - const deleteButton = getByText("Delete"); - fireEvent.click(deleteButton); - await waitFor(() => { - expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); - }); + + await user.click(getByText("Delete")); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); }); - test.skip("Should not call overwriteFile on change if file size > maxSize", async () => { + it("Should not call overwriteFile on change if file size > maxSize", async () => { + const user = userEvent.setup(); + const { getByAltText } = render( { await waitFor(() => expect(getByAltText(mockAlt).getAttribute("src")).toBe(mockObjectUrl) ); - fireEvent.change(getByAltText("test-input"), { - target: { - files: [mockFile], - }, - }); + + await user.upload(getByAltText("test-input"), mockFileForUpload); + expect(SolidFns.overwriteFile).not.toHaveBeenCalled(); }); it("Should call onSave after successful overwrite, if it is passed", async () => { const mockUpdatedObjectUrl = "updated mock object url"; const mockOnSave = jest.fn(); + + const user = userEvent.setup(); const { getByAltText } = render( { await waitFor(() => expect(getByAltText(mockAlt).getAttribute("src")).toBe(mockObjectUrl) ); - fireEvent.change(getByAltText("test-input"), { - target: { - files: [mockFile], - }, - }); + + // Mock the new src url for the image: (window.URL.createObjectURL as jest.Mock).mockReturnValueOnce( mockUpdatedObjectUrl ); - await waitFor(() => - expect(getByAltText(mockAlt).getAttribute("src")).toBe( - mockUpdatedObjectUrl - ) + + await user.upload(getByAltText("test-input"), mockFileForUpload); + + expect(getByAltText(mockAlt).getAttribute("src")).toBe( + mockUpdatedObjectUrl ); + expect(mockOnSave).toHaveBeenCalled(); }); it("Should not fetch updated image if overwriteFile fails", async () => { (SolidFns.overwriteFile as jest.Mock).mockRejectedValueOnce(null); + + const user = userEvent.setup(); const { getByAltText } = render( { await waitFor(() => expect(getByAltText(mockAlt).getAttribute("src")).toBe(mockObjectUrl) ); - fireEvent.change(getByAltText("test-input"), { - target: { - files: [mockFile], - }, - }); - await waitFor(() => - expect(SolidFns.overwriteFile).toHaveBeenCalledTimes(1) - ); - await waitFor(() => expect(SolidFns.getFile).toHaveBeenCalledTimes(1)); + await user.upload(getByAltText("test-input"), mockFileForUpload); + + expect(SolidFns.overwriteFile).toHaveBeenCalledTimes(1); + expect(SolidFns.getFile).toHaveBeenCalledTimes(1); }); it("Should call onError if overwriteFile fails, if it is passed", async () => { const mockOnError = jest.fn(); (SolidFns.overwriteFile as jest.Mock).mockRejectedValueOnce(null); + + const user = userEvent.setup(); const { getByAltText } = render( { await waitFor(() => expect(getByAltText(mockAlt).getAttribute("src")).toBe(mockObjectUrl) ); - fireEvent.change(getByAltText("test-input"), { - target: { - files: [mockFile], - }, - }); - await waitFor(() => expect(mockOnError).toHaveBeenCalled()); + + await user.upload(getByAltText("test-input"), mockFileForUpload); + + expect(mockOnError).toHaveBeenCalled(); }); }); }); diff --git a/src/components/image/index.tsx b/src/components/image/index.tsx index 17489de4..175b198d 100644 --- a/src/components/image/index.tsx +++ b/src/components/image/index.tsx @@ -124,8 +124,10 @@ export function Image({ !propProperty || typeof value !== "string" || !autosave - ) + ) { return; + } + try { const updatedThing = removeUrl(propThing, propProperty, value); const updatedDataset = setThing(solidDataset, updatedThing); diff --git a/src/components/logIn/index.test.tsx b/src/components/logIn/index.test.tsx index 8ed8ce8f..725f2185 100644 --- a/src/components/logIn/index.test.tsx +++ b/src/components/logIn/index.test.tsx @@ -20,7 +20,8 @@ */ import * as React from "react"; -import { render, fireEvent, waitFor } from "@testing-library/react"; +import { render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { login, getDefaultSession, @@ -40,6 +41,8 @@ const session = { } as any; beforeEach(() => { + jest.resetAllMocks(); + (getDefaultSession as jest.Mock).mockReturnValue(session); (handleIncomingRedirect as jest.Mock).mockResolvedValueOnce(null); }); @@ -85,6 +88,8 @@ describe(" component functional testing", () => { const oidcIssuer = "https://test.url"; const redirectUrl = "https://local.url/redirect"; + const user = userEvent.setup(); + const { getByText } = render( component functional testing", () => { ); - fireEvent.click(getByText("Log In")); - await waitFor(() => - expect(login).toHaveBeenCalledWith({ - oidcIssuer, - redirectUrl, - }) - ); + await user.click(getByText("Log In")); + + expect(login).toHaveBeenCalledWith({ + oidcIssuer, + redirectUrl, + }); }); it("fires the onKeyPress function if enter is pressed", async () => { const oidcIssuer = "https://test.url"; const redirectUrl = "https://local.url/redirect"; + const user = userEvent.setup(); + const { getByText } = render( component functional testing", () => { ); - fireEvent.keyDown(getByText("Log In"), { key: "Enter", code: "Enter" }); + getByText("Log In").focus(); - await waitFor(() => - expect(login).toHaveBeenCalledWith({ - oidcIssuer, - redirectUrl, - }) - ); + await user.keyboard("{Enter}"); + + expect(login).toHaveBeenCalledWith({ + oidcIssuer, + redirectUrl, + }); }); it("does not fire the onKeyPress function if a non-enter button is pressed", async () => { const oidcIssuer = "https://test.url"; const redirectUrl = "https://local.url/redirect"; + const user = userEvent.setup(); + const { getByText } = render( component functional testing", () => { ); - fireEvent.keyDown(getByText("Log In"), { key: "A", code: "A" }); + getByText("Log In").focus(); + + await user.keyboard("A"); + expect(login).not.toHaveBeenCalled(); }); it("fires the onClick function and calls OnError", async () => { (login as jest.Mock).mockRejectedValue(null); + const user = userEvent.setup(); + const { getByText } = render( component functional testing", () => { /> ); - fireEvent.click(getByText("Log In")); - await waitFor(() => expect(onError).toHaveBeenCalledTimes(1)); + await user.click(getByText("Log In")); + + expect(onError).toHaveBeenCalledTimes(1); }); it("fires the onClick function and doesn't call OnError if it wasn't provided", async () => { (login as jest.Mock).mockRejectedValue(null); + const user = userEvent.setup(); + const { getByText } = render( component functional testing", () => { /> ); - fireEvent.click(getByText("Log In")); - await waitFor(() => expect(onError).toHaveBeenCalledTimes(0)); + + await user.click(getByText("Log In")); + + expect(onError).toHaveBeenCalledTimes(0); }); }); diff --git a/src/components/logIn/index.tsx b/src/components/logIn/index.tsx index aa2251f9..f9d16547 100644 --- a/src/components/logIn/index.tsx +++ b/src/components/logIn/index.tsx @@ -63,6 +63,8 @@ export const LoginButton: React.FC = (propsLogin: Props) => { function keyDownHandler( e: React.KeyboardEvent ): Promise { + e.preventDefault(); + return e.key === "Enter" ? loginHandler() : Promise.resolve(); } diff --git a/src/components/logOut/index.test.tsx b/src/components/logOut/index.test.tsx index eb79121f..8aa96c79 100644 --- a/src/components/logOut/index.test.tsx +++ b/src/components/logOut/index.test.tsx @@ -20,7 +20,8 @@ */ import * as React from "react"; -import { render, fireEvent, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { logout, @@ -42,6 +43,8 @@ const session = { } as any; beforeEach(() => { + jest.resetAllMocks(); + (getDefaultSession as jest.Mock).mockReturnValue(session); (handleIncomingRedirect as jest.Mock).mockResolvedValueOnce(null); }); @@ -75,76 +78,100 @@ describe(" component visual testing", () => { describe(" component functional testing", () => { it("fires the onClick function and calls onLogout", async () => { + const user = userEvent.setup(); + const { getByText } = render( ); - fireEvent.click(getByText("Log Out")); + await user.click(getByText("Log Out")); - await waitFor(() => expect(logout).toHaveBeenCalledTimes(1)); - await waitFor(() => expect(onLogout).toHaveBeenCalledTimes(1)); + expect(logout).toHaveBeenCalledTimes(1); + expect(onLogout).toHaveBeenCalledTimes(1); }); it("fires the onKeyPress function if enter is pressed", async () => { + const user = userEvent.setup(); + const { getByText } = render( ); - fireEvent.keyDown(getByText("Log Out"), { key: "Enter", code: "Enter" }); - await waitFor(() => expect(logout).toHaveBeenCalledTimes(1)); - await waitFor(() => expect(onLogout).toHaveBeenCalledTimes(1)); + // Focus the button + getByText("Log Out").focus(); + + await user.keyboard("{Enter}"); + + expect(logout).toHaveBeenCalledTimes(1); + expect(onLogout).toHaveBeenCalledTimes(1); }); it("does not fire the onKeyPress function if a non-enter button is pressed", async () => { + const user = userEvent.setup(); + const { getByText } = render( ); - fireEvent.keyDown(getByText("Log Out"), { key: "A", code: "A" }); + // First focus the button, then press enter: + getByText("Log Out").focus(); + + await user.keyboard("A"); + + expect(onLogout).not.toHaveBeenCalled(); expect(logout).not.toHaveBeenCalled(); }); it("fires on click and doesn't pass onLogout", async () => { + const user = userEvent.setup(); + const { getByText } = render( ); - fireEvent.click(getByText("Log Out")); - await waitFor(() => expect(logout).toHaveBeenCalledTimes(1)); - await waitFor(() => expect(onLogout).toHaveBeenCalledTimes(0)); + await user.click(getByText("Log Out")); + + expect(logout).toHaveBeenCalledTimes(1); + expect(onLogout).toHaveBeenCalledTimes(0); }); it("fires the onClick function and calls OnError", async () => { (logout as jest.Mock).mockRejectedValue(null); + const user = userEvent.setup(); + const { getByText } = render( ); - fireEvent.click(getByText("Log Out")); - await waitFor(() => expect(logout).toHaveBeenCalledTimes(1)); - await waitFor(() => expect(onError).toHaveBeenCalledTimes(1)); + await user.click(getByText("Log Out")); + + expect(logout).toHaveBeenCalledTimes(1); + expect(onError).toHaveBeenCalledTimes(1); }); it("fires the onClick function and doesn't call OnError if it wasn't provided", async () => { (logout as jest.Mock).mockRejectedValue(null); + const user = userEvent.setup(); + const { getByText } = render( ); - fireEvent.click(getByText("Log Out")); - await waitFor(() => expect(onError).toHaveBeenCalledTimes(0)); + await user.click(getByText("Log Out")); + + expect(onError).toHaveBeenCalledTimes(0); }); }); diff --git a/src/components/logOut/index.tsx b/src/components/logOut/index.tsx index 01a979f3..025172f6 100644 --- a/src/components/logOut/index.tsx +++ b/src/components/logOut/index.tsx @@ -47,6 +47,8 @@ export const LogoutButton: React.FC = (propsLogout: Props) => { function keyDownHandler( e: React.KeyboardEvent ): Promise { + e.preventDefault(); + return e.key === "Enter" ? logoutHandler() : Promise.resolve(); } diff --git a/src/components/table/index.test.tsx b/src/components/table/index.test.tsx index 0647a91e..7ecd03b0 100644 --- a/src/components/table/index.test.tsx +++ b/src/components/table/index.test.tsx @@ -21,7 +21,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; -import { render, fireEvent, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import { ErrorBoundary } from "react-error-boundary"; import * as SolidFns from "@inrupt/solid-client"; import { Table, TableColumn } from "./index"; @@ -127,7 +129,8 @@ describe(" component functional tests", () => { expect(queryByText(namePredicate)).toBeNull(); }); - it("does not sort columns without sortable prop", () => { + it("does not sort columns without sortable prop", async () => { + const user = userEvent.setup(); const { getByText, queryByText } = render(
component functional tests", () => {
); - fireEvent.click(getByText(namePredicate)); + + await user.click(getByText(namePredicate)); + expect(queryByText("🔽")).toBeNull(); expect(queryByText("🔼")).toBeNull(); }); - it("ignored capitalization when sorting", () => { + it("ignored capitalization when sorting", async () => { const thingA = SolidFns.addStringNoLocale( SolidFns.createThing(), namePredicate, @@ -160,6 +165,7 @@ describe(" component functional tests", () => { ); const datasetToSort = SolidFns.setThing(datasetWithThingA, thingB); + const user = userEvent.setup(); const { getByText, queryAllByRole } = render(
component functional tests", () => { ); expect(queryAllByRole("cell")[0].innerHTML).toBe("Foo"); expect(queryAllByRole("cell")[1].innerHTML).toBe("example"); - fireEvent.click(getByText(namePredicate)); + + await user.click(getByText(namePredicate)); + expect(queryAllByRole("cell")[0].innerHTML).toBe("example"); expect(queryAllByRole("cell")[1].innerHTML).toBe("Foo"); }); - it("uses sortFn for sorting if passed", () => { + it("uses sortFn for sorting if passed", async () => { const thingA = SolidFns.addStringNoLocale( SolidFns.createThing(), namePredicate, @@ -200,6 +208,7 @@ describe("
component functional tests", () => { return valueA.localeCompare(valueB); }; + const user = userEvent.setup(); const { getByText, queryAllByRole } = render(
component functional tests", () => { ); expect(queryAllByRole("cell")[0].innerHTML).toBe("Another Name"); expect(queryAllByRole("cell")[1].innerHTML).toBe("Name A"); - fireEvent.click(getByText(namePredicate)); + + await user.click(getByText(namePredicate)); + expect(queryAllByRole("cell")[0].innerHTML).toBe("Name A"); expect(queryAllByRole("cell")[1].innerHTML).toBe("Another Name"); }); - it("updates header when sorting", () => { + it("updates header when sorting", async () => { + const user = userEvent.setup(); const { getByText, queryByText } = render(
component functional tests", () => { expect(queryByText("🔽")).toBeNull(); expect(queryByText("🔼")).toBeNull(); - fireEvent.click(getByText("Name")); + await user.click(getByText("Name")); + expect(queryByText("🔽")).toBeNull(); expect(getByText("🔼")).not.toBeNull(); - fireEvent.click(getByText("Name")); + await user.click(getByText("Name")); + expect(queryByText("🔼")).toBeNull(); expect(getByText("🔽")).not.toBeNull(); }); diff --git a/src/components/text/index.test.tsx b/src/components/text/index.test.tsx index 76352e46..178ffd71 100644 --- a/src/components/text/index.test.tsx +++ b/src/components/text/index.test.tsx @@ -21,7 +21,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; -import { render, fireEvent, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import { ErrorBoundary } from "react-error-boundary"; import * as SolidFns from "@inrupt/solid-client"; import * as helpers from "../../helpers"; @@ -211,6 +213,8 @@ describe(" component functional testing", () => { .spyOn(SolidFns, "setStringNoLocale") .mockImplementation(() => mockThing); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "updated nick value" } }); - input.blur(); + + await user.type(input, "updated nick value"); + // blur the input: + await user.tab(); + expect(SolidFns.setStringNoLocale).toHaveBeenCalled(); }); @@ -232,6 +239,8 @@ describe(" component functional testing", () => { .spyOn(SolidFns, "setStringWithLocale") .mockImplementation(() => mockThing); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "updated nick value" } }); - input.blur(); + + await user.type(input, "updated nick value"); + // blur the input: + await user.tab(); + expect(SolidFns.setStringWithLocale).toHaveBeenCalled(); }); @@ -253,6 +265,8 @@ describe(" component functional testing", () => { jest .spyOn(SolidFns, "setStringWithLocale") .mockImplementation(() => mockThing); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockNick); input.focus(); - input.blur(); + + await user.type(input, "u{Backspace}"); + // blur the input: + await user.tab(); + expect(SolidFns.setStringWithLocale).toHaveBeenCalledTimes(0); }); @@ -290,6 +308,9 @@ describe(" component functional testing", () => { const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); jest.spyOn(SolidFns, "getSolidDataset").mockResolvedValue(latestDataset); + + const user = userEvent.setup(); + const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "updated nick value" } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, "updated nick value"); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should call onSave for fetched dataset with custom location if it is passed", async () => { @@ -313,6 +337,8 @@ describe(" component functional testing", () => { const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); jest.spyOn(SolidFns, "getSolidDataset").mockResolvedValue(latestDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "updated nick value" } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, "updated nick value"); + + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should call onError if saving fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "updated nick value" } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, "updated nick value"); + + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if saving fetched dataset to custom location fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "updated nick value" } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, "updated nick value"); + + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should throw error if SaveDatasetTo is missing for new data", async () => { jest.spyOn(console, "error").mockImplementation(() => {}); + + const user = userEvent.setup(); const { getByText, getByDisplayValue } = render(
{JSON.stringify(error)}
} @@ -392,9 +436,13 @@ describe(" component functional testing", () => { const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "updated nick value" } }); - input.blur(); - await waitFor(() => expect(getByText("{}")).toBeDefined()); + + await user.type(input, "updated nick value"); + + // blur the input: + await user.tab(); + + expect(getByText("{}")).toBeDefined(); // eslint-disable-next-line no-console (console.error as jest.Mock).mockRestore(); }); @@ -412,6 +460,8 @@ describe(" component functional testing", () => { thing: mockThing, property: mockPredicate, }); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "updated nick value" } }); - input.blur(); + + await user.type(input, "updated nick value"); + // blur the input: + await user.tab(); + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); - await waitFor(() => expect(setDataset).toHaveBeenCalledWith(latestDataset)); + expect(setDataset).toHaveBeenCalledWith(latestDataset); }); }); diff --git a/src/components/value/boolean/index.test.tsx b/src/components/value/boolean/index.test.tsx index 745c1dc8..a6f06193 100644 --- a/src/components/value/boolean/index.test.tsx +++ b/src/components/value/boolean/index.test.tsx @@ -21,7 +21,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; -import { fireEvent, render, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import * as SolidFns from "@inrupt/solid-client"; import * as helpers from "../../../helpers"; import BooleanValue from "./index"; @@ -75,7 +77,7 @@ describe(" component functional testing", () => { expect(getByText(mockValue.toString())).toBeDefined(); }); - it("should call setBoolean on blur", () => { + it("should call setBoolean on blur", async () => { jest .spyOn(SolidFns, "getBoolean" as any) .mockImplementationOnce(() => null); @@ -86,6 +88,7 @@ describe(" component functional testing", () => { jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + const user = userEvent.setup(); const { getByTitle } = render( component functional testing", () => { ); const input = getByTitle("test title"); input.focus(); - fireEvent.click(input); + + await user.click(input); + input.blur(); expect(mockSetter).toHaveBeenCalled(); }); @@ -121,6 +126,7 @@ describe(" component functional testing", () => { }); it("Should call saveSolidDatasetAt onBlur if autosave is true", async () => { + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue("false"); input.focus(); - fireEvent.click(input); - input.blur(); - await waitFor(() => expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled()); + + await user.click(input); + + await user.tab(); + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); }); it("Should not call saveSolidDatasetAt onBlur if autosave is false", async () => { @@ -157,6 +165,8 @@ describe(" component functional testing", () => { const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); jest.spyOn(SolidFns, "getSolidDataset").mockResolvedValue(latestDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue("false"); input.focus(); - fireEvent.click(input); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.click(input); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should call onSave for fetched dataset with custom location if it is passed", async () => { @@ -180,6 +193,8 @@ describe(" component functional testing", () => { const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); jest.spyOn(SolidFns, "getSolidDataset").mockResolvedValue(latestDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue("false"); input.focus(); - fireEvent.click(input); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.click(input); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should update context with latest dataset after saving", async () => { const setDataset = jest.fn(); @@ -212,6 +230,8 @@ describe(" component functional testing", () => { }); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); jest.spyOn(SolidFns, "getSolidDataset").mockResolvedValue(latestDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue("false"); input.focus(); - fireEvent.click(input); - input.blur(); + + await user.click(input); + // blur the input: + await user.tab(); + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); - await waitFor(() => expect(setDataset).toHaveBeenCalledWith(latestDataset)); + expect(setDataset).toHaveBeenCalledWith(latestDataset); }); it("Should call onError if saving fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue("false"); input.focus(); - fireEvent.click(input); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.click(input); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if Thing not found", async () => { @@ -263,6 +292,7 @@ describe(" component functional testing", () => { property: mockPredicate, }); const onError = jest.fn(); + render( component functional testing", () => { onError={onError} /> ); + await waitFor(() => expect(onError).toHaveBeenCalled()); }); it("Should call onError if saving fetched dataset to custom location fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue("false"); input.focus(); - fireEvent.click(input); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.click(input); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if trying to save a non-fetched dataset without saveDatasetTo", async () => { @@ -303,6 +340,8 @@ describe(" component functional testing", () => { ); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue("false"); input.focus(); - fireEvent.click(input); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.click(input); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); }); diff --git a/src/components/value/datetime/index.test.tsx b/src/components/value/datetime/index.test.tsx index 9a2777c5..79f2055b 100644 --- a/src/components/value/datetime/index.test.tsx +++ b/src/components/value/datetime/index.test.tsx @@ -21,7 +21,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; -import { fireEvent, render, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import * as SolidFns from "@inrupt/solid-client"; import * as helpers from "../../../helpers"; import DatetimeValue from "./index"; @@ -75,7 +77,8 @@ describe(" component functional testing", () => { expect(getByText("2021-05-04T06:00:00")).toBeDefined(); }); - it("should call setDatetime on blur", () => { + + it("should call setDatetime on blur", async () => { jest .spyOn(SolidFns, "setDatetime" as any) .mockImplementationOnce(() => null); @@ -86,6 +89,7 @@ describe(" component functional testing", () => { jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + const user = userEvent.setup(); const { getByTitle } = render( component functional testing", () => { inputProps={{ title: "test title" }} /> ); + const input = getByTitle("test title"); input.focus(); - fireEvent.change(input, { target: { value: testBday } }); - input.blur(); + + await user.type(input, testBday.toISOString()); + // blur the input: + await user.tab(); + expect(mockSetter).toHaveBeenCalled(); }); it("Should not call setter on blur if the value of the input hasn't changed", async () => { @@ -120,6 +128,8 @@ describe(" component functional testing", () => { }); it("Should not call saveSolidDatasetAt onBlur if autosave is false", async () => { jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByLabelText } = render( component functional testing", () => { edit /> ); + const input = getByLabelText("Date and Time"); input.focus(); - fireEvent.change(input, { target: { value: testBday } }); - input.blur(); + + await user.type(input, testBday.toISOString()); + // blur the input: + await user.tab(); + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalledTimes(0); }); it("Should call saveSolidDatasetAt onBlur if autosave is true", async () => { + const user = userEvent.setup(); const { getByLabelText } = render( component functional testing", () => { autosave /> ); + const input = getByLabelText("Date and Time"); input.focus(); - fireEvent.change(input, { target: { value: testBday } }); - input.blur(); - await waitFor(() => expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled()); + + await user.type(input, testBday.toISOString()); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); }); it("Should call onSave if it is passed", async () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByLabelText } = render( component functional testing", () => { autosave /> ); + const input = getByLabelText("Date and Time"); input.focus(); - fireEvent.change(input, { target: { value: testBday } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, testBday.toISOString()); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should call onSave for fetched dataset with custom location if it is passed", async () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByLabelText } = render( component functional testing", () => { autosave /> ); + const input = getByLabelText("Date and Time"); input.focus(); - fireEvent.change(input, { target: { value: testBday } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, testBday.toISOString()); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should call onError if saving fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByLabelText } = render( component functional testing", () => { ); const input = getByLabelText("Date and Time"); input.focus(); - fireEvent.change(input, { target: { value: testBday } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testBday.toISOString()); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if Thing not found", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); @@ -232,6 +268,8 @@ describe(" component functional testing", () => { it("Should call onError if saving fetched dataset to custom location fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByLabelText } = render( component functional testing", () => { autosave /> ); + const input = getByLabelText("Date and Time"); input.focus(); - fireEvent.change(input, { target: { value: testBday } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testBday.toISOString()); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if trying to save a non-fetched dataset without saveDatasetTo", async () => { const mockUnfetchedDataset = SolidFns.setThing( @@ -256,6 +298,8 @@ describe(" component functional testing", () => { ); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByLabelText } = render( component functional testing", () => { ); const input = getByLabelText("Date and Time"); input.focus(); - fireEvent.change(input, { target: { value: testBday } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); - }); - it("when datetime-local is unsupported, it calls setDatetime with the correct value", () => { - jest.spyOn(helpers, "useDatetimeBrowserSupport").mockReturnValue(false); - jest.spyOn(SolidFns, "getDatetime").mockImplementationOnce(() => null); + await user.type(input, testBday.toISOString()); + // blur the input: + await user.tab(); - const mockSetter = jest.spyOn(SolidFns, "setDatetime"); + expect(onError).toHaveBeenCalled(); + }); + it("when datetime-local is unsupported, it calls setDatetime with the correct value", async () => { + const expectedDate = new Date(Date.UTC(2020, 2, 3, 0, 0, 0)); + const expectedDateAndTime = new Date(Date.UTC(2020, 2, 3, 5, 45, 0)); + jest.spyOn(helpers, "useDatetimeBrowserSupport").mockReturnValue(false); + jest.spyOn(SolidFns, "getDatetime").mockImplementationOnce(() => null); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); - jest.spyOn(SolidFns, "getSolidDataset").mockResolvedValue(latestDataset); + const mockSetter = jest.spyOn(SolidFns, "setDatetime"); + + const user = userEvent.setup(); const { getByLabelText } = render( component functional testing", () => { autosave /> ); + const dateInput = getByLabelText("Date"); dateInput.focus(); - fireEvent.change(dateInput, { target: { value: "2020-03-03" } }); + + await user.type(dateInput, "2020-03-03"); + dateInput.blur(); + const timeInput = getByLabelText("Time"); timeInput.focus(); - fireEvent.change(timeInput, { target: { value: "05:45" } }); + + await user.type(timeInput, "05:45"); + timeInput.blur(); - const expectedDate = new Date(Date.UTC(2020, 2, 3, 0, 0, 0)); - const expectedDateAndTime = new Date(Date.UTC(2020, 2, 3, 5, 45, 0)); + expect(mockSetter.mock.calls).toEqual([ [mockThing, mockPredicate, expectedDate], [mockThing, mockPredicate, expectedDateAndTime], @@ -319,6 +372,8 @@ describe(" component functional testing", () => { thing: mockThing, property: mockPredicate, }); + + const user = userEvent.setup(); const { getByLabelText } = render( component functional testing", () => { ); const input = getByLabelText("Date and Time"); input.focus(); - fireEvent.change(input, { target: { value: "2007-08-14T11:20:00" } }); - input.blur(); - await waitFor(() => { - expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); - expect(setDataset).toHaveBeenCalledWith(latestDataset); - }); + + await user.type(input, "2007-08-14T11:20:00"); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); + expect(setDataset).toHaveBeenCalledWith(latestDataset); }); }); diff --git a/src/components/value/decimal/index.test.tsx b/src/components/value/decimal/index.test.tsx index bb1fea28..d2062e82 100644 --- a/src/components/value/decimal/index.test.tsx +++ b/src/components/value/decimal/index.test.tsx @@ -21,7 +21,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; -import { fireEvent, render, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import * as SolidFns from "@inrupt/solid-client"; import * as helpers from "../../../helpers"; import DecimalValue from "./index"; @@ -73,7 +75,7 @@ describe(" component functional testing", () => { expect(getByText(mockVersion)).toBeDefined(); }); - it("should call setDecimal on blur", () => { + it("should call setDecimal on blur", async () => { jest .spyOn(SolidFns, "setDecimal" as any) .mockImplementationOnce(() => null); @@ -84,6 +86,7 @@ describe(" component functional testing", () => { jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + const user = userEvent.setup(); const { getByTitle } = render( component functional testing", () => { ); const input = getByTitle("test title"); input.focus(); - fireEvent.change(input, { target: { value: testVersion } }); - input.blur(); + + await user.type(input, testVersion.toString()); + // blur the input: + await user.tab(); + expect(mockSetter).toHaveBeenCalled(); }); @@ -119,6 +125,7 @@ describe(" component functional testing", () => { }); it("Should call saveSolidDatasetAt onBlur if autosave is true", async () => { + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockVersion); input.focus(); - fireEvent.change(input, { target: { value: testVersion } }); - input.blur(); - await waitFor(() => expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled()); + + await user.type(input, testVersion.toString()); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); }); it("Should not call saveSolidDatasetAt onBlur if autosave is false", async () => { @@ -154,6 +164,8 @@ describe(" component functional testing", () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockVersion); input.focus(); - fireEvent.change(input, { target: { value: testVersion } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, testVersion.toString()); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should call onSave for fetched dataset with custom location if it is passed", async () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockVersion); input.focus(); - fireEvent.change(input, { target: { value: testVersion } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, testVersion.toString()); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should update context with latest dataset after saving", async () => { const setDataset = jest.fn(); @@ -206,6 +227,8 @@ describe(" component functional testing", () => { thing: mockThing, property: mockPredicate, }); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockVersion); input.focus(); - fireEvent.change(input, { target: { value: testVersion } }); - input.blur(); - await waitFor(() => { - expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); - expect(setDataset).toHaveBeenCalledWith(latestDataset); - }); + + await user.type(input, testVersion.toString()); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); + expect(setDataset).toHaveBeenCalledWith(latestDataset); }); it("Should call onError if Thing not found", async () => { @@ -254,6 +279,8 @@ describe(" component functional testing", () => { it("Should call onError if saving fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockVersion); input.focus(); - fireEvent.change(input, { target: { value: testVersion } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testVersion.toString()); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if saving fetched dataset to custom location fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockVersion); input.focus(); - fireEvent.change(input, { target: { value: testVersion } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testVersion.toString()); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if trying to save a non-fetched dataset without saveDatasetTo", async () => { @@ -300,6 +337,8 @@ describe(" component functional testing", () => { ); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockVersion); input.focus(); - fireEvent.change(input, { target: { value: testVersion } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testVersion.toString()); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); }); diff --git a/src/components/value/integer/index.test.tsx b/src/components/value/integer/index.test.tsx index 09c922fa..41fe158c 100644 --- a/src/components/value/integer/index.test.tsx +++ b/src/components/value/integer/index.test.tsx @@ -21,7 +21,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; -import { fireEvent, render, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import * as SolidFns from "@inrupt/solid-client"; import * as helpers from "../../../helpers"; import IntegerValue from "./index"; @@ -73,7 +75,7 @@ describe(" component functional testing", () => { expect(getByText(mockCopyrightYear)).toBeDefined(); }); - it("should call setInteger on blur", () => { + it("should call setInteger on blur", async () => { jest .spyOn(SolidFns, "setInteger" as any) .mockImplementationOnce(() => null); @@ -84,6 +86,7 @@ describe(" component functional testing", () => { jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + const user = userEvent.setup(); const { getByTitle } = render( component functional testing", () => { inputProps={{ title: "test title" }} /> ); + const input = getByTitle("test title"); input.focus(); - fireEvent.change(input, { target: { value: testCopyrightYear } }); - input.blur(); + + await user.type(input, testCopyrightYear.toString()); + // blur the input: + await user.tab(); + expect(mockSetter).toHaveBeenCalled(); }); @@ -119,6 +126,7 @@ describe(" component functional testing", () => { }); it("Should call saveSolidDatasetAt onBlur if autosave is true", async () => { + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockCopyrightYear); input.focus(); - fireEvent.change(input, { target: { value: testCopyrightYear } }); - input.blur(); - await waitFor(() => expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled()); + + await user.type(input, testCopyrightYear.toString()); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); }); it("Should not call saveSolidDatasetAt onBlur if autosave is false", async () => { @@ -154,6 +166,8 @@ describe(" component functional testing", () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockCopyrightYear); input.focus(); - fireEvent.change(input, { target: { value: testCopyrightYear } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, testCopyrightYear.toString()); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should call onSave for fetched dataset with custom location if it is passed", async () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockCopyrightYear); input.focus(); - fireEvent.change(input, { target: { value: testCopyrightYear } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, testCopyrightYear.toString()); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should update context with latest dataset after saving", async () => { @@ -207,6 +229,8 @@ describe(" component functional testing", () => { thing: mockThing, property: mockPredicate, }); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockCopyrightYear); input.focus(); - fireEvent.change(input, { target: { value: testCopyrightYear } }); - input.blur(); - await waitFor(() => { - expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); - expect(setDataset).toHaveBeenCalledWith(latestDataset); - }); + + await user.type(input, testCopyrightYear.toString()); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); + expect(setDataset).toHaveBeenCalledWith(latestDataset); }); it("Should call onError if Thing not found", async () => { @@ -255,6 +281,8 @@ describe(" component functional testing", () => { it("Should call onError if saving fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockCopyrightYear); input.focus(); - fireEvent.change(input, { target: { value: testCopyrightYear } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testCopyrightYear.toString()); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if saving fetched dataset to custom location fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockCopyrightYear); input.focus(); - fireEvent.change(input, { target: { value: testCopyrightYear } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testCopyrightYear.toString()); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if trying to save a non-fetched dataset without saveDatasetTo", async () => { @@ -301,6 +339,8 @@ describe(" component functional testing", () => { ); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockCopyrightYear); input.focus(); - fireEvent.change(input, { target: { value: testCopyrightYear } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testCopyrightYear.toString()); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); }); diff --git a/src/components/value/string/index.test.tsx b/src/components/value/string/index.test.tsx index 99333a42..34967177 100644 --- a/src/components/value/string/index.test.tsx +++ b/src/components/value/string/index.test.tsx @@ -21,7 +21,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; -import { fireEvent, render, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import * as SolidFns from "@inrupt/solid-client"; import * as helpers from "../../../helpers"; import StringValue from "./index"; @@ -97,7 +99,7 @@ describe(" component functional testing", () => { expect(getByText(mockNick)).toBeDefined(); }); - it("should call setStringNoLocale on blur", () => { + it("should call setStringNoLocale on blur", async () => { jest .spyOn(SolidFns, "getStringNoLocale" as any) .mockImplementationOnce(() => null); @@ -108,6 +110,7 @@ describe(" component functional testing", () => { jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + const user = userEvent.setup(); const { getByTitle } = render( component functional testing", () => { inputProps={{ title: "test title" }} /> ); + const input = getByTitle("test title"); input.focus(); - fireEvent.change(input, { target: { value: "test string" } }); - input.blur(); + + await user.type(input, "test string"); + // blur the input: + await user.tab(); + expect(mockSetter).toHaveBeenCalled(); }); - it("should call setStringWithLocale on blur when locale is passed", () => { + it("should call setStringWithLocale on blur when locale is passed", async () => { jest .spyOn(SolidFns, "getStringWithLocale" as any) .mockImplementationOnce(() => null); @@ -136,6 +143,7 @@ describe(" component functional testing", () => { jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + const user = userEvent.setup(); const { getByTitle } = render( component functional testing", () => { inputProps={{ title: "test title" }} /> ); + const input = getByTitle("test title"); input.focus(); - fireEvent.change(input, { target: { value: "test string" } }); - input.blur(); + + await user.type(input, "test string"); + // blur the input: + await user.tab(); + expect(mockSetter).toHaveBeenCalled(); }); @@ -194,6 +206,7 @@ describe(" component functional testing", () => { }); it("Should call saveSolidDatasetAt onBlur if autosave is true", async () => { + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "test string" } }); - input.blur(); - await waitFor(() => expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled()); + + await user.type(input, "test string"); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); }); it("Should not call saveSolidDatasetAt onBlur if autosave is false", async () => { jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + const { getByDisplayValue } = render( component functional testing", () => { edit /> ); + getByDisplayValue(mockNick).focus(); getByDisplayValue(mockNick).blur(); + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalledTimes(0); }); @@ -229,6 +249,8 @@ describe(" component functional testing", () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "test value" } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, "test value"); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should call onSave for fetched dataset with custom location if it is passed", async () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "test value" } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, "test value"); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should update context with latest dataset after saving", async () => { @@ -282,6 +314,8 @@ describe(" component functional testing", () => { thing: mockThing, property: mockPredicate, }); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { ); const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "test value" } }); - input.blur(); - await waitFor(() => { - expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); - expect(setDataset).toHaveBeenCalledWith(latestDataset); - }); + + await user.type(input, "test value"); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); + expect(setDataset).toHaveBeenCalledWith(latestDataset); }); it("Should call onError if Thing not found", async () => { @@ -315,6 +350,7 @@ describe(" component functional testing", () => { thing: undefined, property: mockPredicate, }); + render( component functional testing", () => { onError={onError} /> ); + await waitFor(() => expect(onError).toHaveBeenCalled()); }); it("Should call onError if saving fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "test value" } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, "test value"); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if saving fetched dataset to custom location fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "test value" } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, "test value"); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if trying to save a non-fetched dataset without saveDatasetTo", async () => { @@ -376,6 +425,8 @@ describe(" component functional testing", () => { ); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockNick); input.focus(); - fireEvent.change(input, { target: { value: "test value" } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, "test value"); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); }); diff --git a/src/components/value/url/index.test.tsx b/src/components/value/url/index.test.tsx index d44e818f..71ba65c4 100644 --- a/src/components/value/url/index.test.tsx +++ b/src/components/value/url/index.test.tsx @@ -21,7 +21,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from "react"; -import { fireEvent, render, waitFor } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import * as SolidFns from "@inrupt/solid-client"; import * as helpers from "../../../helpers"; import UrlValue from "./index"; @@ -73,7 +75,7 @@ describe(" component functional testing", () => { expect(getByText(mockUrl)).toBeDefined(); }); - it("should call setUrl on blur", () => { + it("should call setUrl on blur", async () => { jest.spyOn(SolidFns, "setUrl" as any).mockImplementationOnce(() => null); const mockSetter = jest @@ -82,6 +84,7 @@ describe(" component functional testing", () => { jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + const user = userEvent.setup(); const { getByTitle } = render( component functional testing", () => { inputProps={{ title: "test title" }} /> ); + const input = getByTitle("test title"); input.focus(); - fireEvent.change(input, { target: { value: testUrl } }); - input.blur(); + + await user.type(input, testUrl); + // blur the input: + await user.tab(); + expect(mockSetter).toHaveBeenCalled(); }); @@ -117,6 +124,7 @@ describe(" component functional testing", () => { }); it("Should call saveSolidDatasetAt onBlur if autosave is true", async () => { + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockUrl); input.focus(); - fireEvent.change(input, { target: { value: testUrl } }); - input.blur(); - await waitFor(() => expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled()); + + await user.type(input, testUrl); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); }); it("Should not call saveSolidDatasetAt onBlur if autosave is false", async () => { @@ -152,6 +164,8 @@ describe(" component functional testing", () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockUrl); input.focus(); - fireEvent.change(input, { target: { value: testUrl } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, testUrl); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should call onSave for fetched dataset with custom location if it is passed", async () => { const onSave = jest.fn(); const onError = jest.fn(); jest.spyOn(SolidFns, "saveSolidDatasetAt").mockResolvedValue(savedDataset); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockUrl); input.focus(); - fireEvent.change(input, { target: { value: testUrl } }); - input.blur(); - await waitFor(() => expect(onSave).toHaveBeenCalled()); + + await user.type(input, testUrl); + // blur the input: + await user.tab(); + + expect(onSave).toHaveBeenCalled(); }); it("Should update context with latest dataset after saving", async () => { const setDataset = jest.fn(); @@ -204,6 +228,8 @@ describe(" component functional testing", () => { thing: mockThing, property: mockPredicate, }); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockUrl); input.focus(); - fireEvent.change(input, { target: { value: testUrl } }); - input.blur(); - await waitFor(() => { - expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); - expect(setDataset).toHaveBeenCalledWith(latestDataset); - }); + + await user.type(input, testUrl); + // blur the input: + await user.tab(); + + expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled(); + expect(setDataset).toHaveBeenCalledWith(latestDataset); }); it("Should call onError if Thing not found", async () => { @@ -252,6 +280,8 @@ describe(" component functional testing", () => { it("Should call onError if saving fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockUrl); input.focus(); - fireEvent.change(input, { target: { value: testUrl } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testUrl); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if saving fetched dataset to custom location fails", async () => { (SolidFns.saveSolidDatasetAt as jest.Mock).mockRejectedValueOnce(null); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockUrl); input.focus(); - fireEvent.change(input, { target: { value: testUrl } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testUrl); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); it("Should call onError if trying to save a non-fetched dataset without saveDatasetTo", async () => { @@ -298,6 +338,8 @@ describe(" component functional testing", () => { ); const onError = jest.fn(); + + const user = userEvent.setup(); const { getByDisplayValue } = render( component functional testing", () => { autosave /> ); + const input = getByDisplayValue(mockUrl); input.focus(); - fireEvent.change(input, { target: { value: testUrl } }); - input.blur(); - await waitFor(() => expect(onError).toHaveBeenCalled()); + + await user.type(input, testUrl); + // blur the input: + await user.tab(); + + expect(onError).toHaveBeenCalled(); }); }); diff --git a/src/components/video/index.test.tsx b/src/components/video/index.test.tsx index e788e95b..cdd75c9d 100644 --- a/src/components/video/index.test.tsx +++ b/src/components/video/index.test.tsx @@ -20,7 +20,9 @@ */ import React from "react"; -import { render, waitFor, fireEvent } from "@testing-library/react"; +import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + import * as SolidFns from "@inrupt/solid-client"; import { Video } from "."; import * as helpers from "../../helpers"; @@ -36,6 +38,9 @@ const mockThing = SolidFns.addUrl( const mockObjectUrl = "mock object url"; const mockFile = SolidFns.mockFileFrom(mockUrl); +const mockFileUpload = new File(["some binary data"], "movie.mp4", { + type: "video/mp4", +}); window.URL.createObjectURL = jest.fn(() => mockObjectUrl); @@ -210,6 +215,7 @@ describe("Video component", () => { }); it("Should not call overwriteFile on change if autosave is false", async () => { + const user = userEvent.setup(); const { getByAltText, getByTitle } = render(