Frontend components often include complex interactions and dynamic UI states that are difficult to validate manualy. The la-velada-web-oficial project—built with Astro—offers a practical case study for applying React Testing Library (RTL) to test interactive components, even within an Astro context. This guide demonstrates how to configure the testing environment, write robust test cases for rendering, user interaction, and state logic, and integrate tests into the development workflow.
Test Environment Setup
The project uses Jest (^30.2.0) as the test runner with ts-jest for TypeScript support. Although Astro components aren’t React components, their interactive parts (e.g., event listeners, DOM mutations) can still be tested using RTL by treating them as standard HTML elements rendered in a JSDOM environment.
After installing dependencies with:
pnpm install
Place test files alongside components using the naming convention [ComponentName].test.tsx.
Testing the BoxerCard Component
The BoxerCard.astro component renders fighter cards with hover effects, opponent highlighting, particle animations, and selection states. Below are key testing strategies applied to this component.
Basic Rendering Validation
Verify that the image source and alt text are correctly generated based on props:
import { render, screen } from '@testing-library/react';
test('displays correct image for fighter', () => {
document.body.innerHTML = `
<div class="boxer-card" data-id="ibai" data-versus="elxokas">
<img src="/images/fighters/cards/ibai.webp" alt="Tarjeta del boxeador Ibai" />
</div>
`;
const img = screen.getByAltText('Tarjeta del boxeador Ibai');
expect(img).toHaveAttribute('src', '/images/fighters/cards/ibai.webp');
});
Simulating Hover Interactions
When a user hovers over one card, the opposing card should be dimmed. This behavior is tested by simulating mouse events and asserting CSS classes:
import { fireEvent } from '@testing-library/react';
test('applies grayscale to versus card on hover', () => {
document.body.innerHTML = `
<div class="boxer-card" data-id="ibai" data-versus="elxokas"></div>
<div class="boxer-card" data-id="elxokas" data-versus="ibai"></div>
`;
const ibaiCard = document.querySelector('[data-id="ibai"]');
const elxokasCard = document.querySelector('[data-id="elxokas"]');
fireEvent.mouseEnter(ibaiCard);
expect(ibaiCard).not.toHaveClass('grayscale-100', 'opacity-40');
expect(elxokasCard).toHaveClass('grayscale-100', 'opacity-40');
fireEvent.mouseLeave(ibaiCard);
expect(ibaiCard).not.toHaveClass('grayscale-100', 'opacity-40');
expect(elxokasCard).not.toHaveClass('grayscale-100', 'opacity-40');
});
Verifying Dynamic Effects
Hovering triggers particle animations apepnded as child elements. Test this by comparing child counts before and after the event:
test('adds particle elements on hover', () => {
const card = document.createElement('div');
card.className = 'boxer-card';
document.body.appendChild(card);
const initialCount = card.children.length;
fireEvent.mouseEnter(card);
expect(card.children.length).toBeGreaterThan(initialCount);
});
Selection State and Animation
When selected, the card receives a selected class and a custom animation. Validate both:
test('applies selection styling', () => {
const card = document.createElement('div');
card.className = 'boxer-card';
document.body.appendChild(card);
card.classList.add('selected');
expect(card).toHaveClass('selected');
expect(getComputedStyle(card).animationName).toContain('selectedPulse');
});
Comprehensive Test Suite Example
A full test suite covers rendering, navigation, custom events, and联动 behaviors:
describe('BoxerCard interactions', () => {
beforeEach(() => {
document.body.innerHTML = `
<a href="/luchador/ibai" class="boxer-card" data-id="ibai" data-versus="elxokas">
<img alt="Tarjeta del boxeador Ibai" />
</a>
<div class="boxer-card" data-id="elxokas" data-versus="ibai">
<div class="versus-info translate-y-2">Details</div>
</div>
`;
});
test('navigates to fighter page', () => {
const link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/luchador/ibai');
});
test('dispatches custom hover event', () => {
const listener = jest.fn();
window.addEventListener('boxer-card-hovered', listener);
const card = document.querySelector('[data-id="ibai"]');
fireEvent.mouseEnter(card);
expect(listener).toHaveBeenCalledWith(
expect.objectContaining({
detail: { id: 'ibai' }
})
);
});
test('reveals versus info on hover', () => {
const ibaiCard = document.querySelector('[data-id="ibai"]');
const versusInfo = document.querySelector('.versus-info');
fireEvent.mouseEnter(ibaiCard);
expect(versusInfo).toHaveClass('translate-y-0');
fireEvent.mouseLeave(ibaiCard);
expect(versusInfo).toHaveClass('translate-y-2');
});
});
Test Automation and CI Integration
Add scripts to package.json:
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
Run tests with pnpm test. Integrate into GitHub Actions:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm test
Handling Common Challenges
Asynchronous Behavior: Use waitFor for delayed effects:
await waitFor(() => {
expect(document.querySelector('.particle')).toBeNull();
}, { timeout: 1000 });
CSS and Class Assertions: Perfer toHaveClass() over string matching. For inline styles, use getComputedStyle().
Third-party Mocks: While Astro’s built-in components don’t require mocking in DOM-based tests, any external JS logic (e.g., analytics) should be stubbed using Jest’s jest.mock().
By applying these techniques, developers can ensure that interactive components in Astro projects like la-velada-web-oficial remain reliable across changes. Similar approaches apply to other components such as ArtistCard.astro or CombatVersus.astro.
Project repository: https://gitcode.com/GitHub_Trending/la/la-velada-web-oficial