diff --git a/packages/react-components/react-portal/library/etc/react-portal.api.md b/packages/react-components/react-portal/library/etc/react-portal.api.md index f0f44c50972578..16cd2d9dbcf898 100644 --- a/packages/react-components/react-portal/library/etc/react-portal.api.md +++ b/packages/react-components/react-portal/library/etc/react-portal.api.md @@ -4,9 +4,11 @@ ```ts +import type { ComponentState } from '@fluentui/react-utilities'; import { elementContains } from '@fluentui/react-utilities'; import * as React_2 from 'react'; import { setVirtualParent } from '@fluentui/react-utilities'; +import type { Slot } from '@fluentui/react-utilities'; export { elementContains } @@ -23,7 +25,7 @@ export type PortalProps = { }; // @public (undocumented) -export type PortalState = Pick & { +export type PortalState = ComponentState & Pick & { mountNode: HTMLElement | null | undefined; virtualParentRootRef: React_2.MutableRefObject; }; diff --git a/packages/react-components/react-portal/library/src/components/Portal/Portal.test.tsx b/packages/react-components/react-portal/library/src/components/Portal/Portal.test.tsx index a7317a2094f1cc..0640111fcd620f 100644 --- a/packages/react-components/react-portal/library/src/components/Portal/Portal.test.tsx +++ b/packages/react-components/react-portal/library/src/components/Portal/Portal.test.tsx @@ -6,14 +6,11 @@ import * as React from 'react'; import { Portal } from './Portal'; describe('Portal', () => { - /** - * Note: see more visual regression tests for Portal in /apps/vr-tests. - */ - it('renders a default state', () => { - const children = 'test'; - const { getByText } = render({children}); - - expect(getByText(children)).toMatchSnapshot(); + it('creates an element and attaches it to "document.body"', () => { + const { getByText } = render(Test); + const element = getByText('Test'); + + expect(document.body.children).toContain(element); }); it('applies "dir" attribute based on a context value', () => { @@ -33,13 +30,16 @@ describe('Portal', () => { expect(getByText('RTL')).toHaveAttribute('dir', 'rtl'); }); + it('applies "className"', () => { + const { getByText } = render(Test); + + expect(getByText('Test')).toHaveClass('foo'); + }); + it('applies "zIndex" style', () => { const { getByText } = render(Test); - const element = getByText('Test'); - expect(element).toHaveStyle({ - zIndex: 1000000, - }); + expect(getByText('Test')).toHaveStyle({ zIndex: 1000000 }); }); it('should not set virtual parent if mount node contains virtual parent', () => { @@ -59,4 +59,46 @@ describe('Portal', () => { const mountNode = container.querySelector('#container'); expect((getParent(mountNode) as HTMLElement).id).toBe('parent'); }); + + describe('mountNode', () => { + it('renders portal content into the specified mount node', () => { + const mountNode = document.createElement('div'); + + mountNode.id = 'mount-node'; + document.body.appendChild(mountNode); + + const { getByText } = render( + + Test + , + ); + const portalEl = getByText('Test'); + + expect(portalEl).toBeInstanceOf(HTMLSpanElement); + expect(portalEl.parentElement).toBe(mountNode); + }); + + it('does not add attributes to a mount node', () => { + const mountNode = document.createElement('div'); + + mountNode.id = 'mount-node'; + document.body.appendChild(mountNode); + + render( + + Test + , + ); + + expect(mountNode).toMatchInlineSnapshot(` +
+ + Test + +
+ `); + }); + }); }); diff --git a/packages/react-components/react-portal/library/src/components/Portal/Portal.types.ts b/packages/react-components/react-portal/library/src/components/Portal/Portal.types.ts index 32cc3590138de0..18dc69908db581 100644 --- a/packages/react-components/react-portal/library/src/components/Portal/Portal.types.ts +++ b/packages/react-components/react-portal/library/src/components/Portal/Portal.types.ts @@ -1,5 +1,10 @@ +import type { ComponentState, Slot } from '@fluentui/react-utilities'; import * as React from 'react'; +export type PortalInternalSlots = { + root?: Slot<'div'>; +}; + export type PortalProps = { /** * React children @@ -14,11 +19,12 @@ export type PortalProps = { mountNode?: HTMLElement | null | { element?: HTMLElement | null; className?: string }; }; -export type PortalState = Pick & { - mountNode: HTMLElement | null | undefined; +export type PortalState = ComponentState & + Pick & { + mountNode: HTMLElement | null | undefined; - /** - * Ref to the root span element as virtual parent - */ - virtualParentRootRef: React.MutableRefObject; -}; + /** + * Ref to the root span element as virtual parent + */ + virtualParentRootRef: React.MutableRefObject; + }; diff --git a/packages/react-components/react-portal/library/src/components/Portal/__snapshots__/Portal.test.tsx.snap b/packages/react-components/react-portal/library/src/components/Portal/__snapshots__/Portal.test.tsx.snap deleted file mode 100644 index 97c165d2a52a99..00000000000000 --- a/packages/react-components/react-portal/library/src/components/Portal/__snapshots__/Portal.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Portal renders a default state 1`] = ` -
- test -
-`; diff --git a/packages/react-components/react-portal/library/src/components/Portal/renderPortal.tsx b/packages/react-components/react-portal/library/src/components/Portal/renderPortal.tsx index f94cfafc2f64dc..9f85f410f7f69a 100644 --- a/packages/react-components/react-portal/library/src/components/Portal/renderPortal.tsx +++ b/packages/react-components/react-portal/library/src/components/Portal/renderPortal.tsx @@ -1,14 +1,22 @@ +/** @jsxRuntime automatic */ +/** @jsxImportSource @fluentui/react-jsx-runtime */ +import { assertSlots } from '@fluentui/react-utilities'; import * as ReactDOM from 'react-dom'; import * as React from 'react'; -import type { PortalState } from './Portal.types'; + +import type { PortalState, PortalInternalSlots } from './Portal.types'; /** * Render the final JSX of Portal */ export const renderPortal_unstable = (state: PortalState): React.ReactElement => { + assertSlots(state); + return ( - + <> +