From 8d9e53b84a0b3a5aa61a31da38ba9c0f43b95c58 Mon Sep 17 00:00:00 2001
From: Huub <50170696+huubl@users.noreply.github.com>
Date: Wed, 2 Oct 2024 16:34:19 +0200
Subject: [PATCH] Revert "Style book: create static categories (#65430)"
This reverts commit 6d8f2a8cb7d9da8061a495caecd3e79a98cb2309.
---
.../src/components/style-book/categories.ts | 91 --------
.../src/components/style-book/constants.ts | 191 ---------------
.../src/components/style-book/examples.ts | 63 -----
.../src/components/style-book/index.js | 221 ++++++++++++------
.../components/style-book/test/categories.js | 171 --------------
.../src/components/style-book/types.ts | 27 ---
test/e2e/specs/site-editor/style-book.spec.js | 3 +
7 files changed, 158 insertions(+), 609 deletions(-)
delete mode 100644 packages/edit-site/src/components/style-book/categories.ts
delete mode 100644 packages/edit-site/src/components/style-book/constants.ts
delete mode 100644 packages/edit-site/src/components/style-book/examples.ts
delete mode 100644 packages/edit-site/src/components/style-book/test/categories.js
delete mode 100644 packages/edit-site/src/components/style-book/types.ts
diff --git a/packages/edit-site/src/components/style-book/categories.ts b/packages/edit-site/src/components/style-book/categories.ts
deleted file mode 100644
index 2c1b627c6d0c60..00000000000000
--- a/packages/edit-site/src/components/style-book/categories.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { getCategories } from '@wordpress/blocks';
-
-/**
- * Internal dependencies
- */
-import type {
- BlockExample,
- StyleBookCategory,
- CategoryExamples,
-} from './types';
-import {
- STYLE_BOOK_CATEGORIES,
- STYLE_BOOK_THEME_SUBCATEGORIES,
-} from './constants';
-
-/**
- * Returns category examples for a given category definition and list of examples.
- * @param {StyleBookCategory} categoryDefinition The category definition.
- * @param {BlockExample[]} examples An array of block examples.
- * @return {CategoryExamples|undefined} An object containing the category examples.
- */
-export function getExamplesByCategory(
- categoryDefinition: StyleBookCategory,
- examples: BlockExample[]
-): CategoryExamples | undefined {
- if ( ! categoryDefinition?.slug || ! examples?.length ) {
- return;
- }
-
- if ( categoryDefinition?.subcategories?.length ) {
- return categoryDefinition.subcategories.reduce(
- ( acc, subcategoryDefinition ) => {
- const subcategoryExamples = getExamplesByCategory(
- subcategoryDefinition,
- examples
- );
- if ( subcategoryExamples ) {
- acc.subcategories = [
- ...acc.subcategories,
- subcategoryExamples,
- ];
- }
- return acc;
- },
- {
- title: categoryDefinition.title,
- slug: categoryDefinition.slug,
- subcategories: [],
- }
- );
- }
-
- const blocksToInclude = categoryDefinition?.blocks || [];
- const blocksToExclude = categoryDefinition?.exclude || [];
- const categoryExamples = examples.filter( ( example ) => {
- return (
- ! blocksToExclude.includes( example.name ) &&
- ( example.category === categoryDefinition.slug ||
- blocksToInclude.includes( example.name ) )
- );
- } );
-
- if ( ! categoryExamples.length ) {
- return;
- }
-
- return {
- title: categoryDefinition.title,
- slug: categoryDefinition.slug,
- examples: categoryExamples,
- };
-}
-
-/**
- * Returns category examples for a given category definition and list of examples.
- *
- * @return {StyleBookCategory[]} An array of top-level category definitions.
- */
-export function getTopLevelStyleBookCategories(): StyleBookCategory[] {
- const reservedCategories = [
- ...STYLE_BOOK_THEME_SUBCATEGORIES,
- ...STYLE_BOOK_CATEGORIES,
- ].map( ( { slug } ) => slug );
- const extraCategories = getCategories().filter(
- ( { slug } ) => ! reservedCategories.includes( slug )
- );
- return [ ...STYLE_BOOK_CATEGORIES, ...extraCategories ];
-}
diff --git a/packages/edit-site/src/components/style-book/constants.ts b/packages/edit-site/src/components/style-book/constants.ts
deleted file mode 100644
index fc06d8f1409f0d..00000000000000
--- a/packages/edit-site/src/components/style-book/constants.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { __ } from '@wordpress/i18n';
-
-/**
- * Internal dependencies
- */
-import type { StyleBookCategory } from './types';
-
-export const STYLE_BOOK_THEME_SUBCATEGORIES: Omit<
- StyleBookCategory,
- 'subcategories'
->[] = [
- {
- slug: 'site-identity',
- title: __( 'Site Identity' ),
- blocks: [ 'core/site-logo', 'core/site-title', 'core/site-tagline' ],
- },
- {
- slug: 'design',
- title: __( 'Design' ),
- blocks: [ 'core/navigation', 'core/avatar', 'core/post-time-to-read' ],
- exclude: [ 'core/home-link', 'core/navigation-link' ],
- },
- {
- slug: 'posts',
- title: __( 'Posts' ),
- blocks: [
- 'core/post-title',
- 'core/post-excerpt',
- 'core/post-author',
- 'core/post-author-name',
- 'core/post-author-biography',
- 'core/post-date',
- 'core/post-terms',
- 'core/term-description',
- 'core/query-title',
- 'core/query-no-results',
- 'core/query-pagination',
- 'core/query-numbers',
- ],
- },
- {
- slug: 'comments',
- title: __( 'Comments' ),
- blocks: [
- 'core/comments-title',
- 'core/comments-pagination',
- 'core/comments-pagination-numbers',
- 'core/comments',
- 'core/comments-author-name',
- 'core/comment-content',
- 'core/comment-date',
- 'core/comment-edit-link',
- 'core/comment-reply-link',
- 'core/comment-template',
- 'core/post-comments-count',
- 'core/post-comments-link',
- ],
- },
-];
-
-export const STYLE_BOOK_CATEGORIES: StyleBookCategory[] = [
- {
- slug: 'text',
- title: __( 'Text' ),
- blocks: [
- 'core/post-content',
- 'core/home-link',
- 'core/navigation-link',
- ],
- },
- {
- slug: 'colors',
- title: __( 'Colors' ),
- blocks: [ 'custom/colors' ],
- },
- {
- slug: 'theme',
- title: __( 'Theme' ),
- subcategories: STYLE_BOOK_THEME_SUBCATEGORIES,
- },
- {
- slug: 'media',
- title: __( 'Media' ),
- blocks: [ 'core/post-featured-image' ],
- },
- {
- slug: 'widgets',
- title: __( 'Widgets' ),
- blocks: [],
- },
- {
- slug: 'embed',
- title: __( 'Embeds' ),
- include: [],
- },
-];
-
-// The content area of the Style Book is rendered within an iframe so that global styles
-// are applied to elements within the entire content area. To support elements that are
-// not part of the block previews, such as headings and layout for the block previews,
-// additional CSS rules need to be passed into the iframe. These are hard-coded below.
-// Note that button styles are unset, and then focus rules from the `Button` component are
-// applied to the `button` element, targeted via `.edit-site-style-book__example`.
-// This is to ensure that browser default styles for buttons are not applied to the previews.
-export const STYLE_BOOK_IFRAME_STYLES = `
- // Forming a "block formatting context" to prevent margin collapsing.
- // @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context
- .is-root-container {
- display: flow-root;
- }
-
- body {
- position: relative;
- padding: 32px !important;
- }
-
- .edit-site-style-book__examples {
- max-width: 1200px;
- margin: 0 auto;
- }
-
- .edit-site-style-book__example {
- max-width: 900px;
- border-radius: 2px;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- gap: 40px;
- padding: 16px;
- width: 100%;
- box-sizing: border-box;
- scroll-margin-top: 32px;
- scroll-margin-bottom: 32px;
- margin: 0 auto 40px auto;
- }
-
- .edit-site-style-book__example.is-selected {
- box-shadow: 0 0 0 1px var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
- }
-
- .edit-site-style-book__example:focus:not(:disabled) {
- box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
- outline: 3px solid transparent;
- }
-
- .edit-site-style-book__examples.is-wide .edit-site-style-book__example {
- flex-direction: row;
- }
-
- .edit-site-style-book__subcategory-title,
- .edit-site-style-book__example-title {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
- font-size: 11px;
- font-weight: 500;
- line-height: normal;
- margin: 0;
- text-align: left;
- text-transform: uppercase;
- }
-
- .edit-site-style-book__subcategory-title {
- font-size: 16px;
- margin-bottom: 40px;
- border-bottom: 1px solid #ddd;
- padding-bottom: 8px;
- }
-
- .edit-site-style-book__examples.is-wide .edit-site-style-book__example-title {
- text-align: right;
- width: 120px;
- }
-
- .edit-site-style-book__example-preview {
- width: 100%;
- }
-
- .edit-site-style-book__example-preview .block-editor-block-list__insertion-point,
- .edit-site-style-book__example-preview .block-list-appender {
- display: none;
- }
-
- .edit-site-style-book__example-preview .is-root-container > .wp-block:first-child {
- margin-top: 0;
- }
- .edit-site-style-book__example-preview .is-root-container > .wp-block:last-child {
- margin-bottom: 0;
- }
-`;
diff --git a/packages/edit-site/src/components/style-book/examples.ts b/packages/edit-site/src/components/style-book/examples.ts
deleted file mode 100644
index 80807b10374c68..00000000000000
--- a/packages/edit-site/src/components/style-book/examples.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { __, sprintf } from '@wordpress/i18n';
-import {
- getBlockType,
- getBlockTypes,
- getBlockFromExample,
- createBlock,
-} from '@wordpress/blocks';
-
-/**
- * Internal dependencies
- */
-import type { BlockExample } from './types';
-
-/**
- * Returns a list of examples for registered block types.
- *
- * @return {BlockExample[]} An array of block examples.
- */
-export function getExamples(): BlockExample[] {
- const nonHeadingBlockExamples = getBlockTypes()
- .filter( ( blockType ) => {
- const { name, example, supports } = blockType;
- return (
- name !== 'core/heading' &&
- !! example &&
- supports.inserter !== false
- );
- } )
- .map( ( blockType ) => ( {
- name: blockType.name,
- title: blockType.title,
- category: blockType.category,
- blocks: getBlockFromExample( blockType.name, blockType.example ),
- } ) );
- const isHeadingBlockRegistered = !! getBlockType( 'core/heading' );
-
- if ( ! isHeadingBlockRegistered ) {
- return nonHeadingBlockExamples;
- }
-
- // Use our own example for the Heading block so that we can show multiple
- // heading levels.
- const headingsExample = {
- name: 'core/heading',
- title: __( 'Headings' ),
- category: 'text',
- blocks: [ 1, 2, 3, 4, 5, 6 ].map( ( level ) => {
- return createBlock( 'core/heading', {
- content: sprintf(
- // translators: %d: heading level e.g: "1", "2", "3"
- __( 'Heading %d' ),
- level
- ),
- level,
- } );
- } ),
- };
-
- return [ headingsExample, ...nonHeadingBlockExamples ];
-}
diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js
index e68474e19f407f..64503dcf7a6dbb 100644
--- a/packages/edit-site/src/components/style-book/index.js
+++ b/packages/edit-site/src/components/style-book/index.js
@@ -12,6 +12,13 @@ import {
privateApis as componentsPrivateApis,
} from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
+import {
+ getCategories,
+ getBlockType,
+ getBlockTypes,
+ getBlockFromExample,
+ createBlock,
+} from '@wordpress/blocks';
import {
BlockList,
privateApis as blockEditorPrivateApis,
@@ -30,12 +37,6 @@ import { ENTER, SPACE } from '@wordpress/keycodes';
*/
import { unlock } from '../../lock-unlock';
import EditorCanvasContainer from '../editor-canvas-container';
-import { STYLE_BOOK_IFRAME_STYLES } from './constants';
-import {
- getExamplesByCategory,
- getTopLevelStyleBookCategories,
-} from './categories';
-import { getExamples } from './examples';
const {
ExperimentalBlockEditorProvider,
@@ -47,10 +48,126 @@ const { mergeBaseAndUserConfigs } = unlock( editorPrivateApis );
const { Tabs } = unlock( componentsPrivateApis );
+// The content area of the Style Book is rendered within an iframe so that global styles
+// are applied to elements within the entire content area. To support elements that are
+// not part of the block previews, such as headings and layout for the block previews,
+// additional CSS rules need to be passed into the iframe. These are hard-coded below.
+// Note that button styles are unset, and then focus rules from the `Button` component are
+// applied to the `button` element, targeted via `.edit-site-style-book__example`.
+// This is to ensure that browser default styles for buttons are not applied to the previews.
+const STYLE_BOOK_IFRAME_STYLES = `
+ .edit-site-style-book__examples {
+ max-width: 900px;
+ margin: 0 auto;
+ }
+
+ .edit-site-style-book__example {
+ border-radius: 2px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ gap: 40px;
+ margin-bottom: 40px;
+ padding: 16px;
+ width: 100%;
+ box-sizing: border-box;
+ scroll-margin-top: 32px;
+ scroll-margin-bottom: 32px;
+ }
+
+ .edit-site-style-book__example.is-selected {
+ box-shadow: 0 0 0 1px var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
+ }
+
+ .edit-site-style-book__example:focus:not(:disabled) {
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
+ outline: 3px solid transparent;
+ }
+
+ .edit-site-style-book__examples.is-wide .edit-site-style-book__example {
+ flex-direction: row;
+ }
+
+ .edit-site-style-book__example-title {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+ font-size: 11px;
+ font-weight: 500;
+ line-height: normal;
+ margin: 0;
+ text-align: left;
+ text-transform: uppercase;
+ }
+
+ .edit-site-style-book__examples.is-wide .edit-site-style-book__example-title {
+ text-align: right;
+ width: 120px;
+ }
+
+ .edit-site-style-book__example-preview {
+ width: 100%;
+ }
+
+ .edit-site-style-book__example-preview .block-editor-block-list__insertion-point,
+ .edit-site-style-book__example-preview .block-list-appender {
+ display: none;
+ }
+
+ .edit-site-style-book__example-preview .is-root-container > .wp-block:first-child {
+ margin-top: 0;
+ }
+ .edit-site-style-book__example-preview .is-root-container > .wp-block:last-child {
+ margin-bottom: 0;
+ }
+`;
+
function isObjectEmpty( object ) {
return ! object || Object.keys( object ).length === 0;
}
+function getExamples() {
+ const nonHeadingBlockExamples = getBlockTypes()
+ .filter( ( blockType ) => {
+ const { name, example, supports } = blockType;
+ return (
+ name !== 'core/heading' &&
+ !! example &&
+ supports.inserter !== false
+ );
+ } )
+ .map( ( blockType ) => ( {
+ name: blockType.name,
+ title: blockType.title,
+ category: blockType.category,
+ blocks: getBlockFromExample( blockType.name, blockType.example ),
+ } ) );
+
+ const isHeadingBlockRegistered = !! getBlockType( 'core/heading' );
+
+ if ( ! isHeadingBlockRegistered ) {
+ return nonHeadingBlockExamples;
+ }
+
+ // Use our own example for the Heading block so that we can show multiple
+ // heading levels.
+ const headingsExample = {
+ name: 'core/heading',
+ title: __( 'Headings' ),
+ category: 'text',
+ blocks: [ 1, 2, 3, 4, 5, 6 ].map( ( level ) => {
+ return createBlock( 'core/heading', {
+ content: sprintf(
+ // translators: %d: heading level e.g: "1", "2", "3"
+ __( 'Heading %d' ),
+ level
+ ),
+ level,
+ } );
+ } ),
+ };
+
+ return [ headingsExample, ...nonHeadingBlockExamples ];
+}
+
function StyleBook( {
enableResizing = true,
isSelected,
@@ -67,11 +184,17 @@ function StyleBook( {
const [ examples ] = useState( getExamples );
const tabs = useMemo(
() =>
- getTopLevelStyleBookCategories().filter( ( category ) =>
- examples.some(
- ( example ) => example.category === category.slug
+ getCategories()
+ .filter( ( category ) =>
+ examples.some(
+ ( example ) => example.category === category.slug
+ )
)
- ),
+ .map( ( category ) => ( {
+ name: category.slug,
+ title: category.title,
+ icon: category.icon,
+ } ) ),
[ examples ]
);
const { base: baseConfig } = useContext( GlobalStylesContext );
@@ -125,8 +248,8 @@ function StyleBook( {
{ tabs.map( ( tab ) => (
{ tab.title }
@@ -134,12 +257,12 @@ function StyleBook( {
{ tabs.map( ( tab ) => (
{
- const categoryDefinition = category
- ? getTopLevelStyleBookCategories().find(
- ( _category ) => _category.slug === category
- )
- : null;
-
- const filteredExamples = categoryDefinition
- ? getExamplesByCategory( categoryDefinition, examples )
- : { examples };
-
return (
- { !! filteredExamples?.examples?.length &&
- filteredExamples.examples.map( ( example ) => (
+ { examples
+ .filter( ( example ) =>
+ category ? example.category === category : true
+ )
+ .map( ( example ) => (
) ) }
- { !! filteredExamples?.subcategories?.length &&
- filteredExamples.subcategories.map( ( subcategory ) => (
-
-
-
- { subcategory.title }
-
-
-
-
- ) ) }
);
}
);
-const Subcategory = ( { examples, isSelected, onSelect } ) => {
- return (
- !! examples?.length &&
- examples.map( ( example ) => (
- {
- onSelect?.( example.name );
- } }
- />
- ) )
- );
-};
-
const Example = ( { id, title, blocks, isSelected, onClick } ) => {
const originalSettings = useSelect(
( select ) => select( blockEditorStore ).getSettings(),
diff --git a/packages/edit-site/src/components/style-book/test/categories.js b/packages/edit-site/src/components/style-book/test/categories.js
deleted file mode 100644
index 5629689e260f89..00000000000000
--- a/packages/edit-site/src/components/style-book/test/categories.js
+++ /dev/null
@@ -1,171 +0,0 @@
-/**
- * Internal dependencies
- */
-import {
- getExamplesByCategory,
- getTopLevelStyleBookCategories,
-} from '../categories';
-import { STYLE_BOOK_CATEGORIES } from '../constants';
-
-jest.mock( '@wordpress/blocks', () => {
- return {
- getCategories() {
- return [
- {
- slug: 'text',
- title: 'Text Registered',
- icon: 'text',
- },
- {
- slug: 'design',
- title: 'Design Registered',
- icon: 'design',
- },
- {
- slug: 'funky',
- title: 'Funky',
- icon: 'funky',
- },
- ];
- },
- };
-} );
-
-// Fixtures
-const exampleThemeBlocks = [
- {
- name: 'core/post-content',
- title: 'Post Content',
- category: 'theme',
- },
- {
- name: 'core/post-terms',
- title: 'Post Terms',
- category: 'theme',
- },
- {
- name: 'core/home-link',
- title: 'Home Link',
- category: 'design',
- },
- {
- name: 'custom/colors',
- title: 'Colors',
- category: 'colors',
- },
- {
- name: 'core/site-logo',
- title: 'Site Logo',
- category: 'theme',
- },
- {
- name: 'core/site-title',
- title: 'Site Title',
- category: 'theme',
- },
- {
- name: 'core/site-tagline',
- title: 'Site Tagline',
- category: 'theme',
- },
- {
- name: 'core/group',
- title: 'Group',
- category: 'design',
- },
- {
- name: 'core/comments-pagination-numbers',
- title: 'Comments Page Numbers',
- category: 'theme',
- },
- {
- name: 'core/post-featured-image',
- title: 'Featured Image',
- category: 'theme',
- },
-];
-
-describe( 'utils', () => {
- describe( 'getTopLevelStyleBookCategories', () => {
- it( 'returns theme subcategories examples', () => {
- expect( getTopLevelStyleBookCategories() ).toEqual( [
- ...STYLE_BOOK_CATEGORIES,
- {
- slug: 'funky',
- title: 'Funky',
- icon: 'funky',
- },
- ] );
- } );
- } );
-
- describe( 'getExamplesByCategory', () => {
- it( 'returns theme subcategories examples', () => {
- const themeCategory = STYLE_BOOK_CATEGORIES.find(
- ( category ) => category.slug === 'theme'
- );
- const themeCategoryExamples = getExamplesByCategory(
- themeCategory,
- exampleThemeBlocks
- );
-
- expect( themeCategoryExamples.slug ).toEqual( 'theme' );
-
- const siteIdentity = themeCategoryExamples.subcategories.find(
- ( subcategory ) => subcategory.slug === 'site-identity'
- );
- expect( siteIdentity ).toEqual( {
- title: 'Site Identity',
- slug: 'site-identity',
- examples: [
- {
- name: 'core/site-logo',
- title: 'Site Logo',
- category: 'theme',
- },
- {
- name: 'core/site-title',
- title: 'Site Title',
- category: 'theme',
- },
- {
- name: 'core/site-tagline',
- title: 'Site Tagline',
- category: 'theme',
- },
- ],
- } );
-
- const design = themeCategoryExamples.subcategories.find(
- ( subcategory ) => subcategory.slug === 'design'
- );
- expect( design ).toEqual( {
- title: 'Design',
- slug: 'design',
- examples: [
- {
- name: 'core/group',
- title: 'Group',
- category: 'design',
- },
- ],
- } );
-
- const posts = themeCategoryExamples.subcategories.find(
- ( subcategory ) => subcategory.slug === 'posts'
- );
-
- expect( posts ).toEqual( {
- title: 'Posts',
- slug: 'posts',
- examples: [
- {
- name: 'core/post-terms',
- title: 'Post Terms',
- category: 'theme',
- },
- ],
- } );
- } );
- } );
-} );
diff --git a/packages/edit-site/src/components/style-book/types.ts b/packages/edit-site/src/components/style-book/types.ts
deleted file mode 100644
index 4729b38b1b2bb1..00000000000000
--- a/packages/edit-site/src/components/style-book/types.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-type Block = {
- name: string;
- attributes: Record< string, unknown >;
- innerBlocks?: Block[];
-};
-
-export type StyleBookCategory = {
- title: string;
- slug: string;
- blocks?: string[];
- exclude?: string[];
- subcategories?: StyleBookCategory[];
-};
-
-export type BlockExample = {
- name: string;
- title: string;
- category: string;
- blocks: Block | Block[];
-};
-
-export type CategoryExamples = {
- title: string;
- slug: string;
- examples?: BlockExample[];
- subcategories?: CategoryExamples[];
-};
diff --git a/test/e2e/specs/site-editor/style-book.spec.js b/test/e2e/specs/site-editor/style-book.spec.js
index 3f871d28ef941b..c4e153e9b5e2fa 100644
--- a/test/e2e/specs/site-editor/style-book.spec.js
+++ b/test/e2e/specs/site-editor/style-book.spec.js
@@ -42,6 +42,9 @@ test.describe( 'Style Book', () => {
test( 'should have tabs containing block examples', async ( { page } ) => {
await expect( page.locator( 'role=tab[name="Text"i]' ) ).toBeVisible();
await expect( page.locator( 'role=tab[name="Media"i]' ) ).toBeVisible();
+ await expect(
+ page.locator( 'role=tab[name="Design"i]' )
+ ).toBeVisible();
await expect(
page.locator( 'role=tab[name="Widgets"i]' )
).toBeVisible();