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(``); }); });