diff --git a/cypress/component/Alerts.cy.tsx b/cypress/component/Alerts.cy.tsx
new file mode 100644
index 0000000000..90eb378a83
--- /dev/null
+++ b/cypress/component/Alerts.cy.tsx
@@ -0,0 +1,51 @@
+/*
+ * 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 { Alert } from '../../packages/ui'
+
+import '../support/component'
+import 'cypress-real-events'
+
+describe('', () => {
+ it('should have shadow by default', async () => {
+ cy.mount(
+
+ Success: Sample alert text.
+
+ )
+ cy.get('div[class$="-view-alert"]')
+ .should('have.css', 'box-shadow')
+ .and('not.equal', 'none')
+ })
+
+ it("shouldn't have shadow, when `hasShadow` is set to false", async () => {
+ cy.mount(
+
+ Success: Sample alert text.
+
+ )
+
+ cy.get('div[class$="-view-alert"]').should('have.css', 'box-shadow', 'none')
+ })
+})
diff --git a/package-lock.json b/package-lock.json
index 87fabc3ab1..fdf60031fe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41137,15 +41137,73 @@
"prop-types": "^15.8.1"
},
"devDependencies": {
+ "@instructure/ui-axe-check": "10.2.0",
"@instructure/ui-babel-preset": "10.2.0",
"@instructure/ui-color-utils": "10.2.0",
- "@instructure/ui-test-utils": "10.2.0"
+ "@instructure/ui-scripts": "10.2.0",
+ "@instructure/ui-test-utils": "10.2.0",
+ "@testing-library/jest-dom": "^6.4.6",
+ "@testing-library/react": "^15.0.7",
+ "@testing-library/user-event": "^14.5.2",
+ "vitest": "^2.0.2"
},
"peerDependencies": {
"react": ">=16.8 <=18",
"react-dom": ">=16.8 <=18"
}
},
+ "packages/ui-alerts/node_modules/@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "packages/ui-alerts/node_modules/@testing-library/react": {
+ "version": "15.0.7",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-15.0.7.tgz",
+ "integrity": "sha512-cg0RvEdD1TIhhkm1IeYMQxrzy0MtUNfa3minv4MjbgcYzJAZ7yD0i0lwoPOTPr+INtiXFezt2o8xMSnyHhEn2Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^10.0.0",
+ "@types/react-dom": "^18.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.0.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "packages/ui-alerts/node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
"packages/ui-avatar": {
"name": "@instructure/ui-avatar",
"version": "10.2.0",
diff --git a/packages/ui-alerts/package.json b/packages/ui-alerts/package.json
index a17b5fe1bb..e57393a1f1 100644
--- a/packages/ui-alerts/package.json
+++ b/packages/ui-alerts/package.json
@@ -23,9 +23,15 @@
},
"license": "MIT",
"devDependencies": {
+ "@instructure/ui-axe-check": "10.2.0",
"@instructure/ui-babel-preset": "10.2.0",
"@instructure/ui-color-utils": "10.2.0",
- "@instructure/ui-test-utils": "10.2.0"
+ "@instructure/ui-scripts": "10.2.0",
+ "@instructure/ui-test-utils": "10.2.0",
+ "@testing-library/user-event": "^14.5.2",
+ "@testing-library/jest-dom": "^6.4.6",
+ "@testing-library/react": "^15.0.7",
+ "vitest": "^2.0.2"
},
"dependencies": {
"@babel/runtime": "^7.24.5",
diff --git a/packages/ui-alerts/src/Alert/__new-tests__/Alert.test.tsx b/packages/ui-alerts/src/Alert/__new-tests__/Alert.test.tsx
new file mode 100644
index 0000000000..32a4339526
--- /dev/null
+++ b/packages/ui-alerts/src/Alert/__new-tests__/Alert.test.tsx
@@ -0,0 +1,275 @@
+/*
+ * 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 { render, screen, waitFor } from '@testing-library/react'
+import { vi } from 'vitest'
+import { runAxeCheck } from '@instructure/ui-axe-check'
+import '@testing-library/jest-dom'
+import userEvent from '@testing-library/user-event'
+
+import { Alert } from '../index'
+import type { AlertProps } from '../props'
+// eslint-disable-next-line no-restricted-imports
+import { generateA11yTests } from '@instructure/ui-scripts/lib/test/generateA11yTests'
+import AlertExamples from '../__examples__/Alert.examples'
+
+describe('', () => {
+ let srdiv: HTMLDivElement | null
+ let consoleWarningMock: ReturnType
+ let consoleErrorMock: ReturnType
+
+ beforeEach(async () => {
+ // Mocking console to prevent test output pollution and expect
+ consoleWarningMock = vi
+ .spyOn(console, 'warn')
+ .mockImplementation(() => {}) as any
+ consoleErrorMock = vi
+ .spyOn(console, 'error')
+ .mockImplementation(() => {}) as any
+ srdiv = document.createElement('div')
+ srdiv.id = '_alertLiveRegion'
+ srdiv.setAttribute('role', 'alert')
+ srdiv.setAttribute('aria-live', 'assertive')
+ srdiv.setAttribute('aria-relevant', 'additions text')
+ srdiv.setAttribute('aria-atomic', 'false')
+ document.body.appendChild(srdiv)
+ })
+
+ afterEach(async () => {
+ srdiv?.parentNode?.removeChild(srdiv)
+ srdiv = null
+ consoleWarningMock.mockRestore()
+ consoleErrorMock.mockRestore()
+ })
+
+ it('should render', async () => {
+ render(Success: Sample alert text.)
+ const text = screen.getByText('Success: Sample alert text.')
+ expect(text).toBeInTheDocument()
+ })
+
+ describe('with generated examples', () => {
+ const generatedComponents = generateA11yTests(Alert, AlertExamples)
+
+ for (const component of generatedComponents) {
+ it(component.description, async () => {
+ const { container } = render(component.content)
+ const axeCheck = await runAxeCheck(container)
+ expect(axeCheck).toBe(true)
+ })
+ }
+ })
+
+ it('should not render the Close button when `renderCloseButtonLabel` is not provided', async () => {
+ render(Success: Sample alert text.)
+ const closeButton = screen.queryByRole('button')
+
+ expect(closeButton).not.toBeInTheDocument()
+ })
+
+ it('should call `onDismiss` when the close button is clicked with renderCloseButtonLabel', async () => {
+ const onDismiss = vi.fn()
+ render(
+ Close}
+ onDismiss={onDismiss}
+ >
+ Success: Sample alert text.
+
+ )
+ const closeButton = screen.getByRole('button')
+
+ userEvent.click(closeButton)
+
+ await waitFor(() => {
+ expect(onDismiss).toHaveBeenCalled()
+ })
+ })
+
+ const iconComponentsVariants: Record<
+ NonNullable,
+ string
+ > = {
+ error: 'IconNo',
+ info: 'IconInfoBorderless',
+ success: 'IconCheckMark',
+ warning: 'IconWarningBorderless'
+ }
+
+ ;(
+ Object.entries(iconComponentsVariants) as [
+ NonNullable,
+ string
+ ][]
+ ).forEach(([variant, iconComponent]) => {
+ it(`"${variant}" variant should have icon "${iconComponent}".`, async () => {
+ const { container } = render(
+
+ Success: Sample alert text.
+
+ )
+ const icon = container.querySelector('svg[class$="-svgIcon"]')
+
+ expect(icon).toHaveAttribute('name', iconComponent)
+ })
+ })
+
+ it('should meet a11y standards', async () => {
+ const { container } = render(
+
+ Success: Sample alert text.
+
+ )
+ const axeCheck = await runAxeCheck(container)
+ expect(axeCheck).toBe(true)
+ })
+
+ it('should add alert text to aria live region, when present', async () => {
+ const liveRegion = document.getElementById('_alertLiveRegion')!
+ render(
+ liveRegion}
+ liveRegionPoliteness="polite"
+ >
+ Success: Sample alert text.
+
+ )
+
+ expect(liveRegion).toHaveTextContent('Success: Sample alert text.')
+ expect(liveRegion).toHaveAttribute('aria-live', 'polite')
+ })
+
+ describe('with `screenReaderOnly', () => {
+ it('should not render anything when using `liveRegion`', async () => {
+ const liveRegion = document.getElementById('_alertLiveRegion')!
+ const { container } = render(
+ liveRegion}
+ screenReaderOnly={true}
+ >
+ Success: Sample alert text. asdsfds
+
+ )
+
+ expect(container.children.length).toBe(0)
+ expect(liveRegion.children.length).toBe(1)
+ })
+
+ it('should warn if `liveRegion` is not defined', async () => {
+ const consoleWarningSpy = vi
+ .spyOn(console, 'error')
+ .mockImplementation(() => {})
+ const warning =
+ "Warning: [Alert] The 'screenReaderOnly' prop must be used in conjunction with 'liveRegion'."
+ render(
+
+ Success: Sample alert text.
+
+ )
+
+ await waitFor(() => {
+ expect(consoleWarningSpy.mock.calls[0][0]).toEqual(
+ expect.stringContaining(warning)
+ )
+ })
+ })
+
+ it('should set aria-atomic to the aria live region when isLiveRegionAtomic is present', async () => {
+ const liveRegion = document.getElementById('_alertLiveRegion')!
+ render(
+ liveRegion}
+ liveRegionPoliteness="polite"
+ isLiveRegionAtomic
+ >
+ Success: Sample alert text.
+
+ )
+
+ expect(liveRegion).toHaveTextContent('Success: Sample alert text.')
+ expect(liveRegion).toHaveAttribute('aria-atomic', 'true')
+ })
+
+ it('should close when told to, with transition', async () => {
+ const liveRegion = document.getElementById('_alertLiveRegion')!
+ const { rerender } = render(
+ liveRegion}>
+ Success: Sample alert text.
+
+ )
+
+ expect(liveRegion.children.length).toBe(1)
+
+ //set open to false
+ rerender(
+ liveRegion}>
+ Success: Sample alert text.
+
+ )
+
+ await waitFor(() => {
+ expect(liveRegion.children.length).toBe(0)
+ })
+ })
+
+ it('should close when told to, without transition', async () => {
+ const liveRegion = document.getElementById('_alertLiveRegion')!
+ const { rerender, container } = render(
+ liveRegion}
+ >
+ Success: Sample alert text.
+
+ )
+
+ expect(liveRegion.children.length).toBe(1)
+
+ //set open to false
+ rerender(
+ liveRegion}
+ >
+ Success: Sample alert text.
+
+ )
+
+ await waitFor(() => {
+ expect(container).not.toHaveTextContent('Success: Sample alert text.')
+ expect(liveRegion.children.length).toBe(0)
+ })
+ })
+ })
+})
diff --git a/packages/ui-alerts/src/Alert/__tests__/Alert.test.tsx b/packages/ui-alerts/src/Alert/__tests__/Alert.test.tsx
deleted file mode 100644
index 5dd57353b2..0000000000
--- a/packages/ui-alerts/src/Alert/__tests__/Alert.test.tsx
+++ /dev/null
@@ -1,278 +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,
- mount,
- stub,
- wait,
- within,
- generateA11yTests
-} from '@instructure/ui-test-utils'
-
-import { Alert } from '../index'
-import AlertExamples from '../__examples__/Alert.examples'
-import type { AlertProps } from '../props'
-
-describe('', async () => {
- let srdiv: HTMLDivElement | null
-
- beforeEach(async () => {
- stub(console, 'warn') // suppress deprecation warnings
- srdiv = document.createElement('div')
- srdiv.id = '_alertLiveRegion'
- srdiv.setAttribute('role', 'alert')
- srdiv.setAttribute('aria-live', 'assertive')
- srdiv.setAttribute('aria-relevant', 'additions text')
- srdiv.setAttribute('aria-atomic', 'false')
- document.body.appendChild(srdiv)
- })
-
- afterEach(async () => {
- srdiv?.parentNode?.removeChild(srdiv)
- srdiv = null
- })
-
- it('should render', async () => {
- const subject = await mount(
- Success: Sample alert text.
- )
-
- expect(subject.getDOMNode()).to.exist()
- })
-
- describe('with generated examples', async () => {
- generateA11yTests(Alert, AlertExamples)
- })
-
- it('should not render the Close button when `renderCloseButtonLabel` is not provided', async () => {
- const subject = await mount(
- Success: Sample alert text.
- )
-
- const alert = within(subject.getDOMNode())
- const closeButton = await alert.find(':focusable', {
- expectEmpty: true
- })
-
- expect(closeButton).to.not.exist()
- })
-
- // TODO fix test when new testing library is introduced
- // it('should call `onDismiss` when the close button is clicked with renderCloseButtonLabel', async () => {
- // const onDismiss = stub()
- // const subject = await mount(
- // Close}
- // onDismiss={onDismiss}
- // >
- // Success: Sample alert text.
- //
- // )
- //
- // const alert = within(subject.getDOMNode())
- // const closeButton = await alert.find(':focusable')
- //
- // await closeButton.click()
- // await wait(() => {
- // expect(onDismiss).to.have.been.called()
- // })
- // })
-
- const iconComponentsVariants: Record<
- NonNullable,
- string
- > = {
- error: 'IconNo',
- info: 'IconInfoBorderless',
- success: 'IconCheckMark',
- warning: 'IconWarningBorderless'
- }
-
- ;(
- Object.entries(iconComponentsVariants) as [
- NonNullable,
- string
- ][]
- ).forEach(([variant, iconComponent]) => {
- it(`"${variant}" variant should have icon "${iconComponent}".`, async () => {
- const subject = await mount(
-
- Success: Sample alert text.
-
- )
-
- const alert = within(subject.getDOMNode())
- const icon = await alert.find(`[name=${iconComponent}]`)
- expect(icon).to.exist()
- })
- })
-
- it('should meet a11y standards', async () => {
- const subject = await mount(
-
- Success: Sample alert text.
-
- )
-
- const alert = within(subject.getDOMNode())
- expect(await alert.accessible()).to.be.true()
- })
-
- it('should add alert text to aria live region, when present', async () => {
- const liver = document.getElementById('_alertLiveRegion')!
- await mount(
- liver}
- liveRegionPoliteness="polite"
- >
- Success: Sample alert text.
-
- )
-
- expect(liver.innerText).to.include('Success: Sample alert text.')
-
- expect(liver.getAttribute('aria-live')).to.equal('polite')
- })
-
- describe('with `screenReaderOnly', async () => {
- it('should not render anything when using `liveRegion`', async () => {
- const liver = document.getElementById('_alertLiveRegion')!
- await mount(
- liver}
- screenReaderOnly={true}
- >
- Success: Sample alert text.
-
- )
-
- const root = document.querySelector('[data-ui-test-utils]')!
-
- expect(root.children.length).to.equal(0)
- expect(liver.children.length).to.equal(1)
- })
-
- it('should warn if `liveRegion` is not defined', async () => {
- const consoleError = stub(console, 'error')
- const warning =
- "Warning: [Alert] The 'screenReaderOnly' prop must be used in conjunction with 'liveRegion'."
- await mount(
-
- Success: Sample alert text.
-
- )
- expect(consoleError).to.be.calledWith(warning)
- })
- })
-
- it('should set aria-atomic to the aria live region when isLiveRegionAtomic is present', async () => {
- const liver = document.getElementById('_alertLiveRegion')!
- await mount(
- liver}
- liveRegionPoliteness="polite"
- isLiveRegionAtomic
- >
- Success: Sample alert text.
-
- )
-
- expect(liver.innerText).to.include('Success: Sample alert text.')
- expect(liver.getAttribute('aria-atomic')).to.equal('true')
- })
-
- it('should close when told to, with transition', async () => {
- const liver = document.getElementById('_alertLiveRegion')!
- const subject = await mount(
- liver}>
- Success: Sample alert text.
-
- )
-
- expect(liver.children.length).to.equal(1)
-
- await subject.setProps({
- open: false
- })
-
- await wait(() => {
- expect(liver.children.length).to.equal(0)
- })
- })
-
- it('should close when told to, without transition', async () => {
- const liver = document.getElementById('_alertLiveRegion')!
- const subject = await mount(
- liver}>
- Success: Sample alert text.
-
- )
-
- expect(liver.children.length).to.equal(1)
-
- await subject.setProps({
- open: false
- })
-
- expect(subject.getDOMNode()).to.not.exist()
- expect(liver.children.length).to.equal(0)
- })
-
- it('should have shadow by default', async () => {
- const subject = await mount(
-
- Success: Sample alert text.
-
- )
-
- const alert = within(subject.getDOMNode()).getDOMNode()
- const computedStyle = getComputedStyle(alert)
-
- expect(computedStyle.boxShadow).to.equal(
- 'rgba(0, 0, 0, 0.1) 0px 3px 6px 0px, rgba(0, 0, 0, 0.16) 0px 3px 6px 0px'
- )
- })
-
- it("shouldn't have shadow, when `hasShadow` is set to false", async () => {
- const subject = await mount(
-
- Success: Sample alert text.
-
- )
-
- const alert = within(subject.getDOMNode()).getDOMNode()
- const computedStyle = getComputedStyle(alert)
-
- expect(computedStyle.boxShadow).to.equal('none')
- })
-})
diff --git a/packages/ui-alerts/tsconfig.build.json b/packages/ui-alerts/tsconfig.build.json
index 407eadaa05..bacfb19666 100644
--- a/packages/ui-alerts/tsconfig.build.json
+++ b/packages/ui-alerts/tsconfig.build.json
@@ -14,10 +14,12 @@
{ "path": "../emotion/tsconfig.build.json" },
{ "path": "../shared-types/tsconfig.build.json" },
{ "path": "../ui-a11y-content/tsconfig.build.json" },
+ { "path": "../ui-axe-check/tsconfig.build.json" },
{ "path": "../ui-buttons/tsconfig.build.json" },
{ "path": "../ui-icons/tsconfig.build.json" },
{ "path": "../ui-motion/tsconfig.build.json" },
{ "path": "../ui-react-utils/tsconfig.build.json" },
+ { "path": "../ui-scripts/tsconfig.build.json" },
{ "path": "../ui-themes/tsconfig.build.json" },
{ "path": "../ui-view/tsconfig.build.json" }
]