Add Portal tests
This commit is contained in:
295
src/lib/components/portal/portal.test.ts
vendored
Normal file
295
src/lib/components/portal/portal.test.ts
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
import svelte from "svelte-inline-compile";
|
||||
import { render } from "@testing-library/svelte";
|
||||
import Portal from "./Portal.svelte";
|
||||
import PortalGroup from "./PortalGroup.svelte";
|
||||
import { click } from "$lib/test-utils/interactions";
|
||||
|
||||
function getPortalRoot() {
|
||||
return document.getElementById('headlessui-portal-root')!
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
it('should be possible to use a Portal', () => {
|
||||
// Dummy assertion to trick TS compiler
|
||||
expect(Portal).not.toBe(PortalGroup);
|
||||
|
||||
expect(getPortalRoot()).toBe(null)
|
||||
|
||||
render(svelte`
|
||||
<main id="parent">
|
||||
<Portal>
|
||||
<p id="content">Contents...</p>
|
||||
</Portal>
|
||||
</main>
|
||||
`)
|
||||
|
||||
let parent = document.getElementById('parent')
|
||||
let content = document.getElementById('content')
|
||||
|
||||
expect(getPortalRoot()).not.toBe(null)
|
||||
|
||||
// Ensure the content is not part of the parent
|
||||
expect(parent).not.toContainElement(content)
|
||||
|
||||
// Ensure the content does exist
|
||||
expect(content).not.toBe(null)
|
||||
expect(content).toHaveTextContent('Contents...')
|
||||
})
|
||||
|
||||
it('should be possible to use multiple Portal elements', () => {
|
||||
expect(getPortalRoot()).toBe(null)
|
||||
|
||||
render(svelte`
|
||||
<main id="parent">
|
||||
<Portal>
|
||||
<p id="content1">Contents 1 ...</p>
|
||||
</Portal>
|
||||
<hr />
|
||||
<Portal>
|
||||
<p id="content2">Contents 2 ...</p>
|
||||
</Portal>
|
||||
</main>
|
||||
`)
|
||||
|
||||
let parent = document.getElementById('parent')
|
||||
let content1 = document.getElementById('content1')
|
||||
let content2 = document.getElementById('content2')
|
||||
|
||||
expect(getPortalRoot()).not.toBe(null)
|
||||
|
||||
// Ensure the content1 is not part of the parent
|
||||
expect(parent).not.toContainElement(content1)
|
||||
|
||||
// Ensure the content2 is not part of the parent
|
||||
expect(parent).not.toContainElement(content2)
|
||||
|
||||
// Ensure the content does exist
|
||||
expect(content1).not.toBe(null)
|
||||
expect(content1).toHaveTextContent('Contents 1 ...')
|
||||
|
||||
// Ensure the content does exist
|
||||
expect(content2).not.toBe(null)
|
||||
expect(content2).toHaveTextContent('Contents 2 ...')
|
||||
})
|
||||
|
||||
it('should cleanup the Portal root when the last Portal is unmounted', async () => {
|
||||
expect(getPortalRoot()).toBe(null)
|
||||
|
||||
render(svelte`
|
||||
<script>
|
||||
let renderA = false;
|
||||
let renderB = false;
|
||||
</script>
|
||||
<main id="parent">
|
||||
<button id="a" on:click={() => renderA = !renderA}>
|
||||
Toggle A
|
||||
</button>
|
||||
<button id="b" on:click={() => renderB = !renderB}>
|
||||
Toggle B
|
||||
</button>
|
||||
|
||||
{#if renderA}
|
||||
<Portal>
|
||||
<p id="content1">Contents 1 ...</p>
|
||||
</Portal>
|
||||
{/if}
|
||||
|
||||
{#if renderB}
|
||||
<Portal>
|
||||
<p id="content2">Contents 2 ...</p>
|
||||
</Portal>
|
||||
{/if}
|
||||
</main>
|
||||
`)
|
||||
|
||||
let a = document.getElementById('a')
|
||||
let b = document.getElementById('b')
|
||||
|
||||
expect(getPortalRoot()).toBe(null)
|
||||
|
||||
// Let's render the first Portal
|
||||
await click(a)
|
||||
|
||||
expect(getPortalRoot()).not.toBe(null)
|
||||
expect(getPortalRoot().childNodes).toHaveLength(1)
|
||||
|
||||
// Let's render the second Portal
|
||||
await click(b)
|
||||
|
||||
expect(getPortalRoot()).not.toBe(null)
|
||||
expect(getPortalRoot().childNodes).toHaveLength(2)
|
||||
|
||||
// Let's remove the first portal
|
||||
await click(a)
|
||||
|
||||
expect(getPortalRoot()).not.toBe(null)
|
||||
expect(getPortalRoot().childNodes).toHaveLength(1)
|
||||
|
||||
// Let's remove the second Portal
|
||||
await click(b)
|
||||
|
||||
expect(getPortalRoot()).toBe(null)
|
||||
|
||||
// Let's render the first Portal again
|
||||
await click(a)
|
||||
|
||||
expect(getPortalRoot()).not.toBe(null)
|
||||
expect(getPortalRoot().childNodes).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should be possible to render multiple portals at the same time', async () => {
|
||||
expect(getPortalRoot()).toBe(null)
|
||||
|
||||
render(svelte`
|
||||
<script>
|
||||
let renderA = true;
|
||||
let renderB = true;
|
||||
let renderC = true;
|
||||
</script>
|
||||
<main id="parent">
|
||||
<button id="a" on:click={() => renderA = !renderA}>
|
||||
Toggle A
|
||||
</button>
|
||||
<button id="b" on:click={() => renderB = !renderB}>
|
||||
Toggle B
|
||||
</button>
|
||||
<button id="c" on:click={() => renderC = !renderC}>
|
||||
Toggle C
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="double"
|
||||
on:click={() => {
|
||||
renderA = !renderA;
|
||||
renderB = !renderB;
|
||||
}}
|
||||
>
|
||||
Toggle A & B{' '}
|
||||
</button>
|
||||
|
||||
{#if renderA}
|
||||
<Portal>
|
||||
<p id="content1">Contents 1 ...</p>
|
||||
</Portal>
|
||||
{/if}
|
||||
|
||||
{#if renderB}
|
||||
<Portal>
|
||||
<p id="content2">Contents 2 ...</p>
|
||||
</Portal>
|
||||
{/if}
|
||||
|
||||
{#if renderC}
|
||||
<Portal>
|
||||
<p id="content3">Contents 3 ...</p>
|
||||
</Portal>
|
||||
{/if}
|
||||
</main>
|
||||
`)
|
||||
|
||||
expect(getPortalRoot()).not.toBe(null)
|
||||
expect(getPortalRoot().childNodes).toHaveLength(3)
|
||||
|
||||
// Remove Portal 1
|
||||
await click(document.getElementById('a'))
|
||||
expect(getPortalRoot().childNodes).toHaveLength(2)
|
||||
|
||||
// Remove Portal 2
|
||||
await click(document.getElementById('b'))
|
||||
expect(getPortalRoot().childNodes).toHaveLength(1)
|
||||
|
||||
// Re-add Portal 1
|
||||
await click(document.getElementById('a'))
|
||||
expect(getPortalRoot().childNodes).toHaveLength(2)
|
||||
|
||||
// Remove Portal 3
|
||||
await click(document.getElementById('c'))
|
||||
expect(getPortalRoot().childNodes).toHaveLength(1)
|
||||
|
||||
// Remove Portal 1
|
||||
await click(document.getElementById('a'))
|
||||
expect(getPortalRoot()).toBe(null)
|
||||
|
||||
// Render A and B at the same time!
|
||||
await click(document.getElementById('double'))
|
||||
expect(getPortalRoot().childNodes).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('should be possible to tamper with the modal root and restore correctly', async () => {
|
||||
expect(getPortalRoot()).toBe(null)
|
||||
|
||||
render(svelte`
|
||||
<script>
|
||||
let renderA = true;
|
||||
let renderB = true;
|
||||
</script>
|
||||
<main id="parent">
|
||||
<button id="a" on:click={() => renderA = !renderA}>
|
||||
Toggle A
|
||||
</button>
|
||||
<button id="b" on:click={() => renderB = !renderB}>
|
||||
Toggle B
|
||||
</button>
|
||||
|
||||
{#if renderA}
|
||||
<Portal>
|
||||
<p id="content1">Contents 1 ...</p>
|
||||
</Portal>
|
||||
{/if}
|
||||
|
||||
{#if renderB}
|
||||
<Portal>
|
||||
<p id="content2">Contents 2 ...</p>
|
||||
</Portal>
|
||||
{/if}
|
||||
</main>
|
||||
`)
|
||||
|
||||
expect(getPortalRoot()).not.toBe(null)
|
||||
|
||||
// Tamper tamper
|
||||
document.body.removeChild(document.getElementById('headlessui-portal-root')!)
|
||||
|
||||
// Hide Portal 1 and 2
|
||||
await click(document.getElementById('a'))
|
||||
await click(document.getElementById('b'))
|
||||
|
||||
expect(getPortalRoot()).toBe(null)
|
||||
|
||||
// Re-show Portal 1 and 2
|
||||
await click(document.getElementById('a'))
|
||||
await click(document.getElementById('b'))
|
||||
|
||||
expect(getPortalRoot()).not.toBe(null)
|
||||
expect(getPortalRoot().childNodes).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('should be possible to force the Portal into a specific element using PortalGroup', async () => {
|
||||
render(svelte`
|
||||
<script>
|
||||
let container = null;
|
||||
</script>
|
||||
<main>
|
||||
<aside bind:this={container} id="group-1">
|
||||
A
|
||||
</aside>
|
||||
|
||||
<PortalGroup target={container}>
|
||||
<section id="group-2">
|
||||
<span>B</span>
|
||||
</section>
|
||||
<Portal>Next to A</Portal>
|
||||
</PortalGroup>
|
||||
|
||||
<Portal>I am in the portal root</Portal>
|
||||
</main>
|
||||
`)
|
||||
|
||||
// The random whitespace in here is a little annoying but whatever
|
||||
expect(document.body.innerHTML).toMatchInlineSnapshot(
|
||||
`"<div><main><aside id=\\"group-1\\">A<div>Next to A</div></aside> <section id=\\"group-2\\"><span>B</span></section> </main></div><div id=\\"headlessui-portal-root\\"><div>I am in the portal root</div></div>"`
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user