import React, { ElementType, createRef, Ref, Fragment } from 'react' import { render as testRender, prettyDOM, getByTestId } from '@testing-library/react' import { suppressConsoleLogs } from '../test-utils/suppress-console-logs' import { render, Features, PropsForFeatures } from './render' import { Props, Expand } from '../types' function contents() { return prettyDOM(getByTestId(document.body, 'wrapper'), undefined, { highlight: false, }) } describe('Default functionality', () => { let slot = {} function Dummy( props: Props & Partial<{ a: any; b: any; c: any }> ) { return (
{render({ ourProps: {}, theirProps: props, slot, defaultTag: 'div', name: 'Dummy', })}
) } it('should be possible to render a dummy component', () => { testRender() expect(contents()).toMatchInlineSnapshot(` "
" `) }) it('should be possible to render a dummy component with some children as a callback', () => { expect.assertions(2) testRender( {(data) => { expect(data).toBe(slot) return Contents }} ) expect(contents()).toMatchInlineSnapshot(` "
Contents
" `) }) it('should be possible to add a ref with a different name', () => { let ref = createRef() function MyComponent({ innerRef, ...props }: Props & { innerRef: Ref }) { return
} function OtherDummy(props: Props) { return (
{render({ ourProps: { ref }, theirProps: props, slot, defaultTag: 'div', name: 'OtherDummy', })}
) } testRender( Contents ) expect(contents()).toMatchInlineSnapshot(` "
Contents
" `) }) it('should be possible to passthrough props to a dummy component', () => { testRender() expect(contents()).toMatchInlineSnapshot(` "
" `) }) it('should be possible to change the underlying DOM node using the `as` prop', () => { testRender() expect(contents()).toMatchInlineSnapshot(` "
" `) }) it('should be possible to change the underlying DOM node using the `as` prop and still have a function as children', () => { testRender({() => Contents}) expect(contents()).toMatchInlineSnapshot(` "
" `) }) it('should be possible to render the children only when the `as` prop is set to Fragment', () => { testRender(Contents) expect(contents()).toMatchInlineSnapshot(` "
Contents
" `) }) it('should forward all the props to the first child when using an as={Fragment}', () => { testRender( {() => Contents} ) expect(contents()).toMatchInlineSnapshot(` "
Contents
" `) }) it( 'should error when we are rendering a Fragment with multiple children', suppressConsoleLogs(() => { expect.assertions(1) return expect(() => { testRender( // @ts-expect-error className cannot be applied to a Fragment Contents A Contents B ) }).toThrowError( new Error( [ 'Passing props on "Fragment"!', '', 'The current component is rendering a "Fragment".', 'However we need to passthrough the following props:', ' - className', '', 'You can apply a few solutions:', ' - Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".', ' - Render a single element as the child so that we can forward the props onto that element.', ].join('\n') ) ) }) ) it("should not error when we are rendering a Fragment with multiple children when we don't passthrough additional props", () => { testRender( Contents A Contents B ) expect(contents()).toMatchInlineSnapshot(` "
Contents A Contents B
" `) }) it( 'should error when we are applying props to a Fragment when we do not have a dedicated element', suppressConsoleLogs(() => { expect.assertions(1) return expect(() => { testRender( // @ts-expect-error className cannot be applied to a Fragment Contents ) }).toThrowError( new Error( [ 'Passing props on "Fragment"!', '', 'The current component is rendering a "Fragment".', 'However we need to passthrough the following props:', ' - className', '', 'You can apply a few solutions:', ' - Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".', ' - Render a single element as the child so that we can forward the props onto that element.', ].join('\n') ) ) }) ) }) // --- function testStaticFeature(Dummy: Function) { it('should be possible to render a `static` dummy component (show = true)', () => { testRender( Contents ) expect(contents()).toMatchInlineSnapshot(` "
Contents
" `) }) it('should be possible to render a `static` dummy component (show = false)', () => { testRender( Contents ) expect(contents()).toMatchInlineSnapshot(` "
Contents
" `) }) } // With the `static` keyword, the user is always in control. When we internally decide to show the // component or hide it then it won't have any effect. This is useful for when you want to wrap your // component in a Transition for example so that the Transition component can control the // showing/hiding based on the `show` prop AND the state of the transition. describe('Features.Static', () => { let slot = {} let EnabledFeatures = Features.Static function Dummy( props: Expand & PropsForFeatures> & { show: boolean } ) { let { show, ...rest } = props return (
{render({ ourProps: {}, theirProps: rest, slot, defaultTag: 'div', features: EnabledFeatures, visible: show, name: 'Dummy', })}
) } testStaticFeature(Dummy) }) // --- function testRenderStrategyFeature(Dummy: Function) { describe('Unmount render strategy', () => { it('should be possible to render an `unmount` dummy component (show = true)', () => { testRender( Contents ) expect(contents()).toMatchInlineSnapshot(` "
Contents
" `) }) it('should be possible to render an `unmount` dummy component (show = false)', () => { testRender( Contents ) // No contents, because we unmounted! expect(contents()).toMatchInlineSnapshot(` "
" `) }) }) describe('Hidden render strategy', () => { it('should be possible to render an `unmount={false}` dummy component (show = true)', () => { testRender( Contents ) expect(contents()).toMatchInlineSnapshot(` "
Contents
" `) }) it('should be possible to render an `unmount={false}` dummy component (show = false)', () => { testRender( Contents ) // We do have contents, but it is marked as hidden! expect(contents()).toMatchInlineSnapshot(` "
" `) }) }) } describe('Features.RenderStrategy', () => { let slot = {} let EnabledFeatures = Features.RenderStrategy function Dummy( props: Expand & PropsForFeatures> & { show: boolean } ) { let { show, ...rest } = props return (
{render({ ourProps: {}, theirProps: rest, slot, defaultTag: 'div', features: EnabledFeatures, visible: show, name: 'Dummy', })}
) } testRenderStrategyFeature(Dummy) }) // --- // This should enable the `static` and `unmount` features. However they can't be used together! describe('Features.Static | Features.RenderStrategy', () => { let slot = {} let EnabledFeatures = Features.Static | Features.RenderStrategy function Dummy( props: Expand & PropsForFeatures> & { show: boolean } ) { let { show, ...rest } = props return (
{render({ ourProps: {}, theirProps: rest, slot, defaultTag: 'div', features: EnabledFeatures, visible: show, name: 'Dummy', })}
) } // TODO: Can we "legit" test this? 🤔 it('should result in a typescript error', () => { testRender( // @ts-expect-error static & unmount together are incompatible Contents ) }) // To avoid duplication, and to make sure that the features tested in isolation can also be // re-used when they are combined. testStaticFeature(Dummy) testRenderStrategyFeature(Dummy) })