Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions e2e/browser-mode/browserReact.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from '@rstest/core';
import { runBrowserCli } from './utils';

describe('browser mode - @rstest/browser-react', () => {
it('should work with render, renderHook, cleanup, and @testing-library/dom', async () => {
const { expectExecSuccess, cli } = await runBrowserCli('browser-react');
await expectExecSuccess();

// Verify all test files ran
expect(cli.stdout).toContain('render.test.tsx');
expect(cli.stdout).toContain('renderHook.test.tsx');
expect(cli.stdout).toContain('cleanup.test.tsx');
expect(cli.stdout).toContain('testingLibraryDom.test.tsx');

// Verify tests passed
expect(cli.stdout).toMatch(/Tests.*passed/);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default defineConfig({
browser: {
enabled: true,
headless: true,
port: BROWSER_PORTS.react,
port: BROWSER_PORTS['browser-react'],
},
include: ['tests/**/*.test.tsx'],
testTimeout: 30000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface ButtonProps {
children: ReactNode;
}

export const Button = ({ onClick, children }: ButtonProps) => {
export const Button = ({ onClick, children }: ButtonProps): JSX.Element => {
return (
<button type="button" className="btn" onClick={onClick}>
{children}
Expand All @@ -18,7 +18,10 @@ interface CounterProps {
title?: string;
}

export const Counter = ({ initialCount = 0, title }: CounterProps) => {
export const Counter = ({
initialCount = 0,
title,
}: CounterProps): JSX.Element => {
const [count, setCount] = useState(initialCount);

return (
Expand All @@ -31,13 +34,11 @@ export const Counter = ({ initialCount = 0, title }: CounterProps) => {
);
};

export const App = () => {
export const App = (): JSX.Element => {
return (
<div className="app">
<h1>React Browser Test</h1>
<p data-testid="description">
Testing @testing-library/react in browser mode
</p>
<p data-testid="description">Testing @rstest/browser-react</p>
<Counter />
</div>
);
Expand Down
20 changes: 20 additions & 0 deletions e2e/browser-mode/fixtures/browser-react/src/useCounter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useState } from 'react';

/**
* A custom hook for testing renderHook
*/
export function useCounter(initialValue = 0): {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
} {
const [count, setCount] = useState(initialValue);

return {
count,
increment: () => setCount((c) => c + 1),
decrement: () => setCount((c) => c - 1),
reset: () => setCount(initialValue),
};
}
42 changes: 42 additions & 0 deletions e2e/browser-mode/fixtures/browser-react/tests/cleanup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { cleanup, render } from '@rstest/browser-react/pure';
import { describe, expect, it } from '@rstest/core';
import { Counter } from '../src/App';

describe('@rstest/browser-react cleanup', () => {
it('should cleanup all mounted components', async () => {
// Render multiple components
await render(<Counter initialCount={1} />);
await render(<Counter initialCount={2} />);
await render(<Counter initialCount={3} />);

// All should be in the DOM
const counters = document.querySelectorAll('.counter');
expect(counters.length).toBe(3);

// Cleanup all
await cleanup();

// All should be removed
const countersAfter = document.querySelectorAll('.counter');
expect(countersAfter.length).toBe(0);
});

it('should allow rendering after cleanup', async () => {
const { container } = await render(<Counter initialCount={10} />);
expect(container.querySelector('[data-testid="count"]')?.textContent).toBe(
'10',
);

await cleanup();

// Should be able to render again
const { container: container2 } = await render(
<Counter initialCount={20} />,
);
expect(container2.querySelector('[data-testid="count"]')?.textContent).toBe(
'20',
);

await cleanup();
});
});
99 changes: 99 additions & 0 deletions e2e/browser-mode/fixtures/browser-react/tests/render.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { render } from '@rstest/browser-react';
import { describe, expect, it } from '@rstest/core';
import { App, Button, Counter } from '../src/App';

describe('@rstest/browser-react render', () => {
it('should render App component correctly', async () => {
const { container } = await render(<App />);

expect(container.querySelector('h1')?.textContent).toBe(
'React Browser Test',
);
expect(
container.querySelector('[data-testid="description"]')?.textContent,
).toBe('Testing @rstest/browser-react');
});

it('should render Button with children', async () => {
const { container } = await render(<Button>Click me</Button>);

const button = container.querySelector('button');
expect(button).toBeTruthy();
expect(button?.textContent).toBe('Click me');
expect(button?.className).toBe('btn');
});

it('should render Counter with initial value', async () => {
const { container } = await render(<Counter initialCount={5} />);

const countDisplay = container.querySelector('[data-testid="count"]');
expect(countDisplay?.textContent).toBe('5');
});

it('should handle unmount correctly', async () => {
const { container, unmount } = await render(<App />);

expect(container.querySelector('h1')).toBeTruthy();

await unmount();

// After unmount, container should be empty
expect(container.innerHTML).toBe('');
});

it('should handle rerender correctly', async () => {
const { container, rerender } = await render(
<Counter initialCount={0} title="First" />,
);

expect(
container.querySelector('[data-testid="counter-title"]')?.textContent,
).toBe('First');

await rerender(<Counter initialCount={10} title="Second" />);

expect(
container.querySelector('[data-testid="counter-title"]')?.textContent,
).toBe('Second');
// Note: initialCount only affects initial state, so count won't change on rerender
});

it('should return correct asFragment', async () => {
const { asFragment } = await render(<Button>Test</Button>);

const fragment = asFragment();
expect(fragment).toBeInstanceOf(DocumentFragment);
expect(fragment.querySelector('button')?.textContent).toBe('Test');
});
});

describe('@rstest/browser-react render options', () => {
it('should support custom container', async () => {
const customContainer = document.createElement('div');
customContainer.id = 'custom-container';
document.body.appendChild(customContainer);

const { container } = await render(<App />, { container: customContainer });

expect(container.id).toBe('custom-container');
expect(container.querySelector('h1')).toBeTruthy();

// Cleanup
document.body.removeChild(customContainer);
});

it('should support wrapper option for providers', async () => {
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<div data-testid="wrapper">{children}</div>
);

const { baseElement } = await render(<Button>Wrapped</Button>, {
wrapper: Wrapper,
});

expect(baseElement.querySelector('[data-testid="wrapper"]')).toBeTruthy();
expect(
baseElement.querySelector('[data-testid="wrapper"] button'),
).toBeTruthy();
});
});
70 changes: 70 additions & 0 deletions e2e/browser-mode/fixtures/browser-react/tests/renderHook.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { renderHook } from '@rstest/browser-react';
import { describe, expect, it } from '@rstest/core';
import { useCounter } from '../src/useCounter';

describe('@rstest/browser-react renderHook', () => {
it('should render hook with initial value', async () => {
const { result } = await renderHook(() => useCounter(5));

expect(result.current.count).toBe(5);
});

it('should handle hook state updates with act', async () => {
const { result, act } = await renderHook(() => useCounter(0));

expect(result.current.count).toBe(0);

await act(() => {
result.current.increment();
});

expect(result.current.count).toBe(1);

await act(() => {
result.current.increment();
result.current.increment();
});

expect(result.current.count).toBe(3);
});

it('should handle rerender with new props', async () => {
const { result, rerender } = await renderHook(
(props?: { initial: number }) => useCounter(props?.initial ?? 0),
{ initialProps: { initial: 10 } },
);

expect(result.current.count).toBe(10);

// Rerender with new initial value
// Note: useState only uses initial value on first render
await rerender({ initial: 20 });

// Count should still be 10 since useState doesn't reinitialize
expect(result.current.count).toBe(10);
});

it('should handle unmount', async () => {
const { result, unmount } = await renderHook(() => useCounter(0));

expect(result.current.count).toBe(0);

await unmount();

// After unmount, we can still access the last value
expect(result.current.count).toBe(0);
});

it('should support wrapper option', async () => {
let contextValue = 'default';

const Wrapper = ({ children }: { children: React.ReactNode }) => {
contextValue = 'wrapped';
return <>{children}</>;
};

await renderHook(() => useCounter(0), { wrapper: Wrapper });

expect(contextValue).toBe('wrapped');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* This test demonstrates using @rstest/browser-react with @testing-library/dom
* for DOM queries. This is the recommended approach for users who want
* testing-library-style queries without the full @testing-library/react dependency.
*/
import { act, render } from '@rstest/browser-react';
import { describe, expect, it } from '@rstest/core';
import {
getByRole,
getByTestId,
getByText,
queryByTestId,
} from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { App, Button, Counter } from '../src/App';

describe('@rstest/browser-react + @testing-library/dom', () => {
it('should work with getByRole query', async () => {
const { container } = await render(<App />);

const heading = getByRole(container, 'heading', { level: 1 });
expect(heading.textContent).toBe('React Browser Test');
});

it('should work with getByTestId query', async () => {
const { container } = await render(<App />);

const description = getByTestId(container, 'description');
expect(description.textContent).toBe('Testing @rstest/browser-react');
});

it('should work with getByText query', async () => {
const { container } = await render(<Button>Click me</Button>);

const button = getByText(container, 'Click me');
expect(button.tagName).toBe('BUTTON');
expect(button.className).toBe('btn');
});

it('should work with queryByTestId for non-existent elements', async () => {
const { container } = await render(<App />);

const nonExistent = queryByTestId(container, 'non-existent');
expect(nonExistent).toBeNull();
});

it('should handle Counter interactions with userEvent', async () => {
const user = userEvent.setup();
const { container } = await render(<Counter initialCount={0} />);

const countDisplay = getByTestId(container, 'count');
expect(countDisplay.textContent).toBe('0');

const incrementBtn = getByRole(container, 'button', { name: /increment/i });
await act(() => user.click(incrementBtn));

expect(countDisplay.textContent).toBe('1');
});

it('should handle multiple interactions', async () => {
const user = userEvent.setup();
const { container } = await render(<Counter initialCount={5} />);

const countDisplay = getByTestId(container, 'count');
const incrementBtn = getByRole(container, 'button', { name: /increment/i });
const decrementBtn = getByRole(container, 'button', { name: /decrement/i });

// Increment twice
await act(() => user.click(incrementBtn));
await act(() => user.click(incrementBtn));
expect(countDisplay.textContent).toBe('7');

// Decrement once
await act(() => user.click(decrementBtn));
expect(countDisplay.textContent).toBe('6');
});

it('should work with baseElement for queries', async () => {
const { baseElement } = await render(<Counter title="Test Counter" />);

// baseElement is document.body by default
const title = getByTestId(baseElement, 'counter-title');
expect(title.textContent).toBe('Test Counter');
});
});
Loading
Loading