From 741c27e80e45cf66a2afb258624a4a3f70c41c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 12 Feb 2026 08:35:47 +0000 Subject: [PATCH 1/3] chore: add accessibility test --- src/helpers/__tests__/accessiblity.test.tsx | 263 +++++++++++++++++++- src/helpers/accessibility.ts | 5 - 2 files changed, 262 insertions(+), 6 deletions(-) diff --git a/src/helpers/__tests__/accessiblity.test.tsx b/src/helpers/__tests__/accessiblity.test.tsx index b768c84b..9868bcdd 100644 --- a/src/helpers/__tests__/accessiblity.test.tsx +++ b/src/helpers/__tests__/accessiblity.test.tsx @@ -4,9 +4,16 @@ import { Image, Pressable, Switch, Text, TextInput, TouchableOpacity, View } fro import { isHiddenFromAccessibility, isInaccessible, render, screen } from '../..'; import { computeAccessibleName, + computeAriaBusy, + computeAriaChecked, computeAriaDisabled, + computeAriaExpanded, computeAriaLabel, + computeAriaSelected, + computeAriaValue, + getRole, isAccessibilityElement, + normalizeRole, } from '../accessibility'; describe('isHiddenFromAccessibility', () => { @@ -280,6 +287,21 @@ describe('isHiddenFromAccessibility', () => { expect(isHiddenFromAccessibility(screen.getByTestId('subject'))).toBe(false); }); + test('uses cache when provided', async () => { + await render( + + + , + ); + const element = screen.getByTestId('subject', { includeHiddenElements: true }); + const cache = new WeakMap(); + + // First call populates the cache + isHiddenFromAccessibility(element, { cache }); + // Second call should use the cache + expect(isHiddenFromAccessibility(element, { cache })).toBe(false); + }); + test('has isInaccessible alias', () => { expect(isInaccessible).toBe(isHiddenFromAccessibility); }); @@ -373,6 +395,17 @@ describe('isAccessibilityElement', () => { expect(isAccessibilityElement(screen.getByTestId('false'))).toBeFalsy(); }); + test('matches Image component with alt prop', async () => { + await render( + + Test image + + , + ); + expect(isAccessibilityElement(screen.getByTestId('with-alt'))).toBeTruthy(); + expect(isAccessibilityElement(screen.getByTestId('without-alt'))).toBeFalsy(); + }); + test('returns false when given null', () => { expect(isAccessibilityElement(null)).toEqual(false); }); @@ -412,6 +445,33 @@ describe('computeAriaLabel', () => { expect(computeAriaLabel(screen.getByTestId('subject'))).toEqual('External Label'); }); + + test('supports accessibilityLabel', async () => { + await render(); + expect(computeAriaLabel(screen.getByTestId('subject'))).toEqual('Legacy Label'); + }); + + test('supports accessibilityLabelledBy', async () => { + await render( + + + + External + + , + ); + expect(computeAriaLabel(screen.getByTestId('subject'))).toEqual('External'); + }); + + test('supports Image with alt prop', async () => { + await render(Image Alt); + expect(computeAriaLabel(screen.getByTestId('subject'))).toEqual('Image Alt'); + }); + + test('returns undefined when aria-labelledby references non-existent element', async () => { + await render(); + expect(computeAriaLabel(screen.getByTestId('subject'))).toBeUndefined(); + }); }); describe('computeAriaDisabled', () => { @@ -482,6 +542,207 @@ describe('computeAriaDisabled', () => { }); }); +describe('getRole', () => { + test('returns explicit role from "role" prop', async () => { + await render(); + expect(getRole(screen.getByTestId('subject'))).toBe('button'); + }); + + test('returns explicit role from "accessibilityRole" prop', async () => { + await render(); + expect(getRole(screen.getByTestId('subject'))).toBe('link'); + }); + + test('prefers "role" over "accessibilityRole"', async () => { + await render(); + expect(getRole(screen.getByTestId('subject'))).toBe('button'); + }); + + test('returns "text" for Text elements', async () => { + await render(Hello); + expect(getRole(screen.getByTestId('subject'))).toBe('text'); + }); + + test('returns "none" for elements without explicit role', async () => { + await render(); + expect(getRole(screen.getByTestId('subject'))).toBe('none'); + }); + + test('normalizes "image" role to "img"', async () => { + await render(); + expect(getRole(screen.getByTestId('subject'))).toBe('img'); + }); +}); + +describe('normalizeRole', () => { + test('converts "image" to "img"', () => { + expect(normalizeRole('image')).toBe('img'); + }); + + test('passes through other roles unchanged', () => { + expect(normalizeRole('button')).toBe('button'); + expect(normalizeRole('link')).toBe('link'); + expect(normalizeRole('none')).toBe('none'); + }); +}); + +describe('computeAriaBusy', () => { + test('returns false by default', async () => { + await render(); + expect(computeAriaBusy(screen.getByTestId('subject'))).toBe(false); + }); + + test('supports aria-busy prop', async () => { + await render(); + expect(computeAriaBusy(screen.getByTestId('subject'))).toBe(true); + }); + + test('supports accessibilityState.busy', async () => { + await render(); + expect(computeAriaBusy(screen.getByTestId('subject'))).toBe(true); + }); +}); + +describe('computeAriaChecked', () => { + test('returns undefined for roles that do not support checked', async () => { + await render(); + expect(computeAriaChecked(screen.getByTestId('subject'))).toBeUndefined(); + }); + + test('supports aria-checked for checkbox role', async () => { + await render( + + + + + , + ); + expect(computeAriaChecked(screen.getByTestId('checked'))).toBe(true); + expect(computeAriaChecked(screen.getByTestId('unchecked'))).toBe(false); + expect(computeAriaChecked(screen.getByTestId('mixed'))).toBe('mixed'); + }); + + test('supports accessibilityState.checked for radio role', async () => { + await render( + , + ); + expect(computeAriaChecked(screen.getByTestId('subject'))).toBe(true); + }); + + test('supports Switch component value', async () => { + await render( + + + + , + ); + expect(computeAriaChecked(screen.getByTestId('on'))).toBe(true); + expect(computeAriaChecked(screen.getByTestId('off'))).toBe(false); + }); +}); + +describe('computeAriaExpanded', () => { + test('returns undefined by default', async () => { + await render(); + expect(computeAriaExpanded(screen.getByTestId('subject'))).toBeUndefined(); + }); + + test('supports aria-expanded prop', async () => { + await render( + + + + , + ); + expect(computeAriaExpanded(screen.getByTestId('expanded'))).toBe(true); + expect(computeAriaExpanded(screen.getByTestId('collapsed'))).toBe(false); + }); + + test('supports accessibilityState.expanded', async () => { + await render( + , + ); + expect(computeAriaExpanded(screen.getByTestId('subject'))).toBe(true); + }); +}); + +describe('computeAriaSelected', () => { + test('returns false by default', async () => { + await render(); + expect(computeAriaSelected(screen.getByTestId('subject'))).toBe(false); + }); + + test('supports aria-selected prop', async () => { + await render(); + expect(computeAriaSelected(screen.getByTestId('subject'))).toBe(true); + }); + + test('supports accessibilityState.selected', async () => { + await render( + , + ); + expect(computeAriaSelected(screen.getByTestId('subject'))).toBe(true); + }); +}); + +describe('computeAriaValue', () => { + test('returns empty values by default', async () => { + await render(); + expect(computeAriaValue(screen.getByTestId('subject'))).toEqual({ + min: undefined, + max: undefined, + now: undefined, + text: undefined, + }); + }); + + test('supports aria-value* props', async () => { + await render( + , + ); + expect(computeAriaValue(screen.getByTestId('subject'))).toEqual({ + min: 0, + max: 100, + now: 50, + text: '50%', + }); + }); + + test('supports accessibilityValue prop', async () => { + await render( + , + ); + expect(computeAriaValue(screen.getByTestId('subject'))).toEqual({ + min: 0, + max: 100, + now: 25, + text: '25%', + }); + }); + + test('aria-value* props take precedence over accessibilityValue', async () => { + await render( + , + ); + const value = computeAriaValue(screen.getByTestId('subject')); + expect(value.now).toBe(75); + expect(value.min).toBe(0); + }); +}); + describe('computeAccessibleName', () => { test('basic cases', async () => { await render( @@ -593,4 +854,4 @@ describe('computeAccessibleName', () => { expect(computeAccessibleName(screen.getByTestId('parent'))).toBe('Hello'); expect(computeAccessibleName(screen.getByTestId('parent-no-text'))).toBe(''); }); -}); +}); \ No newline at end of file diff --git a/src/helpers/accessibility.ts b/src/helpers/accessibility.ts index 2187d0c5..640b7dbc 100644 --- a/src/helpers/accessibility.ts +++ b/src/helpers/accessibility.ts @@ -53,11 +53,6 @@ export function isHiddenFromAccessibility( export const isInaccessible = isHiddenFromAccessibility; function isSubtreeInaccessible(element: HostElement): boolean { - // Null props can happen for React.Fragments - if (element.props == null) { - return false; - } - // See: https://reactnative.dev/docs/accessibility#aria-hidden if (element.props['aria-hidden']) { return true; From a3878e4c4ed2a86feabbde56a49ff246c0a3a4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 12 Feb 2026 08:46:50 +0000 Subject: [PATCH 2/3] naming --- .../__tests__/{accessiblity.test.tsx => accessibility.test.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/helpers/__tests__/{accessiblity.test.tsx => accessibility.test.tsx} (100%) diff --git a/src/helpers/__tests__/accessiblity.test.tsx b/src/helpers/__tests__/accessibility.test.tsx similarity index 100% rename from src/helpers/__tests__/accessiblity.test.tsx rename to src/helpers/__tests__/accessibility.test.tsx From 48b5e5dfeb1c16ccfe45fa687c8d9746e4233673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 12 Feb 2026 11:31:20 +0000 Subject: [PATCH 3/3] fix lint --- src/helpers/__tests__/accessibility.test.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/helpers/__tests__/accessibility.test.tsx b/src/helpers/__tests__/accessibility.test.tsx index 9868bcdd..39edcdd1 100644 --- a/src/helpers/__tests__/accessibility.test.tsx +++ b/src/helpers/__tests__/accessibility.test.tsx @@ -659,9 +659,7 @@ describe('computeAriaExpanded', () => { }); test('supports accessibilityState.expanded', async () => { - await render( - , - ); + await render(); expect(computeAriaExpanded(screen.getByTestId('subject'))).toBe(true); }); }); @@ -678,9 +676,7 @@ describe('computeAriaSelected', () => { }); test('supports accessibilityState.selected', async () => { - await render( - , - ); + await render(); expect(computeAriaSelected(screen.getByTestId('subject'))).toBe(true); }); }); @@ -716,10 +712,7 @@ describe('computeAriaValue', () => { test('supports accessibilityValue prop', async () => { await render( - , + , ); expect(computeAriaValue(screen.getByTestId('subject'))).toEqual({ min: 0, @@ -854,4 +847,4 @@ describe('computeAccessibleName', () => { expect(computeAccessibleName(screen.getByTestId('parent'))).toBe('Hello'); expect(computeAccessibleName(screen.getByTestId('parent-no-text'))).toBe(''); }); -}); \ No newline at end of file +});