diff --git a/package-lock.json b/package-lock.json index 2500db4a6d..9e3f2e9acd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53918,10 +53918,13 @@ "prop-types": "^15.8.1" }, "devDependencies": { + "@instructure/ui-axe-check": "8.45.0", "@instructure/ui-babel-preset": "8.45.0", "@instructure/ui-color-utils": "8.45.0", "@instructure/ui-test-utils": "8.45.0", - "@instructure/ui-themes": "8.45.0" + "@instructure/ui-themes": "8.45.0", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^14.0.0" }, "peerDependencies": { "react": ">=16.8 <=18" @@ -59631,6 +59634,7 @@ "@babel/runtime": "^7.22.15", "@instructure/emotion": "8.45.0", "@instructure/shared-types": "8.45.0", + "@instructure/ui-axe-check": "8.45.0", "@instructure/ui-babel-preset": "8.45.0", "@instructure/ui-color-utils": "8.45.0", "@instructure/ui-react-utils": "8.45.0", @@ -59638,6 +59642,8 @@ "@instructure/ui-testable": "8.45.0", "@instructure/ui-themes": "8.45.0", "@instructure/ui-view": "8.45.0", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^14.0.0", "prop-types": "^15.8.1" } }, diff --git a/packages/ui-byline/package.json b/packages/ui-byline/package.json index a6b79457ed..754bc308fd 100644 --- a/packages/ui-byline/package.json +++ b/packages/ui-byline/package.json @@ -32,10 +32,13 @@ "prop-types": "^15.8.1" }, "devDependencies": { + "@instructure/ui-axe-check": "8.45.0", "@instructure/ui-babel-preset": "8.45.0", "@instructure/ui-color-utils": "8.45.0", "@instructure/ui-test-utils": "8.45.0", - "@instructure/ui-themes": "8.45.0" + "@instructure/ui-themes": "8.45.0", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^14.0.0" }, "peerDependencies": { "react": ">=16.8 <=18" diff --git a/packages/ui-byline/src/Byline/__new-tests__/Byline.test.tsx b/packages/ui-byline/src/Byline/__new-tests__/Byline.test.tsx new file mode 100644 index 0000000000..e4927f03ee --- /dev/null +++ b/packages/ui-byline/src/Byline/__new-tests__/Byline.test.tsx @@ -0,0 +1,157 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 - present Instructure, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { ComponentType } from 'react' +import { render, screen } from '@testing-library/react' +import '@testing-library/jest-dom/extend-expect' + +import { Byline } from '../index' +import { BylineProps } from '../props' +import { runAxeCheck } from '@instructure/ui-axe-check' +import { View } from '@instructure/ui-view' + +const TEST_TITLE = 'Test-title' +const TEST_DESCRIPTION = 'Test-description' +const TEST_HEADING = 'Test-heading' +const TEST_PARAGRAPH = 'Lorem Ipsum...' +const TEST_LINK = 'http://instructure-test.com' +const TEST_IMAGE = ( + +) + +const initProps = { + title: TEST_TITLE, + description: TEST_DESCRIPTION +} + +const renderByline = (props: Partial = { ...initProps }) => { + return render({TEST_IMAGE}) +} + +const originalOmitViewProps = View.omitViewProps + +describe('', () => { + beforeAll(() => { + // View component read Component.name instead of Component.displayName + // causing [undefined] in error messages + type BylineComponentType = ComponentType & { + name: 'Byline' + } + + View.omitViewProps = (props, Component) => { + const ModifiedComponent = { + ...Component, + name: 'Byline' + } as BylineComponentType + return originalOmitViewProps(props, ModifiedComponent) + } + }) + + afterAll(() => { + View.omitViewProps = originalOmitViewProps + }) + + it('should render', () => { + const { container } = renderByline() + + expect(container.firstChild).toBeInTheDocument() + }) + + it('should pass down div and its contents via the description property', () => { + const descriptionElement = ( +
+

+ {TEST_HEADING} +

+

{TEST_PARAGRAPH}

+
+ ) + renderByline({ description: descriptionElement }) + const clickableHeading = screen.getByText(TEST_HEADING) + const descriptionText = screen.getByText(TEST_PARAGRAPH) + + expect(clickableHeading.tagName).toBe('A') + expect(clickableHeading).toHaveAttribute('href', TEST_LINK) + expect(clickableHeading?.parentElement?.tagName).toBe('H2') + + expect(descriptionText).toBeInTheDocument() + expect(descriptionText.tagName).toBe('P') + }) + + it('should meet a11y standards', async () => { + const { container } = renderByline() + const axeCheck = await runAxeCheck(container) + + expect(axeCheck).toBe(true) + }) + + it(`should render a figure by default`, () => { + renderByline() + const figureElement = screen.getByRole('figure') + + expect(figureElement).toBeInTheDocument() + }) + + describe('when passing down props to View', () => { + let spyOnConsoleError: jest.SpyInstance + const customAllowedProps: { [key: string]: string } = { margin: 'small' } + const customIgnoredProps = ['elementRef', 'children'] + + const allProps = View.allowedProps as Array + const testPropsToAllow = Object.keys(customAllowedProps) + const testPropsToDisallow = allProps.filter((prop) => { + return !(prop in customAllowedProps) && !customIgnoredProps.includes(prop) + }) + + beforeEach(() => { + spyOnConsoleError = jest.spyOn(console, 'error').mockImplementation() + }) + + afterEach(() => { + spyOnConsoleError.mockRestore() + }) + + testPropsToAllow.forEach((prop) => { + it(`should allow the '${prop}' prop`, () => { + renderByline({ [prop]: customAllowedProps[prop] }) + + expect(spyOnConsoleError).not.toHaveBeenCalled() + }) + }) + + testPropsToDisallow.forEach((prop) => { + it(`should NOT allow the '${prop}' prop`, () => { + renderByline({ [prop]: 'foo' }) + const expectedWarningMessage = `Warning: [Byline] prop '${prop}' is not allowed.` + const warningMessage = spyOnConsoleError.mock.calls[0][0] + + expect(warningMessage).toBe(expectedWarningMessage) + expect(spyOnConsoleError).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/packages/ui-byline/src/Byline/__tests__/Byline.test.tsx b/packages/ui-byline/src/Byline/__tests__/Byline.test.tsx deleted file mode 100644 index 9eaa5f62d3..0000000000 --- a/packages/ui-byline/src/Byline/__tests__/Byline.test.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2015 - present Instructure, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' - -import { expect, find, mount, stub, within } from '@instructure/ui-test-utils' -import { Byline } from '../index' -import { View } from '@instructure/ui-view' - -describe('', async () => { - // eslint-disable-next-line max-len - const image = ( - - ) - - it('should render', async () => { - const subject = await mount( - - {image} - - ) - - expect(subject.getDOMNode()).to.exist() - }) - - it('should pass down div and its contents via the description property', async () => { - const description = ( -
-

- Clickable Heading -

-

Something here

-
- ) - - const subject = await mount( - {image} - ) - - const media = within(subject.getDOMNode()) - expect(await media.find(':contains(Clickable Heading)')).to.exist() - }) - - it('should meet a11y standards', async () => { - const subject = await mount( - - {image} - - ) - - const media = within(subject.getDOMNode()) - expect(await media.accessible()).to.be.true() - }) - - it(`should render a figure by default`, async () => { - expect(await mount({image})) - expect(await find('figure')).to.exist() - }) - - it(`should not allow the 'as' prop`, async () => { - const consoleError = stub(console, 'error') - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore intentionally wrong prop - await mount({image}) - expect(consoleError).to.be.calledWith( - "Warning: [Byline] prop 'as' is not allowed." - ) - }) - - describe('when passing down props to View', async () => { - const allowedProps: Record = { - margin: 'small' - } - - const ignoreProps = ['elementRef', 'children'] - - View.allowedProps - .filter((prop) => !ignoreProps.includes(prop)) - .forEach((prop) => { - if (Object.keys(allowedProps).indexOf(prop) < 0) { - it(`should NOT allow the '${prop}' prop`, async () => { - const consoleError = stub(console, 'error') - const warning = `Warning: [Byline] prop '${prop}' is not allowed.` - const props = { - [prop]: 'foo' - } - - await mount({image}) - expect(consoleError).to.be.calledWith(warning) - }) - } else { - it(`should allow the '${prop}' prop`, async () => { - const props = { [prop]: allowedProps[prop] } - const consoleError = stub(console, 'error') - - await mount({image}) - expect(consoleError).to.not.be.called() - }) - } - }) - }) -}) diff --git a/packages/ui-byline/tsconfig.build.json b/packages/ui-byline/tsconfig.build.json index 55c37f6eeb..c0d2e7233c 100644 --- a/packages/ui-byline/tsconfig.build.json +++ b/packages/ui-byline/tsconfig.build.json @@ -15,6 +15,7 @@ { "path": "../ui-babel-preset/tsconfig.build.json" }, { "path": "../ui-color-utils/tsconfig.build.json" }, { "path": "../ui-test-utils/tsconfig.build.json" }, - { "path": "../ui-themes/tsconfig.build.json" } + { "path": "../ui-themes/tsconfig.build.json" }, + { "path": "../ui-axe-check/tsconfig.build.json" } ] }