import { render, screen } from '@testing-library/react';
import React, { useContext } from 'react';
import { createPannaClient, type PannaClient } from '../../core';
import { PannaProvider, PannaClientContext } from './panna-provider';
// Mock the thirdweb module
jest.mock('thirdweb/react', () => ({
ThirdwebProvider: ({ children }: { children: React.ReactNode }) => (
{children}
)
}));
// Mock the createPannaClient function
jest.mock('../../core', () => ({
createPannaClient: jest.fn()
}));
const mockCreatePannaClient = createPannaClient as jest.MockedFunction<
typeof createPannaClient
>;
// Test component to consume the context
const TestConsumer: React.FC = () => {
const context = useContext(PannaClientContext);
return (
{context?.client ? 'client-available' : 'client-null'}
{JSON.stringify(context)}
);
};
describe('PannaProvider', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('rendering', () => {
it('should render children without crashing', () => {
render(
Test Child
);
expect(screen.getByTestId('test-child')).toBeInTheDocument();
});
it('should wrap children with ThirdwebProvider', () => {
render(
Test Child
);
expect(screen.getByTestId('thirdweb-provider')).toBeInTheDocument();
expect(screen.getByTestId('test-child')).toBeInTheDocument();
});
});
describe('client creation and context', () => {
it('should provide null client when no clientId is provided', () => {
render(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-null'
);
expect(mockCreatePannaClient).not.toHaveBeenCalled();
});
it('should provide null client when clientId is undefined', () => {
render(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-null'
);
expect(mockCreatePannaClient).not.toHaveBeenCalled();
});
it('should provide null client when clientId is empty string', () => {
render(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-null'
);
expect(mockCreatePannaClient).not.toHaveBeenCalled();
});
it('should create and provide client when valid clientId is provided', () => {
const mockClient = { id: 'test-client' } as unknown as PannaClient;
mockCreatePannaClient.mockReturnValue(mockClient);
render(
);
expect(mockCreatePannaClient).toHaveBeenCalledWith({
clientId: 'test-client-id'
});
expect(mockCreatePannaClient).toHaveBeenCalledTimes(1);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-available'
);
});
});
describe('client memoization', () => {
it('should not recreate client when clientId remains the same', () => {
const mockClient = { id: 'test-client' } as unknown as PannaClient;
mockCreatePannaClient.mockReturnValue(mockClient);
const { rerender } = render(
);
expect(mockCreatePannaClient).toHaveBeenCalledTimes(1);
// Re-render with same clientId
rerender(
);
// Should not call createPannaClient again
expect(mockCreatePannaClient).toHaveBeenCalledTimes(1);
});
it('should recreate client when clientId changes', () => {
const mockClient1 = { id: 'test-client-1' } as unknown as PannaClient;
const mockClient2 = { id: 'test-client-2' } as unknown as PannaClient;
mockCreatePannaClient
.mockReturnValueOnce(mockClient1)
.mockReturnValueOnce(mockClient2);
const { rerender } = render(
);
expect(mockCreatePannaClient).toHaveBeenCalledWith({
clientId: 'test-client-id-1'
});
expect(mockCreatePannaClient).toHaveBeenCalledTimes(1);
// Re-render with different clientId
rerender(
);
expect(mockCreatePannaClient).toHaveBeenCalledWith({
clientId: 'test-client-id-2'
});
expect(mockCreatePannaClient).toHaveBeenCalledTimes(2);
});
it('should change from client to null when clientId is removed', () => {
const mockClient = { id: 'test-client' } as unknown as PannaClient;
mockCreatePannaClient.mockReturnValue(mockClient);
const { rerender } = render(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-available'
);
// Re-render without clientId
rerender(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-null'
);
});
it('should change from null to client when clientId is added', () => {
const mockClient = { id: 'test-client' } as unknown as PannaClient;
mockCreatePannaClient.mockReturnValue(mockClient);
const { rerender } = render(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-null'
);
// Re-render with clientId
rerender(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-available'
);
expect(mockCreatePannaClient).toHaveBeenCalledWith({
clientId: 'test-client-id'
});
});
});
describe('integration', () => {
it('should work with multiple nested consumers', () => {
const mockClient = { id: 'test-client' } as unknown as PannaClient;
mockCreatePannaClient.mockReturnValue(mockClient);
const NestedConsumer: React.FC = () => {
const context = useContext(PannaClientContext);
return (
{context?.client ? 'nested-has-client' : 'nested-no-client'}
);
};
render(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-available'
);
expect(screen.getByTestId('nested-consumer')).toHaveTextContent(
'nested-has-client'
);
});
it('should maintain context across re-renders with dynamic children', () => {
const mockClient = { id: 'test-client' } as unknown as PannaClient;
mockCreatePannaClient.mockReturnValue(mockClient);
const DynamicChildren: React.FC<{ showExtra: boolean }> = ({
showExtra
}) => (
<>
{showExtra && Extra Content
}
>
);
const { rerender } = render(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-available'
);
expect(screen.queryByTestId('extra-content')).not.toBeInTheDocument();
rerender(
);
expect(screen.getByTestId('client-status')).toHaveTextContent(
'client-available'
);
expect(screen.getByTestId('extra-content')).toBeInTheDocument();
});
});
});