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