From 10c31a7cf192de5ff2a6fa877eed537bcec160ca Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Fri, 6 Sep 2024 07:36:09 +1000 Subject: [PATCH] Revert RAC Pending Button (#6986) Revert RAC Pending Button --- .../live-announcer/src/LiveAnnouncer.tsx | 83 +++++----- .../test/SearchAutocomplete.test.js | 6 +- .../color/test/ColorPicker.test.js | 8 +- .../combobox/test/ComboBox.test.js | 6 +- .../provider/test/Provider.test.tsx | 6 +- .../react-aria-components/docs/Button.mdx | 132 ---------------- packages/react-aria-components/package.json | 1 - packages/react-aria-components/src/Button.tsx | 147 +----------------- .../src/ToggleButton.tsx | 2 +- .../stories/Button.stories.tsx | 58 +------ .../stories/button-pending.css | 77 --------- .../react-aria-components/test/Button.test.js | 69 +------- yarn.lock | 1 - 13 files changed, 61 insertions(+), 535 deletions(-) delete mode 100644 packages/react-aria-components/stories/button-pending.css diff --git a/packages/@react-aria/live-announcer/src/LiveAnnouncer.tsx b/packages/@react-aria/live-announcer/src/LiveAnnouncer.tsx index 4812e1fec1a..ae35d8098db 100644 --- a/packages/@react-aria/live-announcer/src/LiveAnnouncer.tsx +++ b/packages/@react-aria/live-announcer/src/LiveAnnouncer.tsx @@ -15,20 +15,21 @@ type Assertiveness = 'assertive' | 'polite'; /* Inspired by https://github.com/AlmeroSteyn/react-aria-live */ const LIVEREGION_TIMEOUT_DELAY = 7000; +let liveAnnouncer: LiveAnnouncer | null = null; + /** * Announces the message using screen reader technology. */ export function announce( message: string, assertiveness: Assertiveness = 'assertive', - timeout = LIVEREGION_TIMEOUT_DELAY, - mode: 'message' | 'ids' = 'message' + timeout = LIVEREGION_TIMEOUT_DELAY ) { if (!liveAnnouncer) { liveAnnouncer = new LiveAnnouncer(); } - liveAnnouncer.announce(message, assertiveness, timeout, mode); + liveAnnouncer.announce(message, assertiveness, timeout); } /** @@ -57,36 +58,34 @@ export function destroyAnnouncer() { // is simple enough to implement without React, so that's what we do here. // See this discussion for more details: https://github.com/reactwg/react-18/discussions/125#discussioncomment-2382638 class LiveAnnouncer { - node: HTMLElement | null = null; - assertiveLog: HTMLElement | null = null; - politeLog: HTMLElement | null = null; + node: HTMLElement | null; + assertiveLog: HTMLElement; + politeLog: HTMLElement; constructor() { - if (typeof document !== 'undefined') { - this.node = document.createElement('div'); - this.node.dataset.liveAnnouncer = 'true'; - // copied from VisuallyHidden - Object.assign(this.node.style, { - border: 0, - clip: 'rect(0 0 0 0)', - clipPath: 'inset(50%)', - height: '1px', - margin: '-1px', - overflow: 'hidden', - padding: 0, - position: 'absolute', - width: '1px', - whiteSpace: 'nowrap' - }); - - this.assertiveLog = this.createLog('assertive'); - this.node.appendChild(this.assertiveLog); - - this.politeLog = this.createLog('polite'); - this.node.appendChild(this.politeLog); - - document.body.prepend(this.node); - } + this.node = document.createElement('div'); + this.node.dataset.liveAnnouncer = 'true'; + // copied from VisuallyHidden + Object.assign(this.node.style, { + border: 0, + clip: 'rect(0 0 0 0)', + clipPath: 'inset(50%)', + height: '1px', + margin: '-1px', + overflow: 'hidden', + padding: 0, + position: 'absolute', + width: '1px', + whiteSpace: 'nowrap' + }); + + this.assertiveLog = this.createLog('assertive'); + this.node.appendChild(this.assertiveLog); + + this.politeLog = this.createLog('polite'); + this.node.appendChild(this.politeLog); + + document.body.prepend(this.node); } createLog(ariaLive: string) { @@ -106,24 +105,18 @@ class LiveAnnouncer { this.node = null; } - announce(message: string, assertiveness = 'assertive', timeout = LIVEREGION_TIMEOUT_DELAY, mode: 'message' | 'ids' = 'message') { + announce(message: string, assertiveness = 'assertive', timeout = LIVEREGION_TIMEOUT_DELAY) { if (!this.node) { return; } let node = document.createElement('div'); - if (mode === 'message') { - node.textContent = message; - } else { - // To read an aria-labelledby, the element must have an appropriate role, such as img. - node.setAttribute('role', 'img'); - node.setAttribute('aria-labelledby', message); - } + node.textContent = message; if (assertiveness === 'assertive') { - this.assertiveLog?.appendChild(node); + this.assertiveLog.appendChild(node); } else { - this.politeLog?.appendChild(node); + this.politeLog.appendChild(node); } if (message !== '') { @@ -138,16 +131,12 @@ class LiveAnnouncer { return; } - if ((!assertiveness || assertiveness === 'assertive') && this.assertiveLog) { + if (!assertiveness || assertiveness === 'assertive') { this.assertiveLog.innerHTML = ''; } - if ((!assertiveness || assertiveness === 'polite') && this.politeLog) { + if (!assertiveness || assertiveness === 'polite') { this.politeLog.innerHTML = ''; } } } - -// singleton, setup immediately so that the DOM is primed for the first announcement as soon as possible -// Safari has a race condition where the first announcement is not read if we wait until the first announce call -let liveAnnouncer: LiveAnnouncer | null = new LiveAnnouncer(); diff --git a/packages/@react-spectrum/autocomplete/test/SearchAutocomplete.test.js b/packages/@react-spectrum/autocomplete/test/SearchAutocomplete.test.js index 553c7c38f6c..db922ee18e4 100644 --- a/packages/@react-spectrum/autocomplete/test/SearchAutocomplete.test.js +++ b/packages/@react-spectrum/autocomplete/test/SearchAutocomplete.test.js @@ -11,7 +11,7 @@ */ jest.mock('@react-aria/live-announcer'); -import {act, fireEvent, pointerMap, render, simulateDesktop, simulateMobile, waitFor, within} from '@react-spectrum/test-utils-internal'; +import {act, fireEvent, pointerMap, render, screen, simulateDesktop, simulateMobile, waitFor, within} from '@react-spectrum/test-utils-internal'; import {announce} from '@react-aria/live-announcer'; import {Button} from '@react-spectrum/button'; import Filter from '@spectrum-icons/workflow/Filter'; @@ -3228,9 +3228,7 @@ describe('SearchAutocomplete', function () { let listbox = getByRole('listbox'); expect(listbox).toBeVisible(); - expect(announce).toHaveBeenCalledTimes(2); - expect(announce).toHaveBeenNthCalledWith(1, '3 options available.'); - expect(announce).toHaveBeenNthCalledWith(2, 'One'); + expect(screen.getAllByRole('log')).toHaveLength(2); platformMock.mockRestore(); }); diff --git a/packages/@react-spectrum/color/test/ColorPicker.test.js b/packages/@react-spectrum/color/test/ColorPicker.test.js index f85a9091e77..81c1611ab42 100644 --- a/packages/@react-spectrum/color/test/ColorPicker.test.js +++ b/packages/@react-spectrum/color/test/ColorPicker.test.js @@ -40,7 +40,7 @@ describe('ColorPicker', function () { let button = getByRole('button'); expect(button).toHaveTextContent('Fill'); - expect(within(button).getByLabelText('vibrant red')).toBeInTheDocument(); + expect(within(button).getByRole('img')).toHaveAttribute('aria-label', 'vibrant red'); await user.click(button); @@ -67,7 +67,7 @@ describe('ColorPicker', function () { act(() => dialog.focus()); await user.keyboard('{Escape}'); act(() => {jest.runAllTimers();}); - expect(within(button).getByLabelText('dark vibrant blue')).toBeInTheDocument(); + expect(within(button).getByRole('img')).toHaveAttribute('aria-label', 'dark vibrant blue'); }); it('should have default value of black', async function () { @@ -81,7 +81,7 @@ describe('ColorPicker', function () { let button = getByRole('button'); expect(button).toHaveTextContent('Fill'); - expect(within(button).getByLabelText('black')).toBeInTheDocument(); + expect(within(button).getByRole('img')).toHaveAttribute('aria-label', 'black'); await user.click(button); @@ -132,6 +132,6 @@ describe('ColorPicker', function () { act(() => getByRole('dialog').focus()); await user.keyboard('{Escape}'); act(() => {jest.runAllTimers();}); - expect(within(button).getByLabelText('vibrant orange')).toBeInTheDocument(); + expect(within(button).getByRole('img')).toHaveAttribute('aria-label', 'vibrant orange'); }); }); diff --git a/packages/@react-spectrum/combobox/test/ComboBox.test.js b/packages/@react-spectrum/combobox/test/ComboBox.test.js index 4322c8104f1..6dd7e03003b 100644 --- a/packages/@react-spectrum/combobox/test/ComboBox.test.js +++ b/packages/@react-spectrum/combobox/test/ComboBox.test.js @@ -11,7 +11,7 @@ */ jest.mock('@react-aria/live-announcer'); -import {act, fireEvent, pointerMap, render, simulateDesktop, simulateMobile, waitFor, within} from '@react-spectrum/test-utils-internal'; +import {act, fireEvent, pointerMap, render, screen, simulateDesktop, simulateMobile, waitFor, within} from '@react-spectrum/test-utils-internal'; import {announce} from '@react-aria/live-announcer'; import {Button} from '@react-spectrum/button'; import {chain} from '@react-aria/utils'; @@ -5149,9 +5149,7 @@ describe('ComboBox', function () { let listbox = getByRole('listbox'); expect(listbox).toBeVisible(); - expect(announce).toHaveBeenCalledTimes(2); - expect(announce).toHaveBeenNthCalledWith(1, '3 options available.'); - expect(announce).toHaveBeenNthCalledWith(2, 'One'); + expect(screen.getAllByRole('log')).toHaveLength(2); platformMock.mockRestore(); }); diff --git a/packages/@react-spectrum/provider/test/Provider.test.tsx b/packages/@react-spectrum/provider/test/Provider.test.tsx index 663b0e486c3..85ccbaff1ed 100644 --- a/packages/@react-spectrum/provider/test/Provider.test.tsx +++ b/packages/@react-spectrum/provider/test/Provider.test.tsx @@ -10,11 +10,13 @@ * governing permissions and limitations under the License. */ -import {act, fireEvent, pointerMap, render} from '@react-spectrum/test-utils-internal'; +// needs to be imported first +// eslint-disable-next-line +import MatchMediaMock from 'jest-matchmedia-mock'; // eslint-disable-next-line rsp-rules/sort-imports +import {act, fireEvent, pointerMap, render} from '@react-spectrum/test-utils-internal'; import {ActionButton, Button} from '@react-spectrum/button'; import {Checkbox} from '@react-spectrum/checkbox'; -import MatchMediaMock from 'jest-matchmedia-mock'; import {Provider} from '../'; // eslint-disable-next-line rulesdir/useLayoutEffectRule import React, {useLayoutEffect, useRef} from 'react'; diff --git a/packages/react-aria-components/docs/Button.mdx b/packages/react-aria-components/docs/Button.mdx index 0aa0066bf4f..6c71a53319f 100644 --- a/packages/react-aria-components/docs/Button.mdx +++ b/packages/react-aria-components/docs/Button.mdx @@ -159,138 +159,6 @@ A `Button` can be disabled using the `isDisabled` prop. -## Pending - -A `Button` can be put into the pending state using the `isPending` prop. -Both a `Text` and [ProgressBar](ProgressBar.html) component are required to show the pending state correctly. -Make sure to internationalize the label you pass to the [ProgressBar](ProgressBar.html) component. - -```tsx example -import {useEffect, useRef, useState} from 'react'; -import {ProgressBar, Text} from 'react-aria-components'; - -function PendingButton(props) { - let [isPending, setPending] = useState(false); - - let timeout = useRef | undefined>(undefined); - let handlePress = (e) => { - setPending(true); - timeout.current = setTimeout(() => { - setPending(false); - timeout.current = undefined; - }, 5000); - }; - - useEffect(() => { - return () => { - clearTimeout(timeout.current); - }; - }, []); - - return ( - - ); -} - -``` - -
- Show CSS - -```css -@keyframes load { - 99% { - visibility: hidden; - } - - 100% { - visibility: visible; - } -} - -@keyframes hidden { - 99% { - visibility: visible; - } - - 100% { - visibility: hidden; - } -} - -.react-aria-Button { - position: relative; -} - -.spinner { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - visibility: hidden; -} -.spinner-pending { - animation: 1s load; - animation-fill-mode: forwards; -} - -.pending { - animation: 1s hidden; - animation-fill-mode: forwards; - visibility: visible; -} - -.loader { - width: 20px; - height: 20px; - border: 3px solid var(--background-color); - border-radius: 50%; - display: inline-block; - position: relative; - box-sizing: border-box; - animation: rotation 1s linear infinite; -} -.loader::after { - content: ''; - box-sizing: border-box; - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - width: 20px; - height: 20px; - border-radius: 50%; - border: 3px solid; - border-color: var(--purple-400) transparent; -} - -@keyframes rotation { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} -``` - -
- ## Link buttons The `Button` component always represents a button semantically. To create a link that visually looks like a button, use the [Link](Link.html) component instead. You can reuse the same styles you apply to the `Button` component on the `Link`. diff --git a/packages/react-aria-components/package.json b/packages/react-aria-components/package.json index a3bd84757b8..46a2b6b604a 100644 --- a/packages/react-aria-components/package.json +++ b/packages/react-aria-components/package.json @@ -44,7 +44,6 @@ "@react-aria/dnd": "^3.7.2", "@react-aria/focus": "^3.18.2", "@react-aria/interactions": "^3.22.2", - "@react-aria/live-announcer": "^3.3.4", "@react-aria/menu": "^3.15.3", "@react-aria/toolbar": "3.0.0-beta.8", "@react-aria/tree": "3.0.0-alpha.5", diff --git a/packages/react-aria-components/src/Button.tsx b/packages/react-aria-components/src/Button.tsx index 4a4a2a78402..1e057dc9b35 100644 --- a/packages/react-aria-components/src/Button.tsx +++ b/packages/react-aria-components/src/Button.tsx @@ -9,31 +9,11 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ - -import {announce} from '@react-aria/live-announcer'; -import { - AriaButtonProps, - HoverEvents, - mergeProps, - useButton, - useFocusRing, - useHover, - useId, - VisuallyHidden -} from 'react-aria'; -import { - ContextValue, - Provider, - RenderProps, - SlotProps, - useContextProps, - useRenderProps -} from './utils'; +import {AriaButtonProps, HoverEvents, mergeProps, useButton, useFocusRing, useHover} from 'react-aria'; +import {ContextValue, RenderProps, SlotProps, useContextProps, useRenderProps} from './utils'; import {createHideableComponent} from '@react-aria/collections'; -import {filterDOMProps, isAppleDevice, isFirefox} from '@react-aria/utils'; -import {ProgressBarContext} from './ProgressBar'; -import React, {createContext, ForwardedRef, useCallback, useEffect, useRef} from 'react'; -import {TextContext} from './Text'; +import {filterDOMProps} from '@react-aria/utils'; +import React, {createContext, ForwardedRef} from 'react'; export interface ButtonRenderProps { /** @@ -60,12 +40,7 @@ export interface ButtonRenderProps { * Whether the button is disabled. * @selector [data-disabled] */ - isDisabled: boolean, - /** - * If the button is currently in the `isPending` state. - * @selector [data-pending] - */ - isPending?: boolean + isDisabled: boolean } export interface ButtonProps extends Omit, HoverEvents, SlotProps, RenderProps { @@ -90,11 +65,7 @@ export interface ButtonProps extends Omit) { [props, ref] = useContextProps(props, ref, ButtonContext); - props = disablePendingProps(props); let ctx = props as ButtonContextValue; - let {isPending} = ctx; let {buttonProps, isPressed} = useButton(props, ref); let {focusProps, isFocused, isFocusVisible} = useFocusRing(props); let {hoverProps, isHovered} = useHover(props); - let renderValues = {isHovered, isPressed, isFocused, isFocusVisible, isDisabled: props.isDisabled || false, isPending}; let renderProps = useRenderProps({ ...props, - values: renderValues, + values: {isHovered, isPressed, isFocused, isFocusVisible, isDisabled: props.isDisabled || false}, defaultClassName: 'react-aria-Button' }); - // an aria label will block children and their labels from being read, this is undesirable for pending state - let hasAriaLabel = !!buttonProps['aria-label'] || !!buttonProps['aria-labelledby']; - let safariDupeLabellingId = useId(); - let buttonId = useId(buttonProps.id); - let contentId = useId(); - let progressId = useId(); - - const isPendingAriaLiveLabel = `${hasAriaLabel ? buttonProps['aria-label'] : ''}`.trim(); - let isPendingAriaLiveLabelledby = hasAriaLabel ? (buttonProps['aria-labelledby']?.replace(buttonId, safariDupeLabellingId) ?? safariDupeLabellingId) : `${contentId} ${safariDupeLabellingId}`.trim(); - isPendingAriaLiveLabelledby = isPendingAriaLiveLabelledby + ' ' + progressId; - - let ariaLive: 'off' | 'polite' | 'assertive' = 'polite'; - if (isAppleDevice() && (!hasAriaLabel || isFirefox())) { - ariaLive = 'off'; - } - - let {textCallbackRef, progressCallbackRef} = useEnforcePendingComponents({...props, ref}); - - // Forcibly announce the pending state on Apple devices because otherwise it won't be announced - let wasPending = useRef(isPending); - useEffect(() => { - if (!wasPending.current && isFocused && isPending && isAppleDevice()) { - announce(isPendingAriaLiveLabelledby, 'assertive', undefined, 'ids'); - } else if (wasPending.current && isFocused && !isPending && isAppleDevice()) { - announce(buttonId, 'assertive', undefined, 'ids'); - } - wasPending.current = isPending; - }, [isPending, isFocused, isPendingAriaLiveLabelledby, buttonId]); - return ( + data-focus-visible={isFocusVisible || undefined} /> ); } -function useEnforcePendingComponents(props) { - let {isPending, ref} = props; - let textRef = useRef(null); - let progressRef = useRef(null); - let textCallbackRef = useCallback(node => { - textRef.current = node; - // use a microtask so the unmounting has time to complete before we throw an error - queueMicrotask(() => { - if (!textRef.current && ref.current && isPending) { - throw new Error('Expected to be used with pending button'); - } - }); - }, [isPending, ref]); - let progressCallbackRef = useCallback(node => { - progressRef.current = node; - queueMicrotask(() => { - if (!progressRef.current && ref.current && isPending) { - throw new Error('Expected to be used with pending button'); - } - }); - }, [isPending, ref]); - useEffect(() => { - if (isPending && (!textRef.current || !progressRef.current)) { - throw new Error('Expected and to be used with pending button'); - } - }, [isPending, textRef, progressRef]); - return {textCallbackRef, progressCallbackRef}; -} - -function disablePendingProps(props) { - // Don't allow interaction while isPending is true - if (props.isPending) { - props.onPress = undefined; - props.onPressStart = undefined; - props.onPressEnd = undefined; - props.onPressChange = undefined; - props.onPressUp = undefined; - props.onKeyDown = undefined; - props.onKeyUp = undefined; - props.onClick = undefined; - props.href = undefined; - } - return props; -} - /** * A button allows a user to perform an action, with mouse, touch, and keyboard interactions. */ diff --git a/packages/react-aria-components/src/ToggleButton.tsx b/packages/react-aria-components/src/ToggleButton.tsx index 6d822066d33..3cf1c82848d 100644 --- a/packages/react-aria-components/src/ToggleButton.tsx +++ b/packages/react-aria-components/src/ToggleButton.tsx @@ -17,7 +17,7 @@ import {forwardRefType} from '@react-types/shared'; import React, {createContext, ForwardedRef, forwardRef} from 'react'; import {ToggleState, useToggleState} from 'react-stately'; -export interface ToggleButtonRenderProps extends Omit { +export interface ToggleButtonRenderProps extends ButtonRenderProps { /** * Whether the button is currently selected. * @selector [data-selected] diff --git a/packages/react-aria-components/stories/Button.stories.tsx b/packages/react-aria-components/stories/Button.stories.tsx index 87b105e2b5e..605b568ab5b 100644 --- a/packages/react-aria-components/stories/Button.stories.tsx +++ b/packages/react-aria-components/stories/Button.stories.tsx @@ -10,12 +10,10 @@ * governing permissions and limitations under the License. */ -import {action} from '@storybook/addon-actions'; -import {Button, ProgressBar, Text} from 'react-aria-components'; +import {Button} from 'react-aria-components'; import {mergeProps} from '@react-aria/utils'; import React, {useEffect, useRef, useState} from 'react'; import * as styles from './button-ripple.css'; -import * as styles2 from './button-pending.css'; export default { title: 'React Aria Components' @@ -27,60 +25,6 @@ export const ButtonExample = () => { ); }; -export const PendingButton = { - render: (args) => , - args: { - children: 'Press me' - } -}; - -function PendingButtonExample(props) { - let [isPending, setPending] = useState(false); - - let timeout = useRef | undefined>(undefined); - let handlePress = (e) => { - action('pressed')(e); - setPending(true); - timeout.current = setTimeout(() => { - setPending(false); - timeout.current = undefined; - }, 5000); - }; - - useEffect(() => { - return () => { - clearTimeout(timeout.current); - }; - }, []); - - let [hideOthers, setHideOthers] = useState(false); - - return ( - <> - {!hideOthers && ( - - )} - - - ); -} - export const RippleButtonExample = () => { return ( Press me diff --git a/packages/react-aria-components/stories/button-pending.css b/packages/react-aria-components/stories/button-pending.css deleted file mode 100644 index 7ff784cc7f2..00000000000 --- a/packages/react-aria-components/stories/button-pending.css +++ /dev/null @@ -1,77 +0,0 @@ -@keyframes load { - 99% { - visibility: hidden; - } - - 100% { - visibility: visible; - } -} - -@keyframes hidden { - 99% { - visibility: visible; - } - - 100% { - visibility: hidden; - } -} - -.button { - position: relative; - height: 20px; - display: flex; - align-items: start; -} - -.spinner { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - visibility: hidden; -} -.spinner-pending { - animation: 1s load; - animation-fill-mode: forwards; -} - -.pending { - animation: 1s hidden; - animation-fill-mode: forwards; - visibility: visible; -} - -.loader { - width: 20px; - height: 20px; - border: 3px solid #FFF; - border-radius: 50%; - display: inline-block; - position: relative; - box-sizing: border-box; - animation: rotation 1s linear infinite; -} -.loader::after { - content: ''; - box-sizing: border-box; - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - width: 18px; - height: 18px; - border-radius: 50%; - border: 3px solid; - border-color: #FF3D00 transparent; -} - -@keyframes rotation { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/packages/react-aria-components/test/Button.test.js b/packages/react-aria-components/test/Button.test.js index 3c45ba3084c..d0d29121b55 100644 --- a/packages/react-aria-components/test/Button.test.js +++ b/packages/react-aria-components/test/Button.test.js @@ -10,16 +10,15 @@ * governing permissions and limitations under the License. */ -import {act, fireEvent, pointerMap, render} from '@react-spectrum/test-utils-internal'; -import {Button, ButtonContext, ProgressBar, Text} from '../'; -import React, {useState} from 'react'; +import {Button, ButtonContext} from '../'; +import {fireEvent, pointerMap, render} from '@react-spectrum/test-utils-internal'; +import React from 'react'; import userEvent from '@testing-library/user-event'; describe('Button', () => { let user; beforeAll(() => { user = userEvent.setup({delay: null, pointerMap}); - jest.useFakeTimers(); }); it('should render a button with default class', () => { @@ -138,66 +137,4 @@ describe('Button', () => { fireEvent.mouseUp(button); expect(button).toHaveTextContent('Test'); }); - - // isPending state - it('displays a spinner after a short delay when isPending prop is true', async function () { - let onPressSpy = jest.fn(); - function TestComponent() { - let [pending, setPending] = useState(false); - return ( - - ); - } - let {getByRole} = render(); - let button = getByRole('button'); - expect(button).not.toHaveAttribute('aria-disabled'); - await user.click(button); - // Button is disabled immediately, but spinner visibility is delayed - expect(button).toHaveAttribute('aria-disabled', 'true'); - // Multiple clicks shouldn't call onPressSpy - await user.click(button); - act(() => { - jest.advanceTimersByTime(1000); - }); - expect(button).toHaveAttribute('aria-disabled', 'true'); - expect(onPressSpy).toHaveBeenCalledTimes(1); - }); - - // isPending anchor element - it('removes href attribute from anchor element when isPending is true', () => { - let {getByRole} = render( - - ); - let button = getByRole('button'); - expect(button).not.toHaveAttribute('href'); - }); }); diff --git a/yarn.lock b/yarn.lock index 7252ecdf402..3dac3fa9cf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28718,7 +28718,6 @@ __metadata: "@react-aria/dnd": "npm:^3.7.2" "@react-aria/focus": "npm:^3.18.2" "@react-aria/interactions": "npm:^3.22.2" - "@react-aria/live-announcer": "npm:^3.3.4" "@react-aria/menu": "npm:^3.15.3" "@react-aria/toolbar": "npm:3.0.0-beta.8" "@react-aria/tree": "npm:3.0.0-alpha.5"