From e4263f590bc8ac38724b714476450bb921f34fae Mon Sep 17 00:00:00 2001 From: Ido Shamun <1993245+idoshamun@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:02:09 +0300 Subject: [PATCH 1/4] fix: keep button label stable during loading --- .../src/components/buttons/Button.spec.tsx | 26 +++++++++++++++++++ .../shared/src/components/buttons/Button.tsx | 9 +++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/components/buttons/Button.spec.tsx b/packages/shared/src/components/buttons/Button.spec.tsx index ed8829a9804..67079ede2e0 100644 --- a/packages/shared/src/components/buttons/Button.spec.tsx +++ b/packages/shared/src/components/buttons/Button.spec.tsx @@ -119,6 +119,32 @@ describe('Button', () => { expect(await screen.findByTestId('buttonLoader')).toBeInTheDocument(); }); + it('keeps the same label wrapper when toggling loading', () => { + const { rerender } = render( + , + ); + + const initialLabel = screen + .getByRole('button') + .querySelector('.btn-label'); + + expect(initialLabel).toBeInTheDocument(); + expect(initialLabel).not.toHaveClass('invisible'); + + rerender( + , + ); + + const loadingLabel = screen + .getByRole('button') + .querySelector('.btn-label'); + + expect(loadingLabel).toBe(initialLabel); + expect(loadingLabel).toHaveClass('invisible'); + }); + it('should set aria-pressed when pressed is true', async () => { renderComponent({ children: 'Button', pressed: true }); expect(await screen.findByRole('button')).toHaveAttribute( diff --git a/packages/shared/src/components/buttons/Button.tsx b/packages/shared/src/components/buttons/Button.tsx index 70b9f9f8b5e..c6971c143c5 100644 --- a/packages/shared/src/components/buttons/Button.tsx +++ b/packages/shared/src/components/buttons/Button.tsx @@ -83,7 +83,8 @@ function ButtonComponent( }: ButtonProps, ref?: Ref>, ): ReactElement { - const iconOnly = icon && isNullOrUndefined(children); + const hasChildren = !isNullOrUndefined(children); + const iconOnly = icon && !hasChildren; const getIconWithSize = useGetIconWithSize( size, iconOnly ?? false, @@ -126,7 +127,11 @@ function ButtonComponent( iconPosition, ) && getIconWithSize(icon, iconSecondaryOnHover ? isHovering : false)} - {loading ? {children} : children} + {hasChildren && ( + + {children} + + )} {icon && iconPosition === ButtonIconPosition.Right && getIconWithSize(icon, iconSecondaryOnHover ? isHovering : false)} From 4272cf4af86f575ffc61531415864269e78d8544 Mon Sep 17 00:00:00 2001 From: Ido Shamun <1993245+idoshamun@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:14:19 +0300 Subject: [PATCH 2/4] fix: format button regression test --- packages/shared/src/components/buttons/Button.spec.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/shared/src/components/buttons/Button.spec.tsx b/packages/shared/src/components/buttons/Button.spec.tsx index 67079ede2e0..616f6ebb96d 100644 --- a/packages/shared/src/components/buttons/Button.spec.tsx +++ b/packages/shared/src/components/buttons/Button.spec.tsx @@ -124,9 +124,7 @@ describe('Button', () => { , ); - const initialLabel = screen - .getByRole('button') - .querySelector('.btn-label'); + const initialLabel = screen.getByRole('button').querySelector('.btn-label'); expect(initialLabel).toBeInTheDocument(); expect(initialLabel).not.toHaveClass('invisible'); @@ -137,9 +135,7 @@ describe('Button', () => { , ); - const loadingLabel = screen - .getByRole('button') - .querySelector('.btn-label'); + const loadingLabel = screen.getByRole('button').querySelector('.btn-label'); expect(loadingLabel).toBe(initialLabel); expect(loadingLabel).toHaveClass('invisible'); From d9fffcffe25fbb19a29a483c7bc1dc1d30cb2e3f Mon Sep 17 00:00:00 2001 From: Ido Shamun <1993245+idoshamun@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:28:15 +0300 Subject: [PATCH 3/4] fix: update button-based test queries --- packages/shared/src/components/Feed.spec.tsx | 30 +++++++++---------- .../modals/user/CookieConsentModal.tsx | 24 +++++++++++---- .../widgets/BestDiscussions.spec.tsx | 3 +- .../FunnelPricing/FunnelPricingV2.spec.tsx | 14 ++------- packages/webapp/__tests__/PostPage.tsx | 4 +-- .../components/banner/CookieBanner.spec.tsx | 4 +-- 6 files changed, 40 insertions(+), 39 deletions(-) diff --git a/packages/shared/src/components/Feed.spec.tsx b/packages/shared/src/components/Feed.spec.tsx index fc0700b5ba4..790c1b82862 100644 --- a/packages/shared/src/components/Feed.spec.tsx +++ b/packages/shared/src/components/Feed.spec.tsx @@ -1172,15 +1172,14 @@ describe('Feed logged in', () => { 'Off-topic or wrong tags', ); fireEvent.click(irrelevantTagsBtn); - const submitBtn = await screen.findByText('Submit report'); + const submitBtn = await screen.findByRole('button', { + name: 'Submit report', + }); expect(submitBtn).toBeDisabled(); - const javascriptElements = await screen.findAllByText('#javascript'); - const javascriptBtn = javascriptElements.find( - (item) => item.tagName === 'BUTTON', - ); - fireEvent.click( - getRequiredValue(javascriptBtn, 'Expected javascript report tag button'), - ); + const javascriptBtn = await screen.findByRole('button', { + name: '#javascript', + }); + fireEvent.click(javascriptBtn); expect(submitBtn).toBeEnabled(); fireEvent.click(submitBtn); @@ -1211,14 +1210,13 @@ describe('Feed logged in', () => { 'Off-topic or wrong tags', ); fireEvent.click(irrelevantTagsBtn); - const submitBtn = await screen.findByText('Submit report'); - const javascriptElements = await screen.findAllByText('#javascript'); - const javascriptBtn = javascriptElements.find( - (item) => item.tagName === 'BUTTON', - ); - fireEvent.click( - getRequiredValue(javascriptBtn, 'Expected javascript report tag button'), - ); + const submitBtn = await screen.findByRole('button', { + name: 'Submit report', + }); + const javascriptBtn = await screen.findByRole('button', { + name: '#javascript', + }); + fireEvent.click(javascriptBtn); expect(submitBtn).toBeEnabled(); const brokenLinkBtn = await screen.findByText('Broken link'); diff --git a/packages/shared/src/components/modals/user/CookieConsentModal.tsx b/packages/shared/src/components/modals/user/CookieConsentModal.tsx index 1a09403c64f..d2f8ff0fb09 100644 --- a/packages/shared/src/components/modals/user/CookieConsentModal.tsx +++ b/packages/shared/src/components/modals/user/CookieConsentModal.tsx @@ -32,10 +32,8 @@ export const CookieConsentModal = ({ }: CookieConsentModalProps): ReactElement => { const { onRequestClose } = modalProps; - const onAcceptPreferences = (e: React.FormEvent) => { - e.preventDefault(); - // get the form - const formData = new FormData((e.target as HTMLInputElement).form); + const submitAcceptedPreferences = (form: HTMLFormElement) => { + const formData = new FormData(form); const formProps = Object.fromEntries(formData); const keys = Object.keys(formProps); const acceptedConsents = keys.filter( @@ -47,6 +45,11 @@ export const CookieConsentModal = ({ (option) => !acceptedConsents.includes(option), ); onAcceptCookies(acceptedConsents, rejectedConsents); + }; + + const onAcceptPreferences = (e: React.FormEvent) => { + e.preventDefault(); + submitAcceptedPreferences(e.currentTarget); onRequestClose(null); }; @@ -61,6 +64,17 @@ export const CookieConsentModal = ({ onRequestClose(e); }; + const onSavePreferences = (e: React.MouseEvent) => { + const { form } = e.currentTarget; + + if (!form) { + throw new Error('Cookie consent form is missing'); + } + + submitAcceptedPreferences(form); + onRequestClose(e); + }; + return ( { it('should set feeling lucky link to the first post', async () => { renderComponent(); - const el = await screen.findByText(`I'm feeling lucky`); - // eslint-disable-next-line testing-library/no-node-access + const el = await screen.findByRole('link', { name: `I'm feeling lucky` }); expect(el).toHaveAttribute('href', defaultPosts[0].commentsPermalink); }); diff --git a/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricingV2.spec.tsx b/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricingV2.spec.tsx index 19b605c7d57..dad0fbac5d0 100644 --- a/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricingV2.spec.tsx +++ b/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricingV2.spec.tsx @@ -240,18 +240,8 @@ const renderComponent = (props = {}, initialState: InitialState = {}) => { describe('FunnelPricingV2', () => { let dateMock: DateMock; const initialDate = new Date('2023-01-01T00:00:00Z'); - const getProceedButton = (): HTMLElement => { - const proceedButton = screen - .getAllByText('Get my plan') - .filter((button) => button.tagName === 'BUTTON') - .at(0); - - if (!proceedButton) { - throw new Error('Expected pricing CTA button'); - } - - return proceedButton; - }; + const getProceedButton = (): HTMLElement => + screen.getByRole('button', { name: 'Get my plan' }); const getMonthlyPlanRadio = (): HTMLElement => { const monthlyPlan = screen diff --git a/packages/webapp/__tests__/PostPage.tsx b/packages/webapp/__tests__/PostPage.tsx index 69533ad8188..d25d079b127 100644 --- a/packages/webapp/__tests__/PostPage.tsx +++ b/packages/webapp/__tests__/PostPage.tsx @@ -899,8 +899,8 @@ describe('downvote flow', () => { it('should prevent user to click block if no tags are selected', async () => { await prepareDownvote(); - const block = await screen.findByText('Block'); - expect(block?.disabled).toBe(true); + const block = await screen.findByRole('button', { name: 'Block' }); + expect(block.disabled).toBe(true); }); it('should display the option to never see the selection again if close panel', async () => { diff --git a/packages/webapp/components/banner/CookieBanner.spec.tsx b/packages/webapp/components/banner/CookieBanner.spec.tsx index 212e8b87d6b..20e92967345 100644 --- a/packages/webapp/components/banner/CookieBanner.spec.tsx +++ b/packages/webapp/components/banner/CookieBanner.spec.tsx @@ -55,7 +55,7 @@ describe('CookieBanner outside GDPR', () => { it('should render just a single button to accept all when outside GDPR coverage', async () => { renderComponent(); await nextTick(); - const el = await screen.findByText('I understand'); + const el = await screen.findByRole('button', { name: 'I understand' }); expect(el.tagName).toBe('BUTTON'); }); @@ -83,7 +83,7 @@ describe('CookieBanner outside GDPR', () => { renderComponent(); await screen.findByTestId('cookie_content'); - const button = await screen.findByText('I understand'); + const button = await screen.findByRole('button', { name: 'I understand' }); await act(() => fireEvent.click(button)); await nextTick(); const cookies = getCookies([GdprConsentKey.Necessary]); From 88b8ddb35da10f097dc0376b7005fd9e221d2c79 Mon Sep 17 00:00:00 2001 From: Ido Shamun <1993245+idoshamun@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:37:21 +0300 Subject: [PATCH 4/4] fix: handle duplicate pricing ctas in test --- .../onboarding/steps/FunnelPricing/FunnelPricingV2.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricingV2.spec.tsx b/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricingV2.spec.tsx index dad0fbac5d0..8427f2abfd7 100644 --- a/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricingV2.spec.tsx +++ b/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricingV2.spec.tsx @@ -241,7 +241,7 @@ describe('FunnelPricingV2', () => { let dateMock: DateMock; const initialDate = new Date('2023-01-01T00:00:00Z'); const getProceedButton = (): HTMLElement => - screen.getByRole('button', { name: 'Get my plan' }); + screen.getAllByRole('button', { name: 'Get my plan' })[0]; const getMonthlyPlanRadio = (): HTMLElement => { const monthlyPlan = screen