[Presentation] Migrate all usages of EuiPage*_Deprecated (#161496)

closes https://github.com/elastic/kibana/issues/161428

PR also updates examples title. Instead of naming the embeddable used,
the title now reflects what the example demonstrates.
* "Hello world embeddable" -> "Render embeddable"
* "Todo embeddable" -> "Update embeddable state"
* "List container embeddable" -> "Groups of embeddables"
* "Dynamically adding children to a container" -> "Context menu"

There is a lot more that could be done to enhance these examples, but I
did not want to get more side tracked then I already did.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2023-07-10 12:28:30 -06:00 committed by GitHub
parent 9e5844f715
commit d3d0cdba73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 187 additions and 300 deletions

View file

@ -10,7 +10,7 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { BrowserRouter as Router, withRouter, RouteComponentProps } from 'react-router-dom'; import { BrowserRouter as Router, withRouter, RouteComponentProps } from 'react-router-dom';
import { Route } from '@kbn/shared-ux-router'; import { Route } from '@kbn/shared-ux-router';
import { EuiPage, EuiPageSideBar_Deprecated as EuiPageSideBar, EuiSideNav } from '@elastic/eui'; import { EuiPageTemplate, EuiSideNav } from '@elastic/eui';
import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
@ -51,7 +51,7 @@ const Nav = withRouter(({ history, navigateToApp, pages }: NavProps) => {
<EuiSideNav <EuiSideNav
items={[ items={[
{ {
name: 'Embeddable explorer', name: 'Embeddable examples',
id: 'home', id: 'home',
items: [...navItems], items: [...navItems],
}, },
@ -81,7 +81,7 @@ const EmbeddableExplorerApp = ({
}: Props) => { }: Props) => {
const pages: PageDef[] = [ const pages: PageDef[] = [
{ {
title: 'Hello world embeddable', title: 'Render embeddable',
id: 'helloWorldEmbeddableSection', id: 'helloWorldEmbeddableSection',
component: ( component: (
<HelloWorldEmbeddableExample <HelloWorldEmbeddableExample
@ -90,7 +90,7 @@ const EmbeddableExplorerApp = ({
), ),
}, },
{ {
title: 'Todo embeddable', title: 'Update embeddable state',
id: 'todoEmbeddableSection', id: 'todoEmbeddableSection',
component: ( component: (
<TodoEmbeddableExample <TodoEmbeddableExample
@ -99,17 +99,16 @@ const EmbeddableExplorerApp = ({
), ),
}, },
{ {
title: 'List container embeddable', title: 'Groups of embeddables',
id: 'listContainerSection', id: 'listContainerSection',
component: ( component: (
<ListContainerExample <ListContainerExample
listContainerEmbeddableFactory={embeddableExamples.factories.getListContainerEmbeddableFactory()} listContainerEmbeddableFactory={embeddableExamples.factories.getListContainerEmbeddableFactory()}
searchableListContainerEmbeddableFactory={embeddableExamples.factories.getSearchableListContainerEmbeddableFactory()}
/> />
), ),
}, },
{ {
title: 'Dynamically adding children to a container', title: 'Context menu',
id: 'embeddablePanelExample', id: 'embeddablePanelExample',
component: ( component: (
<EmbeddablePanelExample <EmbeddablePanelExample
@ -126,12 +125,12 @@ const EmbeddableExplorerApp = ({
return ( return (
<Router basename={basename}> <Router basename={basename}>
<EuiPage> <EuiPageTemplate offset={0}>
<EuiPageSideBar> <EuiPageTemplate.Sidebar>
<Nav navigateToApp={navigateToApp} pages={pages} /> <Nav navigateToApp={navigateToApp} pages={pages} />
</EuiPageSideBar> </EuiPageTemplate.Sidebar>
{routes} {routes}
</EuiPage> </EuiPageTemplate>
</Router> </Router>
); );
}; };

View file

@ -7,16 +7,7 @@
*/ */
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { import { EuiPanel, EuiText, EuiPageTemplate } from '@elastic/eui';
EuiPanel,
EuiPageBody,
EuiPageContent_Deprecated as EuiPageContent,
EuiPageContentBody_Deprecated as EuiPageContentBody,
EuiPageHeader,
EuiPageHeaderSection,
EuiTitle,
EuiText,
} from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui';
import { EmbeddableStart, IEmbeddable } from '@kbn/embeddable-plugin/public'; import { EmbeddableStart, IEmbeddable } from '@kbn/embeddable-plugin/public';
import { import {
@ -116,16 +107,10 @@ export function EmbeddablePanelExample({ embeddableServices, searchListContainer
}); });
return ( return (
<EuiPageBody> <>
<EuiPageHeader> <EuiPageTemplate.Header pageTitle="Context menu" />
<EuiPageHeaderSection> <EuiPageTemplate.Section grow={false}>
<EuiTitle size="l"> <>
<h1>The embeddable panel component</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText> <EuiText>
You can render your embeddable inside the EmbeddablePanel component. This adds some You can render your embeddable inside the EmbeddablePanel component. This adds some
extra rendering and offers a context menu with pluggable actions. Using EmbeddablePanel extra rendering and offers a context menu with pluggable actions. Using EmbeddablePanel
@ -142,8 +127,8 @@ export function EmbeddablePanelExample({ embeddableServices, searchListContainer
</EuiPanel> </EuiPanel>
<EuiSpacer /> <EuiSpacer />
</EuiPageContentBody> </>
</EuiPageContent> </EuiPageTemplate.Section>
</EuiPageBody> </>
); );
} }

View file

@ -8,14 +8,12 @@
import React from 'react'; import React from 'react';
import { import {
EuiPageBody, EuiPageTemplate,
EuiPageContent_Deprecated as EuiPageContent,
EuiPageContentBody_Deprecated as EuiPageContentBody,
EuiPageHeader,
EuiPageHeaderSection,
EuiPanel, EuiPanel,
EuiText, EuiText,
EuiTitle, EuiTitle,
EuiCodeBlock,
EuiSpacer,
} from '@elastic/eui'; } from '@elastic/eui';
import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public'; import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public';
import { import {
@ -29,40 +27,46 @@ interface Props {
export function HelloWorldEmbeddableExample({ helloWorldEmbeddableFactory }: Props) { export function HelloWorldEmbeddableExample({ helloWorldEmbeddableFactory }: Props) {
return ( return (
<EuiPageBody> <>
<EuiPageHeader> <EuiPageTemplate.Header pageTitle="Render embeddable" />
<EuiPageHeaderSection> <EuiPageTemplate.Section grow={false} bottomBorder="extended">
<EuiTitle size="l"> <>
<h1>Hello world example</h1> <EuiTitle size="xs">
<h2>Embeddable prop</h2>
</EuiTitle> </EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText> <EuiText>
Here the embeddable is rendered without the factory. A developer may use this method if Use embeddable constructor to pass embeddable directly to{' '}
they want to statically embed a single embeddable into their application or page. Also <strong>EmbeddableRenderer</strong>. Use <strong>input</strong> prop to declaratively
`input` prop may be used to declaratively update current embeddable input update embeddable input.
</EuiText> </EuiText>
<EuiPanel data-test-subj="helloWorldEmbeddablePanel" paddingSize="none" role="figure"> <EuiSpacer />
<EuiPanel data-test-subj="helloWorldEmbeddablePanel" role="figure">
<EmbeddableRenderer embeddable={new HelloWorldEmbeddable({ id: 'hello' })} /> <EmbeddableRenderer embeddable={new HelloWorldEmbeddable({ id: 'hello' })} />
</EuiPanel> </EuiPanel>
<EuiSpacer />
<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
{`<EmbeddableRenderer embeddable={new HelloWorldEmbeddable({ id: 'hello' })} />`}
</EuiCodeBlock>
</>
</EuiPageTemplate.Section>
<EuiPageTemplate.Section grow={false}>
<>
<EuiTitle size="xs">
<h2>Factory prop</h2>
</EuiTitle>
<EuiText> <EuiText>
Here the embeddable is rendered using the factory. Internally it creates embeddable Use <strong>factory</strong> prop to programatically instantiate embeddable.
using factory.create(). This method is used programatically when a container embeddable
attempts to initialize it&#39;s children embeddables. This method can be used when you
only have a access to a factory.
</EuiText> </EuiText>
<EuiPanel <EuiSpacer />
data-test-subj="helloWorldEmbeddableFromFactory" <EuiPanel data-test-subj="helloWorldEmbeddableFromFactory" role="figure">
paddingSize="none"
role="figure"
>
<EmbeddableRenderer factory={helloWorldEmbeddableFactory} input={{ id: '1234' }} /> <EmbeddableRenderer factory={helloWorldEmbeddableFactory} input={{ id: '1234' }} />
</EuiPanel> </EuiPanel>
</EuiPageContentBody> <EuiSpacer />
</EuiPageContent> <EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
</EuiPageBody> {`<EmbeddableRenderer factory={helloWorldEmbeddableFactory} input={{ id: '1234' }} />`}
</EuiCodeBlock>
</>
</EuiPageTemplate.Section>
</>
); );
} }

View file

@ -7,41 +7,69 @@
*/ */
import React from 'react'; import React from 'react';
import { import { EuiPanel, EuiSpacer, EuiText, EuiPageTemplate, EuiCodeBlock } from '@elastic/eui';
EuiPageBody,
EuiPageContent_Deprecated as EuiPageContent,
EuiPageContentBody_Deprecated as EuiPageContentBody,
EuiPageHeader,
EuiPageHeaderSection,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { EmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public'; import { EmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public';
import { import {
HELLO_WORLD_EMBEDDABLE, HELLO_WORLD_EMBEDDABLE,
MULTI_TASK_TODO_EMBEDDABLE,
TODO_EMBEDDABLE, TODO_EMBEDDABLE,
ListContainerFactory, ListContainerFactory,
SearchableListContainerFactory,
} from '@kbn/embeddable-examples-plugin/public'; } from '@kbn/embeddable-examples-plugin/public';
import { SearchableContainerInput } from '@kbn/embeddable-examples-plugin/public/searchable_list_container/searchable_list_container';
import { TodoInput } from '@kbn/embeddable-examples-plugin/public/todo'; import { TodoInput } from '@kbn/embeddable-examples-plugin/public/todo';
import { MultiTaskTodoInput } from '@kbn/embeddable-examples-plugin/public/multi_task_todo';
interface Props { interface Props {
listContainerEmbeddableFactory: ListContainerFactory; listContainerEmbeddableFactory: ListContainerFactory;
searchableListContainerEmbeddableFactory: SearchableListContainerFactory;
} }
export function ListContainerExample({ export function ListContainerExample({ listContainerEmbeddableFactory }: Props) {
listContainerEmbeddableFactory, return (
searchableListContainerEmbeddableFactory, <>
}: Props) { <EuiPageTemplate.Header pageTitle="Groups of embeddables" />
const listInput: SearchableContainerInput = { <EuiPageTemplate.Section grow={false}>
<>
<EuiText>Use container embeddable to render a group of embeddables.</EuiText>
<EuiSpacer />
<EuiPanel data-test-subj="listContainerEmbeddablePanel" paddingSize="none" role="figure">
<EmbeddableRenderer
factory={listContainerEmbeddableFactory}
input={{
id: 'hello',
title: 'Todo list',
viewMode: ViewMode.VIEW,
panels: {
'1': {
type: HELLO_WORLD_EMBEDDABLE,
explicitInput: {
id: '1',
},
},
'2': {
type: TODO_EMBEDDABLE,
explicitInput: {
id: '2',
task: 'Goes out on Wednesdays!',
icon: 'broom',
title: 'Take out the trash',
} as TodoInput,
},
'3': {
type: TODO_EMBEDDABLE,
explicitInput: {
id: '3',
icon: 'broom',
title: 'Vaccum the floor',
} as TodoInput,
},
},
}}
/>
</EuiPanel>
<EuiSpacer />
<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
{`<EmbeddableRenderer
factory={listContainerEmbeddableFactory}
input={{
id: 'hello', id: 'hello',
title: 'My todo list', title: 'Todo list',
viewMode: ViewMode.VIEW, viewMode: ViewMode.VIEW,
panels: { panels: {
'1': { '1': {
@ -68,110 +96,11 @@ export function ListContainerExample({
} as TodoInput, } as TodoInput,
}, },
}, },
}; }}
/>`}
const searchableInput: SearchableContainerInput = { </EuiCodeBlock>
id: '1', </>
title: 'My searchable todo list', </EuiPageTemplate.Section>
viewMode: ViewMode.VIEW, </>
panels: {
'1': {
type: HELLO_WORLD_EMBEDDABLE,
explicitInput: {
id: '1',
title: 'Hello',
},
},
'2': {
type: TODO_EMBEDDABLE,
explicitInput: {
id: '2',
task: 'Goes out on Wednesdays!',
icon: 'broom',
title: 'Take out the trash',
} as TodoInput,
},
'3': {
type: MULTI_TASK_TODO_EMBEDDABLE,
explicitInput: {
id: '3',
icon: 'searchProfilerApp',
title: 'Learn more',
tasks: ['Go to school', 'Watch planet earth', 'Read the encyclopedia'],
} as MultiTaskTodoInput,
},
},
};
return (
<EuiPageBody>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>List container example</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText>
Here is a container embeddable that contains other embeddables and displays them in a
list.
</EuiText>
<EuiPanel data-test-subj="listContainerEmbeddablePanel" paddingSize="none" role="figure">
<EmbeddableRenderer input={listInput} factory={listContainerEmbeddableFactory} />
</EuiPanel>
<EuiSpacer />
<EuiText>
<p>
The reason to use a container embeddable instead of just a custom react component is
because it comes with helpful methods to store the state of all its children
embeddables, listeners to clean up the input state when an embeddable is added or
removed, and a way to pass down the container embeddable&#39;s own input to its
children. In the above example, the container did not take any input. Let&#39;s modify
it so it does.
</p>
<p>
In this Searchable List Container, the container takes in a search string as input and
passes that down to all its children. It also listens to its children that output
`hasMatch`, and removes them from the list when there is a search string and the child
doesn&#39;t match.
</p>
<p>
The first HelloWorldEmbeddable does not emit the hasMatch output variable, so the
container chooses to hide it.
</p>
<p>
Check out the &quot;Dynamically adding children&quot; section, to see how to add
children to this container, and see it rendered inside an `EmbeddablePanel` component.
</p>
</EuiText>
<EuiSpacer />
<EuiPanel
data-test-subj="searchableListContainerEmbeddablePanel"
paddingSize="none"
role="figure"
>
<EmbeddableRenderer
input={searchableInput}
factory={searchableListContainerEmbeddableFactory}
/>{' '}
</EuiPanel>
<EuiSpacer />
<EuiText>
<p>
There currently is no formal way to limit what children can be added to a container.
If the use case arose, it wouldn&#39;t be difficult. In the mean time, it&#39;s good
to understand that children may ignore input they don&#39;t care about. Likewise the
container will have to choose what to do when it encounters children that are missing
certain output variables.
</p>
</EuiText>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
); );
} }

View file

@ -8,22 +8,17 @@
import React from 'react'; import React from 'react';
import { import {
EuiButton, EuiCodeBlock,
EuiFieldText, EuiFieldText,
EuiFlexGroup, EuiForm,
EuiFlexItem,
EuiFormRow, EuiFormRow,
EuiPageBody,
EuiPageContent_Deprecated as EuiPageContent,
EuiPageContentBody_Deprecated as EuiPageContentBody,
EuiPageHeader,
EuiPageHeaderSection,
EuiPanel, EuiPanel,
EuiText, EuiText,
EuiTextArea, EuiTextArea,
EuiTitle, EuiPageTemplate,
EuiSpacer,
EuiSelect,
} from '@elastic/eui'; } from '@elastic/eui';
import { TodoInput } from '@kbn/embeddable-examples-plugin/public/todo';
import { TodoEmbeddableFactory } from '@kbn/embeddable-examples-plugin/public'; import { TodoEmbeddableFactory } from '@kbn/embeddable-examples-plugin/public';
import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public'; import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public';
@ -32,96 +27,94 @@ interface Props {
} }
interface State { interface State {
task?: string; task: string;
title?: string; title: string;
icon?: string; icon: string;
loading: boolean;
input: TodoInput;
} }
const ICON_OPTIONS = [
{ value: 'beaker', text: 'beaker' },
{ value: 'bell', text: 'bell' },
{ value: 'bolt', text: 'bolt' },
{ value: 'broom', text: 'broom' },
{ value: 'bug', text: 'bug' },
{ value: 'bullseye', text: 'bullseye' },
];
export class TodoEmbeddableExample extends React.Component<Props, State> { export class TodoEmbeddableExample extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
loading: true, icon: 'broom',
input: { task: 'Take out the trash',
id: '1', title: 'Trash',
task: 'Take out the trash',
icon: 'broom',
title: 'Trash',
},
}; };
} }
private onUpdateEmbeddableInput = () => {
const { task, title, icon, input } = this.state;
this.setState({ input: { ...input, task: task ?? '', title, icon } });
};
public render() { public render() {
return ( return (
<EuiPageBody> <>
<EuiPageHeader> <EuiPageTemplate.Header pageTitle="Update embeddable state" />
<EuiPageHeaderSection> <EuiPageTemplate.Section grow={false}>
<EuiTitle size="l"> <>
<h1>Todo example</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText> <EuiText>
This embeddable takes input parameters, task, title and icon. You can update them Use <strong>input</strong> prop to update embeddable state.
using this form. Input changes will be passed inside `EmbeddableRenderer` as a prop
</EuiText> </EuiText>
<EuiFlexGroup> <EuiSpacer />
<EuiFlexItem grow={true}> <EuiForm>
<EuiFormRow label="Title"> <EuiFormRow label="Title">
<EuiFieldText <EuiFieldText
data-test-subj="titleTodo" data-test-subj="titleTodo"
onChange={(ev) => this.setState({ title: ev.target.value })} value={this.state.title}
/> onChange={(ev) => this.setState({ title: ev.target.value })}
</EuiFormRow> />
</EuiFlexItem> </EuiFormRow>
<EuiFlexItem grow={true}> <EuiFormRow label="Icon">
<EuiFormRow label="Icon"> <EuiSelect
<EuiFieldText data-test-subj="iconTodo"
data-test-subj="iconTodo" value={this.state.icon}
onChange={(ev) => this.setState({ icon: ev.target.value })} options={ICON_OPTIONS}
/> onChange={(ev) => this.setState({ icon: ev.target.value })}
</EuiFormRow> />
</EuiFlexItem> </EuiFormRow>
<EuiFlexItem> <EuiFormRow label="Task">
<EuiFormRow label="Task"> <EuiTextArea
<EuiTextArea fullWidth
fullWidth resize="horizontal"
resize="horizontal" data-test-subj="taskTodo"
data-test-subj="taskTodo" value={this.state.task}
onChange={(ev) => this.setState({ task: ev.target.value })} onChange={(ev) => this.setState({ task: ev.target.value })}
/> />
</EuiFormRow> </EuiFormRow>
</EuiFlexItem> </EuiForm>
<EuiFlexItem grow={false}> <EuiSpacer />
<EuiFormRow hasEmptyLabelSpace> <EuiPanel data-test-subj="todoEmbeddable" role="figure">
<EuiButton
data-test-subj="updateTodoButton"
onClick={this.onUpdateEmbeddableInput}
>
Update
</EuiButton>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel data-test-subj="todoEmbeddable" paddingSize="none" role="figure">
<EmbeddableRenderer <EmbeddableRenderer
factory={this.props.todoEmbeddableFactory} factory={this.props.todoEmbeddableFactory}
input={this.state.input} input={{
id: '1',
task: this.state.task,
title: this.state.title,
icon: this.state.icon,
}}
/> />
</EuiPanel> </EuiPanel>
</EuiPageContentBody> <EuiSpacer />
</EuiPageContent> <EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
</EuiPageBody> {`<EmbeddableRenderer
factory={this.props.todoEmbeddableFactory}
input={{
id: '1',
task: this.state.task,
title: this.state.title,
icon: this.state.icon,
}}
/>`}
</EuiCodeBlock>
</>
</EuiPageTemplate.Section>
</>
); );
} }
} }

View file

@ -22,36 +22,14 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
it('list containers render', async () => { it('list containers render', async () => {
await retry.try(async () => { await retry.try(async () => {
const title = await testSubjects.getVisibleText('listContainerTitle'); const title = await testSubjects.getVisibleText('listContainerTitle');
expect(title).to.be('My todo list'); expect(title).to.be('Todo list');
const titles = await testSubjects.getVisibleTextAll('todoEmbeddableTitle'); const titles = await testSubjects.getVisibleTextAll('todoEmbeddableTitle');
expect(titles).to.eql(['Take out the trash', 'Vaccum the floor', 'Take out the trash']); expect(titles).to.eql(['Take out the trash', 'Vaccum the floor']);
const searchableTitle = await testSubjects.getVisibleText('searchableListContainerTitle');
expect(searchableTitle).to.be('My searchable todo list');
const text = await testSubjects.getVisibleTextAll('helloWorldEmbeddable'); const text = await testSubjects.getVisibleTextAll('helloWorldEmbeddable');
expect(text).to.eql(['HELLO WORLD!', 'HELLO WORLD!']); expect(text).to.eql(['HELLO WORLD!']);
const tasks = await testSubjects.getVisibleTextAll('multiTaskTodoTask');
expect(tasks).to.eql(['Go to school', 'Watch planet earth', 'Read the encyclopedia']);
}); });
}); });
it('searchable container deletes children', async () => {
await testSubjects.click('todoCheckBox-1');
await testSubjects.click('deleteCheckedTodos');
const text = await testSubjects.getVisibleTextAll('helloWorldEmbeddable');
expect(text).to.eql(['HELLO WORLD!']);
});
it('searchable container finds matches in multi-task children', async () => {
await testSubjects.setValue('filterTodos', 'earth');
await testSubjects.click('checkMatchingTodos');
await testSubjects.click('deleteCheckedTodos');
await testSubjects.missingOrFail('multiTaskTodoTask');
});
}); });
} }

View file

@ -31,7 +31,6 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
it('todo embeddable updates', async () => { it('todo embeddable updates', async () => {
await testSubjects.setValue('taskTodo', 'read a book'); await testSubjects.setValue('taskTodo', 'read a book');
await testSubjects.setValue('titleTodo', 'Learn'); await testSubjects.setValue('titleTodo', 'Learn');
await testSubjects.click('updateTodoButton');
await retry.try(async () => { await retry.try(async () => {
const title = await testSubjects.getVisibleText('todoEmbeddableTitle'); const title = await testSubjects.getVisibleText('todoEmbeddableTitle');