אחד הדברים שהכי קשה להכיל במעבר ל react-testing-library הוא החשיבות הגדולה שמפתחי הספריה מעניקים ל Accessibility Roles. בעוד רוב ספריות הבדיקה נותנות לנו להגיע לאלמנטים באיזו צורה שאנחנו רוצים (דרך CSS או אפילו XPath), כשמגיעים ל react-testing-library מגלים ש getByCSS זו לא פונקציה אמיתית שם.
השינוי הטכני הזה נובע משינוי תפיסה לגבי תפקיד הבדיקה ומה אנחנו רוצים לבדוק: בעוד ספריות בדיקה מסורתיות רוצות לבדוק שדברים מגיעים ל DOM בצורה נכונה, ב react-testing-library אנחנו רוצים לבדוק יותר מזה, אנחנו רוצים לבדוק שאנחנו מבינים את העמוד שלנו כמו שמשתמש מבין אותו. המעבר ל getByRole מהווה שינוי בנקודת המבט.
שימו לב לקטע הבא לדוגמה שהצגתי בוובינר אתמול:
export default function SimpleList({ items }) {
const [filter, setFilter] = useState('');
return (
<>
<input
type="search"
value={filter}
onChange={(e) => setFilter(e.target.value)}
name="filter-list"
/>
<ul>
{items.filter(item => item.text.includes(filter)).map((item, idx) => (
<li
key={item.id}
className={(idx + 1) % 2 === 0 ? "even" : "odd"}
>
{item.text}
</li>
))}
</ul>
</>
);
}
בספריית בדיקות רגילה הייתי יכול להשתמש ב CSS כדי להגיע לאלמנט ה input ולמשל לקרוא ל:
document.querySelector('input[name="filter-list"]')
אבל במעבר ל react-testing-library ובפרט אם אני מוכן לקבל את השגעונות שלהם ולהשתמש ב getByRole אני אגלה שקשה לי לתפוס את אלמנט ה input הזה. הקוד הבא יעבוד:
const el = screen.getByRole('searchbox');
נו וזה עובד כל עוד יש תיבה אחת על המסך. הוספה של תיבה נוספת לקומפוננטה תשבור את הבדיקה, ואין לי דרך באמצעות getByRole לציין אף אחד מהמאפיינים של ה input כדי שיעזרו לי לבחור את האלמנט שאני רוצה.
המעבר ל getByRole מכריח אותי לחשוב כמו המשתמש - איך המשתמש יודע על איזו תיבה מדובר? איך המשתמש יודע איפה להכניס את הקלט? משתמש לא רואה את המאפיין name או את הקלאסים של התיבה. הוא יכול לראות את הטקסט שרשום בה אבל בדרך כלל הוא יראה את ה Label שמשויך לתיבת הקלט. וכבר אנחנו רואים את הבעיה בקומפוננטה: ל input אין label שמסביר מה היא עושה.
תיקון של הקוד והוספת label נראה כך:
export default function SimpleList({ items }) {
const [filter, setFilter] = useState('');
return (
<>
<label>Filter List
<input
type="search"
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
</label>
<ul>
{items.filter(item => item.text.includes(filter)).map((item, idx) => (
<li
key={item.id}
className={(idx + 1) % 2 === 0 ? "even" : "odd"}
>
{item.text}
</li>
))}
</ul>
</>
);
}
ושימו לב לקסם - עכשיו אפשר להשתמש בטקסט ב label כדי לזהות את ה input שהסתכלנו עליו:
screen.getByRole('searchbox', { name: 'Filter List' } );