diff --git a/package-lock.json b/package-lock.json index b46bf967a..5a9a48199 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3904,19 +3904,23 @@ } }, "@types/jest": { - "version": "24.0.13", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.13.tgz", - "integrity": "sha512-3m6RPnO35r7Dg+uMLj1+xfZaOgIHHHut61djNjzwExXN4/Pm9has9C6I1KMYSfz7mahDhWUOVg4HW/nZdv5Pww==", + "version": "24.0.23", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.23.tgz", + "integrity": "sha512-L7MBvwfNpe7yVPTXLn32df/EK+AMBFAFvZrRuArGs7npEWnlziUXK+5GMIUTI4NIuwok3XibsjXCs5HxviYXjg==", "dev": true, "requires": { - "@types/jest-diff": "*" + "jest-diff": "^24.3.0" } }, - "@types/jest-diff": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz", - "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==", - "dev": true + "@types/jest-axe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/jest-axe/-/jest-axe-3.2.1.tgz", + "integrity": "sha512-sn+MFd66gNnvhtBkbQBY6q2aznzLXUIN/jJqXd11D0P+PbUnDrthqyOj81O8BLhEYopmUXIp/ktVvdtj/1GZdw==", + "dev": true, + "requires": { + "@types/jest": "*", + "axe-core": "^3.0.3" + } }, "@types/joi": { "version": "13.3.0", @@ -4184,6 +4188,11 @@ "@types/node": "*" } }, + "@types/prettier": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.0.tgz", + "integrity": "sha512-gDE8JJEygpay7IjA/u3JiIURvwZW08f0cZSZLAzFoX/ZmeqvS0Sqv+97aKuHpNsalAMMhwPe+iAS6fQbfmbt7A==" + }, "@types/prop-types": { "version": "15.5.8", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz", @@ -19836,6 +19845,26 @@ } } }, + "jest-axe": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jest-axe/-/jest-axe-3.2.0.tgz", + "integrity": "sha512-QSQwSwG72/cpmhJU0fBsaUUvu9mb2uAqhccGQVG6JbIV8sK+aIXh8hYl7vxraMF/I6soQod1aqSdD/j7LjpVFQ==", + "dev": true, + "requires": { + "axe-core": "3.3.1", + "chalk": "2.4.2", + "jest-matcher-utils": "24.8.0", + "lodash.merge": "4.6.2" + }, + "dependencies": { + "axe-core": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.3.1.tgz", + "integrity": "sha512-gw1T0JptHPF4AdLLqE8yQq3Z7YvsYkpFmFWd84r6hnq/QoKRr8icYHFumhE7wYl5TVIHgVlchMyJsAYh0CfwCQ==", + "dev": true + } + } + }, "jest-changed-files": { "version": "24.8.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.8.0.tgz", diff --git a/package.json b/package.json index 56ae60033..56dc116ca 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "dependencies": { "@coralproject/bunyan-prettystream": "^0.1.4", "@metascraper/helpers": "^5.7.21", + "@types/prettier": "^1.19.0", "abort-controller": "^3.0.0", "akismet-api": "^4.2.0", "apollo-server-express": "^2.8.1", @@ -186,7 +187,8 @@ "@types/html-to-text": "^1.4.31", "@types/html-webpack-plugin": "^3.2.0", "@types/ioredis": "^4.0.10", - "@types/jest": "^24.0.13", + "@types/jest": "^24.0.23", + "@types/jest-axe": "^3.2.1", "@types/joi": "^13.0.8", "@types/jsdom": "^12.2.3", "@types/jsonwebtoken": "^7.2.7", @@ -300,6 +302,7 @@ "husky": "^2.2.0", "intersection-observer": "^0.6.0", "jest": "^24.8.0", + "jest-axe": "^3.2.0", "jest-junit": "^6.4.0", "jest-localstorage-mock": "^2.4.0", "jest-mock-console": "^1.0.0", diff --git a/scripts/watcher/ChokidarWatcher.ts b/scripts/watcher/ChokidarWatcher.ts index 65a6ad701..456fb0f27 100644 --- a/scripts/watcher/ChokidarWatcher.ts +++ b/scripts/watcher/ChokidarWatcher.ts @@ -23,9 +23,10 @@ export default class ChokidarWatcher implements Watcher { let firstError: Error | null = null; // If this is set, a pending promise is waiting for the next result. - let pending: - | ({ resolve: (result: string) => void; reject: (error: Error) => void }) - | null = null; + let pending: { + resolve: (result: string) => void; + reject: (error: Error) => void; + } | null = null; // Only start client if we have something to watch. if (paths.length) { diff --git a/scripts/watcher/SaneWatcher.ts b/scripts/watcher/SaneWatcher.ts index 6769ba24e..3984f4f75 100644 --- a/scripts/watcher/SaneWatcher.ts +++ b/scripts/watcher/SaneWatcher.ts @@ -48,7 +48,7 @@ export default class SaneWatcher implements Watcher { const queue: string[] = []; // If this is set, a pending promise is waiting for the next result. - let pending: ({ resolve: (result: string) => void }) | null = null; + let pending: { resolve: (result: string) => void } | null = null; // Only start client if we have something to watch. if (paths.length) { diff --git a/src/core/client/account/test/confirmEmail.spec.tsx b/src/core/client/account/test/confirmEmail.spec.tsx index b7f9c31dc..57cdae59e 100644 --- a/src/core/client/account/test/confirmEmail.spec.tsx +++ b/src/core/client/account/test/confirmEmail.spec.tsx @@ -31,7 +31,9 @@ it("renders missing confirm token", async () => { replaceHistoryLocation("http://localhost/account/email/confirm"); const { root } = await createTestRenderer(); await waitForElement(() => within(root).getByTestID("invalid-link")); + expect(within(root).toJSON()).toMatchSnapshot(); + expect(await within(root).axe()).toHaveNoViolations(); }); it("renders form", async () => { @@ -57,6 +59,7 @@ it("renders form", async () => { ); }); expect(within(root).toJSON()).toMatchSnapshot(); + expect(await within(root).axe()).toHaveNoViolations(); restMock.verify(); }); @@ -98,6 +101,7 @@ it("renders error from server", async () => { ); }); restMock.verify(); + expect(await within(root).axe()).toHaveNoViolations(); } }); diff --git a/src/core/client/account/test/resetPassword.spec.tsx b/src/core/client/account/test/resetPassword.spec.tsx index 8cbb83e52..a5d564b8c 100644 --- a/src/core/client/account/test/resetPassword.spec.tsx +++ b/src/core/client/account/test/resetPassword.spec.tsx @@ -32,6 +32,7 @@ it("renders missing reset token", async () => { const { root } = await createTestRenderer(); await waitForElement(() => within(root).getByTestID("invalid-link")); expect(within(root).toJSON()).toMatchSnapshot(); + expect(await within(root).axe()).toHaveNoViolations(); }); it("renders form", async () => { @@ -57,6 +58,7 @@ it("renders form", async () => { ); }); expect(within(root).toJSON()).toMatchSnapshot(); + expect(await within(root).axe()).toHaveNoViolations(); restMock.verify(); }); @@ -98,6 +100,7 @@ it("renders error from server", async () => { ); }); restMock.verify(); + expect(await within(root).axe()).toHaveNoViolations(); } }); diff --git a/src/core/client/account/test/unsubscribeNotifications.spec.tsx b/src/core/client/account/test/unsubscribeNotifications.spec.tsx index 1cb162bab..e4c69b94d 100644 --- a/src/core/client/account/test/unsubscribeNotifications.spec.tsx +++ b/src/core/client/account/test/unsubscribeNotifications.spec.tsx @@ -32,6 +32,7 @@ it("renders missing confirm token", async () => { const { root } = await createTestRenderer(); await waitForElement(() => within(root).getByTestID("invalid-link")); expect(within(root).toJSON()).toMatchSnapshot(); + expect(await within(root).axe()).toHaveNoViolations(); }); it("renders form", async () => { @@ -53,6 +54,7 @@ it("renders form", async () => { await waitForElement(() => within(root).getByTestID("unsubscribe-form")); }); expect(within(root).toJSON()).toMatchSnapshot(); + expect(await within(root).axe()).toHaveNoViolations(); restMock.verify(); }); @@ -93,6 +95,7 @@ it("renders error from server", async () => { ); }); restMock.verify(); + expect(await within(root).axe()).toHaveNoViolations(); } }); diff --git a/src/core/client/auth/test/addEmailAddress.spec.tsx b/src/core/client/auth/test/addEmailAddress.spec.tsx index f6711d1df..be5c2b0d6 100644 --- a/src/core/client/auth/test/addEmailAddress.spec.tsx +++ b/src/core/client/auth/test/addEmailAddress.spec.tsx @@ -68,6 +68,7 @@ it("renders addEmailAddress view", async () => { expect(toJSON(root)).toMatchSnapshot(); }); }); + expect(await within(root).axe()).toHaveNoViolations(); }); it("shows error when submitting empty form", async () => { diff --git a/src/core/client/auth/test/createPassword.spec.tsx b/src/core/client/auth/test/createPassword.spec.tsx index 426d36a74..b3fba3544 100644 --- a/src/core/client/auth/test/createPassword.spec.tsx +++ b/src/core/client/auth/test/createPassword.spec.tsx @@ -60,6 +60,7 @@ async function createTestRenderer( it("renders createPassword view", async () => { const { root } = await createTestRenderer(); expect(toJSON(root)).toMatchSnapshot(); + expect(await within(root).axe()).toHaveNoViolations(); }); it("shows error when submitting empty form", async () => { diff --git a/src/core/client/auth/test/createUsername.spec.tsx b/src/core/client/auth/test/createUsername.spec.tsx index 9c9afaa21..b5da984c7 100644 --- a/src/core/client/auth/test/createUsername.spec.tsx +++ b/src/core/client/auth/test/createUsername.spec.tsx @@ -58,6 +58,7 @@ async function createTestRenderer( it("renders createUsername view", async () => { const { root } = await createTestRenderer(); expect(toJSON(root)).toMatchSnapshot(); + expect(await within(root).axe()).toHaveNoViolations(); }); it("shows error when submitting empty form", async () => { diff --git a/src/core/client/auth/test/forgotPassword.spec.tsx b/src/core/client/auth/test/forgotPassword.spec.tsx index 24b931836..f28770926 100644 --- a/src/core/client/auth/test/forgotPassword.spec.tsx +++ b/src/core/client/auth/test/forgotPassword.spec.tsx @@ -64,6 +64,7 @@ afterEach(async () => { it("renders forgot password view", async () => { const { testRenderer } = await createTestRenderer(); expect(testRenderer.toJSON()).toMatchSnapshot(); + expect(await within(testRenderer.root).axe()).toHaveNoViolations(); }); it("shows error when submitting empty form", async () => { diff --git a/src/core/client/auth/test/signIn.spec.tsx b/src/core/client/auth/test/signIn.spec.tsx index f1959c11c..9d56b10ef 100644 --- a/src/core/client/auth/test/signIn.spec.tsx +++ b/src/core/client/auth/test/signIn.spec.tsx @@ -58,6 +58,7 @@ async function createTestRenderer( it("renders sign in view", async () => { const { testRenderer } = await createTestRenderer(); expect(testRenderer.toJSON()).toMatchSnapshot(); + expect(await within(testRenderer.root).axe()).toHaveNoViolations(); }); it("renders sign in view with error", async () => { diff --git a/src/core/client/auth/test/signUp.spec.tsx b/src/core/client/auth/test/signUp.spec.tsx index f999eb118..3da11a7b8 100644 --- a/src/core/client/auth/test/signUp.spec.tsx +++ b/src/core/client/auth/test/signUp.spec.tsx @@ -53,6 +53,7 @@ async function createTestRenderer(customResolver: any = {}) { it("renders sign up form", async () => { const { testRenderer } = await createTestRenderer(); expect(testRenderer.toJSON()).toMatchSnapshot(); + expect(await within(testRenderer.root).axe()).toHaveNoViolations(); }); it("shows error when submitting empty form", async () => { diff --git a/src/core/client/framework/testHelpers/denormalize.ts b/src/core/client/framework/testHelpers/denormalize.ts index 78415d064..c156a1bc1 100644 --- a/src/core/client/framework/testHelpers/denormalize.ts +++ b/src/core/client/framework/testHelpers/denormalize.ts @@ -48,7 +48,7 @@ export function denormalizeComments(commentList: Array>) { } export function denormalizeStory(story: Fixture) { - const commentNodes = + const commentEdges = (story.comments && story.comments.edges && story.comments.edges.map((edge: any) => ({ @@ -61,20 +61,21 @@ export function denormalizeStory(story: Fixture) { }; if (commentsPageInfo.endCursor === undefined) { commentsPageInfo.endCursor = - commentNodes.length > 0 - ? commentNodes[commentNodes.length - 1].node.createdAt + commentEdges.length > 0 + ? commentEdges[commentEdges.length - 1].node.createdAt : null; } - const featuredCommentsCount = commentNodes.filter( - n => n.tags && n.tags.some((t: GQLTag) => t.code === GQLTAG.FEATURED) + const featuredCommentsCount = commentEdges.filter( + e => + e.node.tags && e.node.tags.some((t: GQLTag) => t.code === GQLTAG.FEATURED) ).length; return createFixture({ ...story, - comments: { edges: commentNodes, pageInfo: commentsPageInfo }, + comments: { edges: commentEdges, pageInfo: commentsPageInfo }, commentCounts: { ...story.commentCounts, - totalPublished: commentNodes.length, + totalPublished: commentEdges.length, tags: { ...(story.commentCounts && story.commentCounts.tags), FEATURED: featuredCommentsCount, diff --git a/src/core/client/framework/testHelpers/index.ts b/src/core/client/framework/testHelpers/index.ts index 4a130e441..ad2647b4f 100644 --- a/src/core/client/framework/testHelpers/index.ts +++ b/src/core/client/framework/testHelpers/index.ts @@ -46,3 +46,4 @@ export { default as overwriteQueryResolver, createQueryResolverOverwrite, } from "./overwriteQueryResolver"; +export { default as toHTML } from "./toHTML"; diff --git a/src/core/client/framework/testHelpers/toHTML.ts b/src/core/client/framework/testHelpers/toHTML.ts new file mode 100644 index 000000000..452ea3a53 --- /dev/null +++ b/src/core/client/framework/testHelpers/toHTML.ts @@ -0,0 +1,89 @@ +import { stripIndent } from "common-tags"; +import prettier from "prettier"; +import { ReactTestInstance } from "react-test-renderer"; + +import toJSON, { ReactTestRendererNode } from "./toJSON"; + +function convertPropertyToString(prop: string, value: any): string { + let propOut = prop; + let valueOut = ""; + + if (propOut === "dangerouslySetInnerHTML") { + return ""; + } + + // React uses `htmlFor` instead of `for` because of js restrictions. + if (propOut === "htmlFor") { + propOut = "for"; + } + + switch (typeof value) { + case "function": + valueOut = "[Function]"; + break; + case "string": + valueOut = value; + break; + case "undefined": + valueOut = propOut; + return ""; + case "boolean": + // Usually true means the property has been set without a value + // and false the property is not set. + // Exception: aria-labels need to be set to "true" / "false". + if (!prop.startsWith("aria-")) { + return value ? propOut : ""; + } + // fall through + default: + valueOut = JSON.stringify(value); + } + valueOut = valueOut.replace('"', """); + return `${propOut}="${valueOut}"`; +} + +function convertJSONToHTML( + node: ReactTestRendererNode | ReactTestRendererNode[] +): string { + if (typeof node === "string") { + return node; + } + if (Array.isArray(node)) { + return node.map(c => convertJSONToHTML(c)).join("\n"); + } + + const props = Object.keys(node.props) + .map(k => convertPropertyToString(k, node.props[k])) + .join(" "); + + let innerHTML = ""; + if ("dangerouslySetInnerHTML" in node.props) { + innerHTML = node.props.dangerouslySetInnerHTML.__html; + } else if (node.children) { + innerHTML = convertJSONToHTML(node.children); + } + + if (innerHTML === "") { + return `<${node.type} ${props} />`; + } + return stripIndent` + <${node.type} ${props}> + ${innerHTML} + `; +} + +/** + * Turns a ReactTestInstance into its HTML representation. + */ +export default function toHTML( + inst: ReactTestInstance, + options: { pretty?: boolean } = {} +) { + const result = toJSON(inst); + if (result === null) { + return ""; + } + + const output = convertJSONToHTML(result); + return options.pretty ? prettier.format(output, { parser: "html" }) : output; +} diff --git a/src/core/client/framework/testHelpers/toJSON.tsx b/src/core/client/framework/testHelpers/toJSON.tsx index dc79dc502..1458f76e0 100644 --- a/src/core/client/framework/testHelpers/toJSON.tsx +++ b/src/core/client/framework/testHelpers/toJSON.tsx @@ -1,12 +1,12 @@ import { ReactTestInstance } from "react-test-renderer"; -interface ReactTestRendererJSON { +export interface ReactTestRendererJSON { type: string; props: { [propName: string]: any }; children: null | ReactTestRendererNode[]; $$typeof?: symbol; // Optional because we add it with defineProperty(). } -type ReactTestRendererNode = ReactTestRendererJSON | string; +export type ReactTestRendererNode = ReactTestRendererJSON | string; export function toJSONRecursive( inst: ReactTestInstance diff --git a/src/core/client/framework/testHelpers/within.ts b/src/core/client/framework/testHelpers/within.ts index a0f5a6645..0e9be557b 100644 --- a/src/core/client/framework/testHelpers/within.ts +++ b/src/core/client/framework/testHelpers/within.ts @@ -1,3 +1,4 @@ +import { axe } from "jest-axe"; import { ReactTestInstance } from "react-test-renderer"; import { getByID, queryByID } from "./byID"; @@ -22,6 +23,7 @@ import { queryByType, queryParentByType, } from "./byType"; +import toHTML from "./toHTML"; import toJSON from "./toJSON"; type Func0 = () => R; @@ -68,6 +70,17 @@ export default function within(container: ReactTestInstance) { queryByType: applyContainer(container, queryByType), queryParentByType: applyContainer(container, queryParentByType), queryAllByType: applyContainer(container, queryAllByType), - toJSON: () => toJSON(container), + toJSON: applyContainer(container, toJSON), + toHTML: applyContainer(container, toHTML), + /** + * Check for some accessibility violations + * + * Example use: + * `expect(await within(container).axe()).toHaveNoViolations();` + */ + axe: () => axe(toHTML(container)), + /** Output the html representation of the container */ + // eslint-disable-next-line no-console + debug: () => console.log(toHTML(container, { pretty: true })), }; } diff --git a/src/core/client/stream/test/comments/featured/__snapshots__/renderFeaturedStream.spec.tsx.snap b/src/core/client/stream/test/comments/featured/__snapshots__/renderFeaturedStream.spec.tsx.snap index 0554ce6b3..6d4fd6d20 100644 --- a/src/core/client/stream/test/comments/featured/__snapshots__/renderFeaturedStream.spec.tsx.snap +++ b/src/core/client/stream/test/comments/featured/__snapshots__/renderFeaturedStream.spec.tsx.snap @@ -253,6 +253,75 @@ exports[`renders comment stream 1`] = ` className="TabBar-root TabBar-secondary coral coral-tabBarSecondary coral-tabBarComments StreamContainer-tabBarRoot" role="tablist" > +
+ +
+ + +
  • { within(testRenderer.root).getByTestID("comments-featuredComments-log") ); expect(within(testRenderer.root).toJSON()).toMatchSnapshot(); + expect(await within(testRenderer.root).axe()).toHaveNoViolations(); }); diff --git a/src/core/client/stream/test/comments/permalink/permalinkView.spec.tsx b/src/core/client/stream/test/comments/permalink/permalinkView.spec.tsx index 47d307f95..1b41857d1 100644 --- a/src/core/client/stream/test/comments/permalink/permalinkView.spec.tsx +++ b/src/core/client/stream/test/comments/permalink/permalinkView.spec.tsx @@ -93,6 +93,7 @@ it("renders permalink view", async () => { within(testRenderer.root).getByTestID("current-tab-pane") ); expect(within(tabPane).toJSON()).toMatchSnapshot(); + expect(await within(testRenderer.root).axe()).toHaveNoViolations(); }); it("show all comments", async () => { diff --git a/src/core/client/stream/test/comments/permalink/permalinkViewPostReply.spec.tsx b/src/core/client/stream/test/comments/permalink/permalinkViewPostReply.spec.tsx index c7434f77a..f29e98d1a 100644 --- a/src/core/client/stream/test/comments/permalink/permalinkViewPostReply.spec.tsx +++ b/src/core/client/stream/test/comments/permalink/permalinkViewPostReply.spec.tsx @@ -125,6 +125,8 @@ it("post a reply", async () => { }), }); + expect(await within(form).axe()).toHaveNoViolations(); + // Write reply . act(() => rte.props.onChange({ html: "Hello world!" })); act(() => { diff --git a/src/core/client/stream/test/comments/stream/editComment.spec.tsx b/src/core/client/stream/test/comments/stream/editComment.spec.tsx index 26096c89d..d8f914b61 100644 --- a/src/core/client/stream/test/comments/stream/editComment.spec.tsx +++ b/src/core/client/stream/test/comments/stream/editComment.spec.tsx @@ -100,6 +100,7 @@ it("edit a comment", async () => { .props.onClick() ); expect(within(comment).toJSON()).toMatchSnapshot("edit form"); + expect(await within(comment).axe()).toHaveNoViolations(); act(() => testRenderer.root diff --git a/src/core/client/stream/test/comments/stream/loadMore.spec.tsx b/src/core/client/stream/test/comments/stream/loadMore.spec.tsx index 095da8bca..d7a09250f 100644 --- a/src/core/client/stream/test/comments/stream/loadMore.spec.tsx +++ b/src/core/client/stream/test/comments/stream/loadMore.spec.tsx @@ -119,6 +119,8 @@ it("loads more comments", async () => { within(testRenderer.root).getByTestID("comments-allComments-log") ); + expect(await within(streamLog).axe()).toHaveNoViolations(); + // Get amount of comments before. const commentsBefore = within(streamLog).getAllByTestID(/^comment-/).length; diff --git a/src/core/client/stream/test/comments/stream/renderReplies.spec.tsx b/src/core/client/stream/test/comments/stream/renderReplies.spec.tsx index 95ed44782..7757d56ef 100644 --- a/src/core/client/stream/test/comments/stream/renderReplies.spec.tsx +++ b/src/core/client/stream/test/comments/stream/renderReplies.spec.tsx @@ -42,4 +42,5 @@ it("renders reply list", async () => { ); // Wait for loading. expect(within(commentReplyList).toJSON()).toMatchSnapshot(); + expect(await within(commentReplyList).axe()).toHaveNoViolations(); }); diff --git a/src/core/client/stream/test/comments/stream/renderStream.spec.tsx b/src/core/client/stream/test/comments/stream/renderStream.spec.tsx index 0e58a1ca5..9ee057d3e 100644 --- a/src/core/client/stream/test/comments/stream/renderStream.spec.tsx +++ b/src/core/client/stream/test/comments/stream/renderStream.spec.tsx @@ -45,4 +45,5 @@ it("renders comment stream", async () => { within(testRenderer.root).getByTestID("comments-allComments-log") ); expect(within(testRenderer.root).toJSON()).toMatchSnapshot(); + expect(await within(testRenderer.root).axe()).toHaveNoViolations(); }); diff --git a/src/core/client/stream/test/configure/renderConfigure.spec.tsx b/src/core/client/stream/test/configure/renderConfigure.spec.tsx index 8dad10eab..384a2c3ba 100644 --- a/src/core/client/stream/test/configure/renderConfigure.spec.tsx +++ b/src/core/client/stream/test/configure/renderConfigure.spec.tsx @@ -1,6 +1,6 @@ import sinon from "sinon"; -import { act, wait, waitForElement, within } from "coral-framework/testHelpers"; +import { act, waitForElement, within } from "coral-framework/testHelpers"; import { moderators, settings, stories } from "../fixtures"; import create from "./create"; @@ -42,11 +42,7 @@ async function createTestRenderer( } it("renders configure", async () => { - const { tabPane } = await createTestRenderer(); - - await act(async () => { - await wait(() => { - expect(within(tabPane).toJSON()).toMatchSnapshot(); - }); - }); + const { tabPane, testRenderer } = await createTestRenderer(); + expect(within(tabPane).toJSON()).toMatchSnapshot(); + expect(await within(testRenderer.root).axe()).toHaveNoViolations(); }); diff --git a/src/core/client/stream/test/profile/account.spec.tsx b/src/core/client/stream/test/profile/account.spec.tsx index 0b604a8be..3bd1469b9 100644 --- a/src/core/client/stream/test/profile/account.spec.tsx +++ b/src/core/client/stream/test/profile/account.spec.tsx @@ -67,6 +67,7 @@ it("renders the empty settings pane", async () => { testRenderer: { root }, } = await createTestRenderer(); expect(within(root).toJSON()).toMatchSnapshot(); + expect(await within(root).axe()).toHaveNoViolations(); }); it("doesn't show the change password pane when local auth is disabled", async () => { @@ -112,6 +113,7 @@ it("render password change form", async () => { const newPassword = await waitForElement(() => within(form).getByID("newPassword", { exact: false }) ); + expect(await within(changePassword).axe()).toHaveNoViolations(); // Submit an empty form. act(() => { diff --git a/src/core/client/stream/test/profile/changeEmail.spec.tsx b/src/core/client/stream/test/profile/changeEmail.spec.tsx index 965db7e17..01d1eac3a 100644 --- a/src/core/client/stream/test/profile/changeEmail.spec.tsx +++ b/src/core/client/stream/test/profile/changeEmail.spec.tsx @@ -9,7 +9,7 @@ import { within, } from "coral-framework/testHelpers"; -import { baseUser, settings, stories } from "../fixtures"; +import { settings, stories, userWithEmail } from "../fixtures"; import create from "./create"; const story = stories[0]; @@ -23,7 +23,7 @@ async function createTestRenderer( createResolversStub({ Query: { settings: () => settings, - viewer: () => baseUser, + viewer: () => userWithEmail, stream: () => story, }, }), @@ -49,7 +49,7 @@ describe("change email form", () => { const setup = await createTestRenderer({ resolvers: createResolversStub({ Query: { - viewer: () => baseUser, + viewer: () => userWithEmail, }, Mutation: { updateEmail: ({ variables }) => { @@ -58,7 +58,7 @@ describe("change email form", () => { }); return { user: { - ...baseUser, + ...userWithEmail, email: "updated_email@test.com", }, }; @@ -77,6 +77,7 @@ describe("change email form", () => { act(() => { editButton.props.onClick(); }); + expect(await within(changeEmail).axe()).toHaveNoViolations(); const form = within(changeEmail).getByType("form"); act(() => { form.props.onSubmit(); diff --git a/src/core/client/stream/test/profile/changeUsername.spec.tsx b/src/core/client/stream/test/profile/changeUsername.spec.tsx index fc0b1e983..ccd0e0a94 100644 --- a/src/core/client/stream/test/profile/changeUsername.spec.tsx +++ b/src/core/client/stream/test/profile/changeUsername.spec.tsx @@ -76,7 +76,9 @@ describe("with recently changed username", () => { const form = within(changeUsername).queryByType("form"); const message = within(changeUsername).queryByText( "Your username has been changed in the last 14 days", - { exact: false } + { + exact: false, + } ); expect(form).toBeNull(); expect(message).toBeTruthy(); @@ -106,10 +108,13 @@ describe("with new username", () => { act(() => { editButton.props.onClick(); }); + expect(await within(changeUsername).axe()).toHaveNoViolations(); within(changeUsername).getByType("form"); const message = within(changeUsername).queryByText( "Your username has been changed in the last 14 days", - { exact: false } + { + exact: false, + } ); expect(message).toBeNull(); }); diff --git a/src/core/client/stream/test/profile/deleteAccount.spec.tsx b/src/core/client/stream/test/profile/deleteAccount.spec.tsx index 01d2adcc8..0aca01813 100644 --- a/src/core/client/stream/test/profile/deleteAccount.spec.tsx +++ b/src/core/client/stream/test/profile/deleteAccount.spec.tsx @@ -109,6 +109,8 @@ describe("delete account steps", () => { nextButton.props.onClick(); }); } + + expect(await within(modal).axe()).toHaveNoViolations(); const form = within(modal).getByType("form"); const confirm = within(modal).getByTestID("confirm-page-confirmation"); const password = within(modal).getByTestID("confirm-page-password"); diff --git a/src/core/client/stream/test/profile/myComments.spec.tsx b/src/core/client/stream/test/profile/myComments.spec.tsx index e1de14d3d..5f2ab7f68 100644 --- a/src/core/client/stream/test/profile/myComments.spec.tsx +++ b/src/core/client/stream/test/profile/myComments.spec.tsx @@ -87,6 +87,7 @@ it("renders profile", async () => { within(testRenderer.root).getByTestID("profile-commentHistory") ); expect(within(commentHistory).toJSON()).toMatchSnapshot(); + expect(await within(commentHistory).axe()).toHaveNoViolations(); }); it("loads more comments", async () => { diff --git a/src/core/client/stream/test/profile/notificationSettings.spec.tsx b/src/core/client/stream/test/profile/notificationSettings.spec.tsx index acbf8c111..51bfebcfd 100644 --- a/src/core/client/stream/test/profile/notificationSettings.spec.tsx +++ b/src/core/client/stream/test/profile/notificationSettings.spec.tsx @@ -73,6 +73,7 @@ it("render notifications form", async () => { const container = await waitForElement(() => within(testRenderer.root).getByTestID("profile-account-notifications") ); + expect(await within(container).axe()).toHaveNoViolations(); const form = within(container).getByType("form"); // Get the form fields. diff --git a/src/core/client/test/setupTestFramework.ts b/src/core/client/test/setupTestFramework.ts index 879ed6001..1de7d4631 100644 --- a/src/core/client/test/setupTestFramework.ts +++ b/src/core/client/test/setupTestFramework.ts @@ -1,3 +1,5 @@ +import { toHaveNoViolations } from "jest-axe"; + import expectAndFail from "./expectAndFail"; // Automatically unmock console. @@ -17,3 +19,5 @@ process.on("unhandledRejection", err => { // eslint-disable-next-line no-console console.error(err); }); + +expect.extend(toHaveNoViolations); diff --git a/src/core/client/ui/components/Icon/Icon.tsx b/src/core/client/ui/components/Icon/Icon.tsx index facedf846..a1c936835 100644 --- a/src/core/client/ui/components/Icon/Icon.tsx +++ b/src/core/client/ui/components/Icon/Icon.tsx @@ -43,7 +43,7 @@ const Icon: FunctionComponent = props => { return (