@wordpress/ui 0.11.0 → 0.12.1-next.v.202604201441.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +4 -4
- package/build/alert-dialog/popup.cjs +4 -4
- package/build/alert-dialog/popup.cjs.map +2 -2
- package/build/collapsible-card/header.cjs +10 -0
- package/build/collapsible-card/header.cjs.map +3 -3
- package/build/dialog/context.cjs +21 -9
- package/build/dialog/context.cjs.map +2 -2
- package/build/dialog/footer.cjs +4 -4
- package/build/dialog/footer.cjs.map +2 -2
- package/build/dialog/header.cjs +4 -4
- package/build/dialog/header.cjs.map +2 -2
- package/build/dialog/popup.cjs +4 -4
- package/build/dialog/popup.cjs.map +2 -2
- package/build/dialog/title.cjs +9 -6
- package/build/dialog/title.cjs.map +2 -2
- package/build/form/primitives/select/item.cjs +3 -3
- package/build/form/primitives/select/item.cjs.map +2 -2
- package/build/form/primitives/select/popup.cjs +3 -3
- package/build/form/primitives/select/popup.cjs.map +2 -2
- package/build/link/link.cjs +8 -18
- package/build/link/link.cjs.map +2 -2
- package/build/link/types.cjs.map +1 -1
- package/build/notice/action-button.cjs +3 -3
- package/build/notice/action-button.cjs.map +2 -2
- package/build/notice/action-link.cjs +8 -7
- package/build/notice/action-link.cjs.map +2 -2
- package/build/notice/actions.cjs +3 -3
- package/build/notice/actions.cjs.map +2 -2
- package/build/notice/close-icon.cjs +3 -3
- package/build/notice/close-icon.cjs.map +2 -2
- package/build/notice/description.cjs +3 -3
- package/build/notice/description.cjs.map +2 -2
- package/build/notice/root.cjs +3 -3
- package/build/notice/root.cjs.map +2 -2
- package/build/notice/title.cjs +3 -3
- package/build/notice/title.cjs.map +2 -2
- package/build/popover/arrow.cjs +4 -4
- package/build/popover/arrow.cjs.map +2 -2
- package/build/popover/context.cjs +21 -9
- package/build/popover/context.cjs.map +2 -2
- package/build/popover/description.cjs +4 -4
- package/build/popover/description.cjs.map +2 -2
- package/build/popover/popup.cjs +8 -5
- package/build/popover/popup.cjs.map +2 -2
- package/build/popover/title.cjs +5 -2
- package/build/popover/title.cjs.map +2 -2
- package/build/tabs/context.cjs +9 -22
- package/build/tabs/context.cjs.map +2 -2
- package/build/tabs/list.cjs +4 -4
- package/build/tabs/list.cjs.map +2 -2
- package/build/tabs/panel.cjs +19 -6
- package/build/tabs/panel.cjs.map +3 -3
- package/build/tabs/tab.cjs +4 -4
- package/build/tabs/tab.cjs.map +2 -2
- package/build/tooltip/popup.cjs +4 -4
- package/build/tooltip/popup.cjs.map +2 -2
- package/build/utils/use-schedule-validation.cjs +59 -0
- package/build/utils/use-schedule-validation.cjs.map +7 -0
- package/build-module/alert-dialog/popup.mjs +4 -4
- package/build-module/alert-dialog/popup.mjs.map +2 -2
- package/build-module/collapsible-card/header.mjs +10 -0
- package/build-module/collapsible-card/header.mjs.map +3 -3
- package/build-module/dialog/context.mjs +21 -9
- package/build-module/dialog/context.mjs.map +2 -2
- package/build-module/dialog/footer.mjs +4 -4
- package/build-module/dialog/footer.mjs.map +2 -2
- package/build-module/dialog/header.mjs +4 -4
- package/build-module/dialog/header.mjs.map +2 -2
- package/build-module/dialog/popup.mjs +4 -4
- package/build-module/dialog/popup.mjs.map +2 -2
- package/build-module/dialog/title.mjs +10 -7
- package/build-module/dialog/title.mjs.map +2 -2
- package/build-module/form/primitives/select/item.mjs +3 -3
- package/build-module/form/primitives/select/item.mjs.map +2 -2
- package/build-module/form/primitives/select/popup.mjs +3 -3
- package/build-module/form/primitives/select/popup.mjs.map +2 -2
- package/build-module/link/link.mjs +8 -18
- package/build-module/link/link.mjs.map +2 -2
- package/build-module/notice/action-button.mjs +3 -3
- package/build-module/notice/action-button.mjs.map +2 -2
- package/build-module/notice/action-link.mjs +8 -7
- package/build-module/notice/action-link.mjs.map +2 -2
- package/build-module/notice/actions.mjs +3 -3
- package/build-module/notice/actions.mjs.map +2 -2
- package/build-module/notice/close-icon.mjs +3 -3
- package/build-module/notice/close-icon.mjs.map +2 -2
- package/build-module/notice/description.mjs +3 -3
- package/build-module/notice/description.mjs.map +2 -2
- package/build-module/notice/root.mjs +3 -3
- package/build-module/notice/root.mjs.map +2 -2
- package/build-module/notice/title.mjs +3 -3
- package/build-module/notice/title.mjs.map +2 -2
- package/build-module/popover/arrow.mjs +4 -4
- package/build-module/popover/arrow.mjs.map +2 -2
- package/build-module/popover/context.mjs +21 -9
- package/build-module/popover/context.mjs.map +2 -2
- package/build-module/popover/description.mjs +4 -4
- package/build-module/popover/description.mjs.map +2 -2
- package/build-module/popover/popup.mjs +8 -5
- package/build-module/popover/popup.mjs.map +2 -2
- package/build-module/popover/title.mjs +6 -3
- package/build-module/popover/title.mjs.map +2 -2
- package/build-module/tabs/context.mjs +11 -24
- package/build-module/tabs/context.mjs.map +2 -2
- package/build-module/tabs/list.mjs +4 -4
- package/build-module/tabs/list.mjs.map +2 -2
- package/build-module/tabs/panel.mjs +19 -6
- package/build-module/tabs/panel.mjs.map +3 -3
- package/build-module/tabs/tab.mjs +4 -4
- package/build-module/tabs/tab.mjs.map +2 -2
- package/build-module/tooltip/popup.mjs +4 -4
- package/build-module/tooltip/popup.mjs.map +2 -2
- package/build-module/utils/use-schedule-validation.mjs +34 -0
- package/build-module/utils/use-schedule-validation.mjs.map +7 -0
- package/build-types/alert-dialog/stories/index.story.d.ts +1 -1
- package/build-types/alert-dialog/stories/index.story.d.ts.map +1 -1
- package/build-types/badge/stories/index.story.d.ts.map +1 -1
- package/build-types/collapsible-card/header.d.ts.map +1 -1
- package/build-types/dialog/context.d.ts +1 -1
- package/build-types/dialog/context.d.ts.map +1 -1
- package/build-types/dialog/title.d.ts.map +1 -1
- package/build-types/empty-state/stories/index.story.d.ts +1 -1
- package/build-types/empty-state/stories/index.story.d.ts.map +1 -1
- package/build-types/form/input-control/stories/index.story.d.ts +1 -1
- package/build-types/form/input-control/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/field/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/fieldset/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/fieldset/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/input/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/input/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/input-layout/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/input-layout/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/select/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/select/stories/index.story.d.ts.map +1 -1
- package/build-types/link/link.d.ts.map +1 -1
- package/build-types/link/types.d.ts +1 -2
- package/build-types/link/types.d.ts.map +1 -1
- package/build-types/notice/action-link.d.ts.map +1 -1
- package/build-types/popover/context.d.ts +1 -1
- package/build-types/popover/context.d.ts.map +1 -1
- package/build-types/popover/popup.d.ts.map +1 -1
- package/build-types/popover/stories/index.story.d.ts +1 -1
- package/build-types/popover/stories/index.story.d.ts.map +1 -1
- package/build-types/popover/title.d.ts.map +1 -1
- package/build-types/stack/stories/index.story.d.ts.map +1 -1
- package/build-types/tabs/context.d.ts.map +1 -1
- package/build-types/tabs/panel.d.ts.map +1 -1
- package/build-types/tabs/stories/index.story.d.ts +1 -1
- package/build-types/tabs/stories/index.story.d.ts.map +1 -1
- package/build-types/text/stories/index.story.d.ts.map +1 -1
- package/build-types/tooltip/stories/index.story.d.ts +1 -1
- package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
- package/build-types/tooltip/stories/usage-guidelines.story.d.ts.map +1 -1
- package/build-types/utils/use-schedule-validation.d.ts +13 -0
- package/build-types/utils/use-schedule-validation.d.ts.map +1 -0
- package/package.json +11 -11
- package/src/alert-dialog/stories/index.story.tsx +2 -2
- package/src/badge/stories/choosing-intent.story.tsx +1 -1
- package/src/badge/stories/index.story.tsx +1 -0
- package/src/collapsible-card/header.tsx +2 -0
- package/src/dialog/context.tsx +28 -15
- package/src/dialog/style.module.css +12 -0
- package/src/dialog/test/index.test.tsx +222 -142
- package/src/dialog/title.tsx +6 -4
- package/src/empty-state/stories/index.story.tsx +2 -1
- package/src/form/input-control/stories/index.story.tsx +4 -1
- package/src/form/primitives/field/stories/index.story.tsx +1 -1
- package/src/form/primitives/fieldset/stories/index.story.tsx +1 -1
- package/src/form/primitives/input/stories/index.story.tsx +2 -1
- package/src/form/primitives/input-layout/stories/index.story.tsx +2 -1
- package/src/form/primitives/select/stories/index.story.tsx +1 -1
- package/src/link/link.tsx +12 -26
- package/src/link/style.module.css +4 -16
- package/src/link/test/index.test.tsx +31 -27
- package/src/link/types.ts +1 -2
- package/src/notice/action-link.tsx +7 -4
- package/src/notice/style.module.css +5 -5
- package/src/popover/context.tsx +28 -12
- package/src/popover/popup.tsx +4 -1
- package/src/popover/stories/index.story.tsx +2 -1
- package/src/popover/style.module.css +23 -1
- package/src/popover/test/index.test.tsx +146 -70
- package/src/popover/title.tsx +6 -3
- package/src/stack/stories/index.story.tsx +1 -0
- package/src/tabs/context.tsx +14 -34
- package/src/tabs/panel.tsx +7 -2
- package/src/tabs/stories/index.story.tsx +2 -1
- package/src/tabs/style.module.css +0 -17
- package/src/tabs/test/index.test.tsx +7 -3
- package/src/text/stories/index.story.tsx +1 -0
- package/src/tooltip/stories/index.story.tsx +2 -1
- package/src/tooltip/stories/usage-guidelines.story.tsx +5 -1
- package/src/tooltip/style.module.css +12 -0
- package/src/utils/css/item-popup.module.css +12 -0
- package/src/utils/use-schedule-validation.ts +45 -0
- package/build/types/css-modules.d.cjs +0 -2
- package/build/types/css-modules.d.cjs.map +0 -7
- package/build/types/react.d.cjs +0 -5
- package/build/types/react.d.cjs.map +0 -7
- package/build-module/types/css-modules.d.mjs +0 -1
- package/build-module/types/css-modules.d.mjs.map +0 -7
- package/build-module/types/react.d.mjs +0 -3
- package/build-module/types/react.d.mjs.map +0 -7
- package/src/types/css-modules.d.ts +0 -4
- package/src/types/react.d.ts +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1-next.v.202604201441.0+dab6d8c07",
|
|
4
4
|
"description": "Themeable React UI components for the WordPress Design System.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
"sideEffects": false,
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@base-ui/react": "^1.4.0",
|
|
47
|
-
"@wordpress/a11y": "^4.44.0",
|
|
48
|
-
"@wordpress/compose": "^7.44.0",
|
|
49
|
-
"@wordpress/element": "^6.44.0",
|
|
50
|
-
"@wordpress/i18n": "^6.17.0",
|
|
51
|
-
"@wordpress/icons": "^12.2.0",
|
|
52
|
-
"@wordpress/keycodes": "^4.44.0",
|
|
53
|
-
"@wordpress/primitives": "^4.44.0",
|
|
54
|
-
"@wordpress/private-apis": "^1.44.0",
|
|
55
|
-
"@wordpress/theme": "^0.11.0",
|
|
47
|
+
"@wordpress/a11y": "^4.44.1-next.v.202604201441.0+dab6d8c07",
|
|
48
|
+
"@wordpress/compose": "^7.44.1-next.v.202604201441.0+dab6d8c07",
|
|
49
|
+
"@wordpress/element": "^6.44.1-next.v.202604201441.0+dab6d8c07",
|
|
50
|
+
"@wordpress/i18n": "^6.17.1-next.v.202604201441.0+dab6d8c07",
|
|
51
|
+
"@wordpress/icons": "^12.2.1-next.v.202604201441.0+dab6d8c07",
|
|
52
|
+
"@wordpress/keycodes": "^4.44.1-next.v.202604201441.0+dab6d8c07",
|
|
53
|
+
"@wordpress/primitives": "^4.44.1-next.v.202604201441.0+dab6d8c07",
|
|
54
|
+
"@wordpress/private-apis": "^1.44.1-next.v.202604201441.0+dab6d8c07",
|
|
55
|
+
"@wordpress/theme": "^0.11.1-next.v.202604201441.0+dab6d8c07",
|
|
56
56
|
"clsx": "^2.1.1",
|
|
57
57
|
"tabbable": "^6.4.0"
|
|
58
58
|
},
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"publishConfig": {
|
|
72
72
|
"access": "public"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "c788005ba4ee2a34851c1217c51602656aa7c3a6"
|
|
75
75
|
}
|
|
@@ -3,8 +3,8 @@ import { useId, useState } from '@wordpress/element';
|
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
4
|
import { action } from 'storybook/actions';
|
|
5
5
|
import { fn } from 'storybook/test';
|
|
6
|
-
|
|
7
|
-
import {
|
|
6
|
+
import * as AlertDialog from '../';
|
|
7
|
+
import { Text } from '../../text';
|
|
8
8
|
|
|
9
9
|
const meta: Meta< typeof AlertDialog.Root > = {
|
|
10
10
|
title: 'Design System/Components/AlertDialog',
|
|
@@ -15,7 +15,7 @@ const meta: Meta< typeof Badge > = {
|
|
|
15
15
|
parameters: {
|
|
16
16
|
controls: { disable: true },
|
|
17
17
|
},
|
|
18
|
-
tags: [ '!dev' /* Hide individual story pages from sidebar
|
|
18
|
+
tags: [ '!dev' /* Hide individual story pages from sidebar */, 'manifest' ],
|
|
19
19
|
};
|
|
20
20
|
export default meta;
|
|
21
21
|
|
|
@@ -5,6 +5,7 @@ import * as Card from '../card';
|
|
|
5
5
|
import * as Collapsible from '../collapsible';
|
|
6
6
|
import { Icon } from '../icon';
|
|
7
7
|
import styles from './style.module.css';
|
|
8
|
+
import defenseStyles from '../utils/css/global-css-defense.module.css';
|
|
8
9
|
import focusStyles from '../utils/css/focus.module.css';
|
|
9
10
|
import { HeaderDescriptionIdContext } from './context';
|
|
10
11
|
import type { HeaderProps } from './types';
|
|
@@ -55,6 +56,7 @@ export const Header = forwardRef< HTMLDivElement, HeaderProps >(
|
|
|
55
56
|
<div
|
|
56
57
|
className={ clsx(
|
|
57
58
|
styles[ 'header-trigger-wrapper' ],
|
|
59
|
+
defenseStyles.div,
|
|
58
60
|
// While the interactive trigger element is the whole header,
|
|
59
61
|
// the focus ring will be displayed only on the icon to visually
|
|
60
62
|
// emulate it being the button.
|
package/src/dialog/context.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useMemo,
|
|
7
7
|
useRef,
|
|
8
8
|
} from '@wordpress/element';
|
|
9
|
+
import { useScheduleValidation } from '../utils/use-schedule-validation';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Whether validation is enabled. This is a build-time constant that allows
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
const VALIDATION_ENABLED = process.env.NODE_ENV !== 'production';
|
|
15
16
|
|
|
16
17
|
type DialogValidationContextType = {
|
|
17
|
-
registerTitle: ( element: HTMLElement | null ) => void;
|
|
18
|
+
registerTitle: ( element: HTMLElement | null ) => () => void;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
// Context is only created in development mode.
|
|
@@ -54,19 +55,7 @@ function DialogValidationProviderDev( {
|
|
|
54
55
|
} ) {
|
|
55
56
|
const titleElementRef = useRef< HTMLElement | null >( null );
|
|
56
57
|
|
|
57
|
-
const
|
|
58
|
-
titleElementRef.current = element;
|
|
59
|
-
}, [] );
|
|
60
|
-
|
|
61
|
-
const contextValue = useMemo(
|
|
62
|
-
() => ( { registerTitle } ),
|
|
63
|
-
[ registerTitle ]
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// Validate that Dialog.Title is rendered with non-empty text content
|
|
67
|
-
useEffect( () => {
|
|
68
|
-
// useLayoutEffect in Title runs before this useEffect,
|
|
69
|
-
// so titleElementRef should already be set if Title is present
|
|
58
|
+
const scheduleValidation = useScheduleValidation( () => {
|
|
70
59
|
const titleElement = titleElementRef.current;
|
|
71
60
|
|
|
72
61
|
if ( ! titleElement ) {
|
|
@@ -84,7 +73,31 @@ function DialogValidationProviderDev( {
|
|
|
84
73
|
'Provide meaningful text content for the dialog title.'
|
|
85
74
|
);
|
|
86
75
|
}
|
|
87
|
-
}
|
|
76
|
+
} );
|
|
77
|
+
|
|
78
|
+
const registerTitle = useCallback(
|
|
79
|
+
( element: HTMLElement | null ) => {
|
|
80
|
+
titleElementRef.current = element;
|
|
81
|
+
scheduleValidation();
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
titleElementRef.current = null;
|
|
85
|
+
scheduleValidation();
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
[ scheduleValidation ]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Schedule an initial validation on mount to catch missing titles
|
|
92
|
+
// (when no Title component is rendered, registerTitle is never called).
|
|
93
|
+
useEffect( () => {
|
|
94
|
+
scheduleValidation();
|
|
95
|
+
}, [ scheduleValidation ] );
|
|
96
|
+
|
|
97
|
+
const contextValue = useMemo(
|
|
98
|
+
() => ( { registerTitle } ),
|
|
99
|
+
[ registerTitle ]
|
|
100
|
+
);
|
|
88
101
|
|
|
89
102
|
return (
|
|
90
103
|
<DialogValidationContext.Provider value={ contextValue }>
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
@layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides;
|
|
2
2
|
|
|
3
|
+
/*
|
|
4
|
+
* Temporary workaround for a Base UI tabbability regression with
|
|
5
|
+
* checkVisibility() and display: contents.
|
|
6
|
+
* See: https://github.com/mui/base-ui/issues/4622
|
|
7
|
+
*
|
|
8
|
+
* This must stay outside the CSS layers to override ThemeProvider's
|
|
9
|
+
* unlayered display: contents.
|
|
10
|
+
*/
|
|
11
|
+
[data-wpds-theme-provider-id]:has(> .popup) {
|
|
12
|
+
display: block;
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
@layer wp-ui-components {
|
|
4
16
|
.backdrop {
|
|
5
17
|
position: fixed;
|
|
@@ -1,36 +1,19 @@
|
|
|
1
1
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import {
|
|
4
|
-
import type { ReactNode } from 'react';
|
|
3
|
+
import { createRef, useState } from '@wordpress/element';
|
|
5
4
|
import * as Dialog from '../index';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
static getDerivedStateFromError() {
|
|
20
|
-
return { hasError: true };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
componentDidCatch( error: Error ) {
|
|
24
|
-
this.props.onError( error );
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
render() {
|
|
28
|
-
if ( this.state.hasError ) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return this.props.children;
|
|
33
|
-
}
|
|
6
|
+
function collectUncaughtErrors() {
|
|
7
|
+
const errors: Error[] = [];
|
|
8
|
+
const handler = ( event: ErrorEvent ) => {
|
|
9
|
+
event.preventDefault();
|
|
10
|
+
errors.push( event.error );
|
|
11
|
+
};
|
|
12
|
+
window.addEventListener( 'error', handler );
|
|
13
|
+
return {
|
|
14
|
+
errors,
|
|
15
|
+
cleanup: () => window.removeEventListener( 'error', handler ),
|
|
16
|
+
};
|
|
34
17
|
}
|
|
35
18
|
|
|
36
19
|
describe( 'Dialog', () => {
|
|
@@ -81,7 +64,9 @@ describe( 'Dialog', () => {
|
|
|
81
64
|
} );
|
|
82
65
|
|
|
83
66
|
describe( 'Development mode validation', () => {
|
|
84
|
-
// Suppress
|
|
67
|
+
// Suppress console.error from React act() warnings and jsdom
|
|
68
|
+
// unhandled-error logging. Validation errors are caught via
|
|
69
|
+
// collectUncaughtErrors (window 'error' event) instead.
|
|
85
70
|
let originalConsoleError: typeof console.error;
|
|
86
71
|
|
|
87
72
|
beforeEach( () => {
|
|
@@ -98,210 +83,305 @@ describe( 'Dialog', () => {
|
|
|
98
83
|
|
|
99
84
|
it( 'should throw when Dialog.Title is missing', async () => {
|
|
100
85
|
const user = userEvent.setup();
|
|
101
|
-
const
|
|
86
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
102
87
|
|
|
103
88
|
render(
|
|
104
|
-
<
|
|
105
|
-
<Dialog.
|
|
106
|
-
|
|
107
|
-
<Dialog.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<Dialog.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
</Dialog.Root>
|
|
117
|
-
</TestErrorBoundary>
|
|
89
|
+
<Dialog.Root>
|
|
90
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
91
|
+
<Dialog.Popup>
|
|
92
|
+
<Dialog.Header>
|
|
93
|
+
{ /* Missing Dialog.Title */ }
|
|
94
|
+
</Dialog.Header>
|
|
95
|
+
<p>Content without a title</p>
|
|
96
|
+
<Dialog.Footer>
|
|
97
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
98
|
+
</Dialog.Footer>
|
|
99
|
+
</Dialog.Popup>
|
|
100
|
+
</Dialog.Root>
|
|
118
101
|
);
|
|
119
102
|
|
|
120
|
-
// Open the dialog - this will trigger the error in useEffect
|
|
121
103
|
await user.click(
|
|
122
104
|
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
123
105
|
);
|
|
124
106
|
|
|
125
107
|
await waitFor( () => {
|
|
126
|
-
expect(
|
|
108
|
+
expect( errors.length ).toBeGreaterThan( 0 );
|
|
127
109
|
} );
|
|
128
110
|
|
|
129
|
-
expect(
|
|
130
|
-
expect( ( onError.mock.calls[ 0 ][ 0 ] as Error ).message ).toBe(
|
|
111
|
+
expect( errors[ 0 ].message ).toBe(
|
|
131
112
|
'Dialog: Missing <Dialog.Title>. ' +
|
|
132
113
|
'For accessibility, every dialog requires a title. ' +
|
|
133
114
|
'If needed, the title can be visually hidden but must not be omitted.'
|
|
134
115
|
);
|
|
116
|
+
|
|
117
|
+
cleanup();
|
|
135
118
|
} );
|
|
136
119
|
|
|
137
120
|
it( 'should not throw before opening the dialog', async () => {
|
|
138
|
-
const
|
|
121
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
139
122
|
|
|
140
123
|
render(
|
|
141
|
-
<
|
|
142
|
-
<Dialog.
|
|
143
|
-
|
|
144
|
-
<Dialog.
|
|
145
|
-
<Dialog.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<Dialog.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
</Dialog.Root>
|
|
154
|
-
</TestErrorBoundary>
|
|
124
|
+
<Dialog.Root>
|
|
125
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
126
|
+
<Dialog.Popup>
|
|
127
|
+
<Dialog.Header>
|
|
128
|
+
<Dialog.Title>My Title</Dialog.Title>
|
|
129
|
+
</Dialog.Header>
|
|
130
|
+
<p>Content with a title</p>
|
|
131
|
+
<Dialog.Footer>
|
|
132
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
133
|
+
</Dialog.Footer>
|
|
134
|
+
</Dialog.Popup>
|
|
135
|
+
</Dialog.Root>
|
|
155
136
|
);
|
|
156
137
|
|
|
157
|
-
// Check that the dialog itself hasn't been rendered in the DOM.
|
|
158
138
|
await expect( screen.findByRole( 'dialog' ) ).rejects.toThrow();
|
|
139
|
+
expect( errors ).toHaveLength( 0 );
|
|
159
140
|
|
|
160
|
-
|
|
141
|
+
cleanup();
|
|
161
142
|
} );
|
|
162
143
|
|
|
163
144
|
it( 'should not throw when Dialog.Title is present', async () => {
|
|
164
145
|
const user = userEvent.setup();
|
|
165
|
-
const
|
|
146
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
166
147
|
|
|
167
148
|
render(
|
|
168
|
-
<
|
|
169
|
-
<Dialog.
|
|
170
|
-
|
|
171
|
-
<Dialog.
|
|
172
|
-
<Dialog.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<Dialog.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
</Dialog.Root>
|
|
181
|
-
</TestErrorBoundary>
|
|
149
|
+
<Dialog.Root>
|
|
150
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
151
|
+
<Dialog.Popup>
|
|
152
|
+
<Dialog.Header>
|
|
153
|
+
<Dialog.Title>My Title</Dialog.Title>
|
|
154
|
+
</Dialog.Header>
|
|
155
|
+
<p>Content with a title</p>
|
|
156
|
+
<Dialog.Footer>
|
|
157
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
158
|
+
</Dialog.Footer>
|
|
159
|
+
</Dialog.Popup>
|
|
160
|
+
</Dialog.Root>
|
|
182
161
|
);
|
|
183
162
|
|
|
184
|
-
// Open the dialog - should not throw
|
|
185
163
|
await user.click(
|
|
186
164
|
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
187
165
|
);
|
|
188
166
|
|
|
189
|
-
// Wait for the dialog to appear and ensure validation does not trigger errors
|
|
190
167
|
await waitFor( () => {
|
|
191
168
|
expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
|
|
192
169
|
} );
|
|
193
|
-
|
|
170
|
+
|
|
171
|
+
// Allow deferred validation to settle.
|
|
172
|
+
await new Promise( ( resolve ) => setTimeout( resolve, 50 ) );
|
|
173
|
+
expect( errors ).toHaveLength( 0 );
|
|
174
|
+
|
|
175
|
+
cleanup();
|
|
194
176
|
} );
|
|
195
177
|
|
|
196
178
|
it( 'should throw when Dialog.Title is empty', async () => {
|
|
197
179
|
const user = userEvent.setup();
|
|
198
|
-
const
|
|
180
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
199
181
|
|
|
200
182
|
render(
|
|
201
|
-
<
|
|
202
|
-
<Dialog.
|
|
203
|
-
|
|
204
|
-
<Dialog.
|
|
205
|
-
<Dialog.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
</Dialog.Popup>
|
|
214
|
-
</Dialog.Root>
|
|
215
|
-
</TestErrorBoundary>
|
|
183
|
+
<Dialog.Root>
|
|
184
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
185
|
+
<Dialog.Popup>
|
|
186
|
+
<Dialog.Header>
|
|
187
|
+
<Dialog.Title />
|
|
188
|
+
</Dialog.Header>
|
|
189
|
+
<p>Content with empty title</p>
|
|
190
|
+
<Dialog.Footer>
|
|
191
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
192
|
+
</Dialog.Footer>
|
|
193
|
+
</Dialog.Popup>
|
|
194
|
+
</Dialog.Root>
|
|
216
195
|
);
|
|
217
196
|
|
|
218
|
-
// Open the dialog - this will trigger the error
|
|
219
197
|
await user.click(
|
|
220
198
|
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
221
199
|
);
|
|
222
200
|
|
|
223
201
|
await waitFor( () => {
|
|
224
|
-
expect(
|
|
202
|
+
expect( errors.length ).toBeGreaterThan( 0 );
|
|
225
203
|
} );
|
|
226
204
|
|
|
227
|
-
expect(
|
|
228
|
-
expect( ( onError.mock.calls[ 0 ][ 0 ] as Error ).message ).toBe(
|
|
205
|
+
expect( errors[ 0 ].message ).toBe(
|
|
229
206
|
'Dialog: <Dialog.Title> cannot be empty. ' +
|
|
230
207
|
'Provide meaningful text content for the dialog title.'
|
|
231
208
|
);
|
|
209
|
+
|
|
210
|
+
cleanup();
|
|
232
211
|
} );
|
|
233
212
|
|
|
234
213
|
it( 'should throw when Dialog.Title contains only whitespace', async () => {
|
|
235
214
|
const user = userEvent.setup();
|
|
236
|
-
const
|
|
215
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
237
216
|
|
|
238
217
|
render(
|
|
239
|
-
<
|
|
240
|
-
<Dialog.
|
|
241
|
-
|
|
242
|
-
<Dialog.
|
|
243
|
-
<Dialog.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
<Dialog.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
</Dialog.Root>
|
|
252
|
-
</TestErrorBoundary>
|
|
218
|
+
<Dialog.Root>
|
|
219
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
220
|
+
<Dialog.Popup>
|
|
221
|
+
<Dialog.Header>
|
|
222
|
+
<Dialog.Title> </Dialog.Title>
|
|
223
|
+
</Dialog.Header>
|
|
224
|
+
<p>Content with whitespace-only title</p>
|
|
225
|
+
<Dialog.Footer>
|
|
226
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
227
|
+
</Dialog.Footer>
|
|
228
|
+
</Dialog.Popup>
|
|
229
|
+
</Dialog.Root>
|
|
253
230
|
);
|
|
254
231
|
|
|
255
|
-
// Open the dialog - this will trigger the error
|
|
256
232
|
await user.click(
|
|
257
233
|
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
258
234
|
);
|
|
259
235
|
|
|
260
236
|
await waitFor( () => {
|
|
261
|
-
expect(
|
|
237
|
+
expect( errors.length ).toBeGreaterThan( 0 );
|
|
262
238
|
} );
|
|
263
239
|
|
|
264
|
-
expect(
|
|
265
|
-
expect( ( onError.mock.calls[ 0 ][ 0 ] as Error ).message ).toBe(
|
|
240
|
+
expect( errors[ 0 ].message ).toBe(
|
|
266
241
|
'Dialog: <Dialog.Title> cannot be empty. ' +
|
|
267
242
|
'Provide meaningful text content for the dialog title.'
|
|
268
243
|
);
|
|
244
|
+
|
|
245
|
+
cleanup();
|
|
269
246
|
} );
|
|
270
247
|
|
|
271
248
|
it( 'should not throw when Dialog.Title contains mixed content with text', async () => {
|
|
272
249
|
const user = userEvent.setup();
|
|
273
|
-
const
|
|
250
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
274
251
|
|
|
275
252
|
render(
|
|
276
|
-
<
|
|
253
|
+
<Dialog.Root>
|
|
254
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
255
|
+
<Dialog.Popup>
|
|
256
|
+
<Dialog.Header>
|
|
257
|
+
<Dialog.Title>
|
|
258
|
+
<span aria-hidden="true">🎉</span>
|
|
259
|
+
Settings
|
|
260
|
+
</Dialog.Title>
|
|
261
|
+
</Dialog.Header>
|
|
262
|
+
<p>Content with icon and text title</p>
|
|
263
|
+
<Dialog.Footer>
|
|
264
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
265
|
+
</Dialog.Footer>
|
|
266
|
+
</Dialog.Popup>
|
|
267
|
+
</Dialog.Root>
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
await user.click(
|
|
271
|
+
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
await waitFor( () => {
|
|
275
|
+
expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
|
|
276
|
+
} );
|
|
277
|
+
|
|
278
|
+
await new Promise( ( resolve ) => setTimeout( resolve, 50 ) );
|
|
279
|
+
expect( errors ).toHaveLength( 0 );
|
|
280
|
+
|
|
281
|
+
cleanup();
|
|
282
|
+
} );
|
|
283
|
+
|
|
284
|
+
it( 'should throw when title is removed after mount', async () => {
|
|
285
|
+
const user = userEvent.setup();
|
|
286
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
287
|
+
|
|
288
|
+
function Test() {
|
|
289
|
+
const [ showTitle, setShowTitle ] = useState( true );
|
|
290
|
+
return (
|
|
277
291
|
<Dialog.Root>
|
|
278
|
-
<Dialog.Trigger>Open
|
|
292
|
+
<Dialog.Trigger>Open</Dialog.Trigger>
|
|
279
293
|
<Dialog.Popup>
|
|
280
|
-
|
|
281
|
-
<Dialog.Title>
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
</
|
|
286
|
-
<p>Content with icon and text title</p>
|
|
287
|
-
<Dialog.Footer>
|
|
288
|
-
<Dialog.Action>Close</Dialog.Action>
|
|
289
|
-
</Dialog.Footer>
|
|
294
|
+
{ showTitle && (
|
|
295
|
+
<Dialog.Title>My Title</Dialog.Title>
|
|
296
|
+
) }
|
|
297
|
+
<button onClick={ () => setShowTitle( false ) }>
|
|
298
|
+
Remove Title
|
|
299
|
+
</button>
|
|
290
300
|
</Dialog.Popup>
|
|
291
301
|
</Dialog.Root>
|
|
292
|
-
|
|
293
|
-
|
|
302
|
+
);
|
|
303
|
+
}
|
|
294
304
|
|
|
295
|
-
|
|
305
|
+
render( <Test /> );
|
|
306
|
+
|
|
307
|
+
await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
|
|
308
|
+
|
|
309
|
+
await waitFor( () => {
|
|
310
|
+
expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
|
|
311
|
+
} );
|
|
312
|
+
|
|
313
|
+
// Let initial validation settle — no errors expected.
|
|
314
|
+
await new Promise( ( resolve ) => setTimeout( resolve, 50 ) );
|
|
315
|
+
expect( errors ).toHaveLength( 0 );
|
|
316
|
+
|
|
317
|
+
// Remove the title.
|
|
296
318
|
await user.click(
|
|
297
|
-
screen.getByRole( 'button', { name: '
|
|
319
|
+
screen.getByRole( 'button', { name: 'Remove Title' } )
|
|
298
320
|
);
|
|
299
321
|
|
|
300
|
-
|
|
322
|
+
await waitFor( () => {
|
|
323
|
+
expect( errors.length ).toBeGreaterThan( 0 );
|
|
324
|
+
} );
|
|
325
|
+
|
|
326
|
+
expect( errors[ 0 ].message ).toBe(
|
|
327
|
+
'Dialog: Missing <Dialog.Title>. ' +
|
|
328
|
+
'For accessibility, every dialog requires a title. ' +
|
|
329
|
+
'If needed, the title can be visually hidden but must not be omitted.'
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
cleanup();
|
|
333
|
+
} );
|
|
334
|
+
|
|
335
|
+
it( 'should recover when title is added back', async () => {
|
|
336
|
+
const user = userEvent.setup();
|
|
337
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
338
|
+
|
|
339
|
+
function Test() {
|
|
340
|
+
const [ showTitle, setShowTitle ] = useState( false );
|
|
341
|
+
return (
|
|
342
|
+
<Dialog.Root>
|
|
343
|
+
<Dialog.Trigger>Open</Dialog.Trigger>
|
|
344
|
+
<Dialog.Popup>
|
|
345
|
+
{ showTitle && (
|
|
346
|
+
<Dialog.Title>My Title</Dialog.Title>
|
|
347
|
+
) }
|
|
348
|
+
<button
|
|
349
|
+
onClick={ () => setShowTitle( ( s ) => ! s ) }
|
|
350
|
+
>
|
|
351
|
+
Toggle Title
|
|
352
|
+
</button>
|
|
353
|
+
</Dialog.Popup>
|
|
354
|
+
</Dialog.Root>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
render( <Test /> );
|
|
359
|
+
|
|
360
|
+
await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
|
|
361
|
+
|
|
301
362
|
await waitFor( () => {
|
|
302
363
|
expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
|
|
303
364
|
} );
|
|
304
|
-
|
|
365
|
+
|
|
366
|
+
// Initially no title — should error.
|
|
367
|
+
await waitFor( () => {
|
|
368
|
+
expect( errors.length ).toBeGreaterThan( 0 );
|
|
369
|
+
} );
|
|
370
|
+
|
|
371
|
+
const errorCountAfterInitial = errors.length;
|
|
372
|
+
|
|
373
|
+
// Add the title back.
|
|
374
|
+
await user.click(
|
|
375
|
+
screen.getByRole( 'button', { name: 'Toggle Title' } )
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// Wait for deferred validation to settle.
|
|
379
|
+
await new Promise( ( resolve ) => setTimeout( resolve, 50 ) );
|
|
380
|
+
|
|
381
|
+
// No new errors should have been thrown.
|
|
382
|
+
expect( errors ).toHaveLength( errorCountAfterInitial );
|
|
383
|
+
|
|
384
|
+
cleanup();
|
|
305
385
|
} );
|
|
306
386
|
} );
|
|
307
387
|
|