Testing
In jsdom and happy-dom there is no real page on the other side of an iframe, so nothing
connects. react-iframe-kit/testing plays the missing side over the same protocol as the
real one:
mockChildis the page inside the iframe, for testing the parent:useIframeRPC,useIframeEvent,useIframeResize,useIframeTitle.mockParentis the page around it, for testing the child:connectToParent,useParent,useParentEvent.
Testing the parent: mockChild
Section titled “Testing the parent: mockChild”import { render, screen, waitFor } from '@testing-library/react';import userEvent from '@testing-library/user-event';import { mockChild } from 'react-iframe-kit/testing';import type { ChildSide, ParentSide } from './contract';
test('pays through the widget', async () => { render(<Checkout />); const child = mockChild<ParentSide, ChildSide>(screen.getByTitle('Payment'), { methods: { pay: async (amount) => ({ ok: amount > 0 }) }, });
await userEvent.click(screen.getByRole('button', { name: 'Pay' })); await waitFor(() => expect(screen.getByText('Paid')).toBeInTheDocument());
child.dispose();});Pass the parent’s side first, like connectToParent. The iframe must be in the document;
mockChild can be created before or after your component connects.
remote |
The parent’s methods, e.g. await child.remote.getUser(). |
emit(name, payload?) |
Sends one of the page’s events to the parent. |
on(name, handler) |
Listens to the parent’s events; returns the unsubscribe function. |
resize({ width, height }) |
Reports a content size, as autoResize would. |
setTitle(title) |
Reports a page title, as syncTitle would. |
inert |
Whether the parent currently asks the page to be inert (useIframeInert). |
whenConnected() |
Resolves once the handshake completes. |
dispose() |
Unloads the page: calls still pending on the parent reject with RIK_CONNECTION_LOST. |
status |
'connecting', 'connected' or 'disposed'. |
Options: methods, origin (default: the origin of the iframe’s src), timeout,
connectTimeout.
Testing the child: mockParent
Section titled “Testing the child: mockParent”import { render, screen, waitFor } from '@testing-library/react';import { mockParent } from 'react-iframe-kit/testing';import type { ChildSide, ParentSide } from './contract';
test('loads the user from the host page', async () => { const parent = mockParent<ChildSide, ParentSide>({ origin: 'https://app.example.com', // must be in the page's allowedOrigins methods: { getUser: () => ({ name: 'Ann' }) }, }); render(<Widget />);
await waitFor(() => expect(screen.getByText('Hello, Ann')).toBeInTheDocument()); await expect(parent.remote.reset()).resolves.toBe(true);
parent.dispose();});Pass the page’s side first, like useIframeRPC. Create it before the code under test
connects: it makes the test page look framed, and a connection started before that stays
'idle'.
remote |
The page’s methods. |
emit(name, payload?) |
Sends one of the parent’s events to the page. |
on(name, handler) |
Listens to the page’s events; returns the unsubscribe function. |
size |
The last size the page reported with autoResize, or null. |
title |
The last title the page reported with syncTitle, or null. |
whenConnected() |
Resolves once the handshake completes. |
dispose() |
Goes away like an unmounting parent: the page’s pending calls reject with RIK_CONNECTION_LOST, and the page is no longer framed. |
status |
'connecting', 'connected' or 'disposed'. |
Options: methods, origin (default: the test page’s origin), timeout,
connectTimeout.
The page’s connection lives for the whole test file. After dispose(), the next
mockParent reconnects it, so each test can create its own.
Both need a global MessageChannel, which Node and Vitest’s jsdom and happy-dom
environments provide. mockParent adds a hidden <iframe> to the document while it
exists.
In happy-dom, set navigation: { disableChildFrameNavigation: true } so an iframe with a
remote src isn’t fetched. Don’t use disableIframePageLoading: it leaves iframes
without a window, and both mocks need one.