import * as React from 'react';
import { render } from '@testing-library/react';
import type { PanelExtension } from '@atlassian/clientside-extensions';
import type { PanelHandlerProps } from './PanelHandler';
import { PanelHandler } from './PanelHandler';
const onMountCallbackSpy = jest.fn();
const onUnmountCallbackSpy = jest.fn();
// onAction method of an Extension
const testRenderFn: PanelExtension.PanelRenderExtension = (api) => {
api.onMount(onMountCallbackSpy);
api.onUnmount(onUnmountCallbackSpy);
};
// onAction method of an Extension
// When an attribute of the extension changes, the useExtensions hook returns a completly new descriptor object
// This function is used to replicate that behaviour.
const changedTestRenderFn: PanelExtension.PanelRenderExtension = (api) => {
api.onMount(onMountCallbackSpy);
api.onUnmount(onUnmountCallbackSpy);
};
// eslint-disable-next-line @typescript-eslint/no-shadow
const TestComponent = ({ render, RootType }: PanelHandlerProps) => {
return ;
};
describe('PanelHandler', () => {
beforeEach(() => {
onMountCallbackSpy.mockReset();
onUnmountCallbackSpy.mockReset();
});
it('should provide an onMount API method to render custom HTML in a container', async () => {
const CUSTOM_CONTENT = 'Custom content';
onMountCallbackSpy.mockImplementationOnce((container: HTMLElement) => {
container.innerHTML = CUSTOM_CONTENT;
});
const { findByText } = render();
expect(onMountCallbackSpy).toHaveBeenCalledTimes(1);
expect(await findByText(CUSTOM_CONTENT)).toBeTruthy();
});
it('should only rerender the extension content if something in the extension descriptor changed', async () => {
const CUSTOM_CONTENT = 'Custom content';
const DIFFERENT_CONTENT = 'Different content';
onMountCallbackSpy
.mockImplementationOnce((container: HTMLElement) => {
container.innerHTML = CUSTOM_CONTENT;
})
.mockImplementationOnce((container: HTMLElement) => {
container.innerHTML = DIFFERENT_CONTENT;
});
const { findByText, rerender } = render();
expect(onMountCallbackSpy).toHaveBeenCalledTimes(1);
expect(await findByText(CUSTOM_CONTENT)).toBeTruthy();
// expect nothing to change
rerender();
expect(onMountCallbackSpy).toHaveBeenCalledTimes(1);
expect(await findByText(CUSTOM_CONTENT)).toBeTruthy();
// expect the new value to be rendered when render function did changed
rerender();
expect(onMountCallbackSpy).toHaveBeenCalledTimes(2);
expect(await findByText(DIFFERENT_CONTENT)).toBeTruthy();
});
it('should provide an onUnmount API method to be called with the container element when destroying the extension', () => {
onMountCallbackSpy.mockImplementation((container: HTMLElement) => {
expect(container).toHaveProperty('innerHTML');
});
const { unmount } = render();
expect(onUnmountCallbackSpy).toHaveBeenCalledTimes(0);
unmount();
expect(onUnmountCallbackSpy).toHaveBeenCalledTimes(1);
});
it('should only call cleanUp callback if the extension changes or the container is destroyed', () => {
const { rerender, unmount } = render();
expect(onUnmountCallbackSpy).toHaveBeenCalledTimes(0);
// the container rerenders with the same extension descriptor, so no need to call cleanup
rerender();
expect(onUnmountCallbackSpy).toHaveBeenCalledTimes(0);
// the extension descriptor changed, so cleanup is called
rerender();
expect(onUnmountCallbackSpy).toHaveBeenCalledTimes(1);
// the container is destroyed, so cleanup is called
unmount();
expect(onUnmountCallbackSpy).toHaveBeenCalledTimes(2);
});
it('should allow to specify a span element as a container', () => {
const { asFragment } = render();
expect(asFragment().firstElementChild).toMatchInlineSnapshot(``);
});
});