Embeddable add panel examples (#57319)

* Embeddable add panel examples

* add tests

* Fix type error after merge

* address code review comments

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Stacey Gammon 2020-02-19 15:16:58 -05:00 committed by GitHub
parent 5946729097
commit 63cfffbe11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 428 additions and 79 deletions

View file

@ -23,9 +23,13 @@ import {
MultiTaskTodoEmbeddable, MultiTaskTodoEmbeddable,
MULTI_TASK_TODO_EMBEDDABLE, MULTI_TASK_TODO_EMBEDDABLE,
MultiTaskTodoInput, MultiTaskTodoInput,
MultiTaskTodoOutput,
} from './multi_task_todo_embeddable'; } from './multi_task_todo_embeddable';
export class MultiTaskTodoEmbeddableFactory extends EmbeddableFactory { export class MultiTaskTodoEmbeddableFactory extends EmbeddableFactory<
MultiTaskTodoInput,
MultiTaskTodoOutput
> {
public readonly type = MULTI_TASK_TODO_EMBEDDABLE; public readonly type = MULTI_TASK_TODO_EMBEDDABLE;
public isEditable() { public isEditable() {
@ -36,6 +40,15 @@ export class MultiTaskTodoEmbeddableFactory extends EmbeddableFactory {
return new MultiTaskTodoEmbeddable(initialInput, parent); return new MultiTaskTodoEmbeddable(initialInput, parent);
} }
/**
* Check out todo_embeddable_factory for a better example that asks for data from
* the user. This just returns default data. That's okay too though, if you want to
* start with default data and expose an "edit" action to modify it.
*/
public async getExplicitInput() {
return { title: 'default title', tasks: ['Im default data'] };
}
public getDisplayName() { public getDisplayName() {
return i18n.translate('embeddableExamples.multiTaskTodo.displayName', { return i18n.translate('embeddableExamples.multiTaskTodo.displayName', {
defaultMessage: 'Multi-task todo item', defaultMessage: 'Multi-task todo item',

View file

@ -17,11 +17,20 @@
* under the License. * under the License.
*/ */
import { IEmbeddableSetup, IEmbeddableStart } from '../../../src/plugins/embeddable/public'; import {
IEmbeddableSetup,
IEmbeddableStart,
EmbeddableFactory,
} from '../../../src/plugins/embeddable/public';
import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public'; import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public';
import { HelloWorldEmbeddableFactory, HELLO_WORLD_EMBEDDABLE } from './hello_world'; import { HelloWorldEmbeddableFactory, HELLO_WORLD_EMBEDDABLE } from './hello_world';
import { TODO_EMBEDDABLE, TodoEmbeddableFactory } from './todo'; import { TODO_EMBEDDABLE, TodoEmbeddableFactory, TodoInput, TodoOutput } from './todo';
import { MULTI_TASK_TODO_EMBEDDABLE, MultiTaskTodoEmbeddableFactory } from './multi_task_todo'; import {
MULTI_TASK_TODO_EMBEDDABLE,
MultiTaskTodoEmbeddableFactory,
MultiTaskTodoOutput,
MultiTaskTodoInput,
} from './multi_task_todo';
import { import {
SEARCHABLE_LIST_CONTAINER, SEARCHABLE_LIST_CONTAINER,
SearchableListContainerFactory, SearchableListContainerFactory,
@ -45,12 +54,9 @@ export class EmbeddableExamplesPlugin
new HelloWorldEmbeddableFactory() new HelloWorldEmbeddableFactory()
); );
deps.embeddable.registerEmbeddableFactory(TODO_EMBEDDABLE, new TodoEmbeddableFactory()); deps.embeddable.registerEmbeddableFactory<
EmbeddableFactory<MultiTaskTodoInput, MultiTaskTodoOutput>
deps.embeddable.registerEmbeddableFactory( >(MULTI_TASK_TODO_EMBEDDABLE, new MultiTaskTodoEmbeddableFactory());
MULTI_TASK_TODO_EMBEDDABLE,
new MultiTaskTodoEmbeddableFactory()
);
} }
public start(core: CoreStart, deps: EmbeddableExamplesStartDependencies) { public start(core: CoreStart, deps: EmbeddableExamplesStartDependencies) {
@ -66,6 +72,11 @@ export class EmbeddableExamplesPlugin
LIST_CONTAINER, LIST_CONTAINER,
new ListContainerFactory(deps.embeddable.getEmbeddableFactory) new ListContainerFactory(deps.embeddable.getEmbeddableFactory)
); );
deps.embeddable.registerEmbeddableFactory<EmbeddableFactory<TodoInput, TodoOutput>>(
TODO_EMBEDDABLE,
new TodoEmbeddableFactory(core.overlays.openModal)
);
} }
public stop() {} public stop() {}

View file

@ -1,40 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { IContainer, EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { TodoEmbeddable, TODO_EMBEDDABLE, TodoInput } from './todo_embeddable';
export class TodoEmbeddableFactory extends EmbeddableFactory {
public readonly type = TODO_EMBEDDABLE;
public isEditable() {
return true;
}
public async create(initialInput: TodoInput, parent?: IContainer) {
return new TodoEmbeddable(initialInput, parent);
}
public getDisplayName() {
return i18n.translate('embeddableExamples.todo.displayName', {
defaultMessage: 'Todo item',
});
}
}

View file

@ -0,0 +1,92 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState } from 'react';
import { EuiModalBody } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { OverlayStart } from 'kibana/public';
import { EuiFieldText } from '@elastic/eui';
import { EuiButton } from '@elastic/eui';
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
import { IContainer, EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { TodoEmbeddable, TODO_EMBEDDABLE, TodoInput, TodoOutput } from './todo_embeddable';
function TaskInput({ onSave }: { onSave: (task: string) => void }) {
const [task, setTask] = useState('');
return (
<EuiModalBody>
<EuiFieldText
data-test-subj="taskInputField"
value={task}
placeholder="Enter task here"
onChange={e => setTask(e.target.value)}
/>
<EuiButton data-test-subj="createTodoEmbeddable" onClick={() => onSave(task)}>
Save
</EuiButton>
</EuiModalBody>
);
}
export class TodoEmbeddableFactory extends EmbeddableFactory<
TodoInput,
TodoOutput,
TodoEmbeddable
> {
public readonly type = TODO_EMBEDDABLE;
constructor(private openModal: OverlayStart['openModal']) {
super();
}
public isEditable() {
return true;
}
public async create(initialInput: TodoInput, parent?: IContainer) {
return new TodoEmbeddable(initialInput, parent);
}
/**
* This function is used when dynamically creating a new embeddable to add to a
* container. Some input may be inherited from the container, but not all. This can be
* used to collect specific embeddable input that the container will not provide, like
* in this case, the task string.
*/
public async getExplicitInput() {
return new Promise<{ task: string }>(resolve => {
const onSave = (task: string) => resolve({ task });
const overlay = this.openModal(
toMountPoint(
<TaskInput
onSave={(task: string) => {
onSave(task);
overlay.close();
}}
/>
)
);
});
}
public getDisplayName() {
return i18n.translate('embeddableExamples.todo.displayName', {
defaultMessage: 'Todo item',
});
}
}

View file

@ -5,6 +5,6 @@
"configPath": ["embeddable_explorer"], "configPath": ["embeddable_explorer"],
"server": false, "server": false,
"ui": true, "ui": true,
"requiredPlugins": ["embeddable", "embeddableExamples"], "requiredPlugins": ["uiActions", "inspector", "embeddable", "embeddableExamples"],
"optionalPlugins": [] "optionalPlugins": []
} }

View file

@ -23,11 +23,21 @@ import { BrowserRouter as Router, Route, withRouter, RouteComponentProps } from
import { EuiPage, EuiPageSideBar, EuiSideNav } from '@elastic/eui'; import { EuiPage, EuiPageSideBar, EuiSideNav } from '@elastic/eui';
import { IEmbeddableStart } from 'src/plugins/embeddable/public'; import { IEmbeddableStart } from '../../../src/plugins/embeddable/public';
import { AppMountContext, AppMountParameters, CoreStart } from '../../../src/core/public'; import { UiActionsStart } from '../../../src/plugins/ui_actions/public';
import { Start as InspectorStartContract } from '../../../src/plugins/inspector/public';
import {
AppMountContext,
AppMountParameters,
CoreStart,
SavedObjectsStart,
IUiSettingsClient,
OverlayStart,
} from '../../../src/core/public';
import { HelloWorldEmbeddableExample } from './hello_world_embeddable_example'; import { HelloWorldEmbeddableExample } from './hello_world_embeddable_example';
import { TodoEmbeddableExample } from './todo_embeddable_example'; import { TodoEmbeddableExample } from './todo_embeddable_example';
import { ListContainerExample } from './list_container_example'; import { ListContainerExample } from './list_container_example';
import { EmbeddablePanelExample } from './embeddable_panel_example';
interface PageDef { interface PageDef {
title: string; title: string;
@ -61,15 +71,29 @@ const Nav = withRouter(({ history, navigateToApp, pages }: NavProps) => {
); );
}); });
interface Props {
basename: string;
navigateToApp: CoreStart['application']['navigateToApp'];
embeddableApi: IEmbeddableStart;
uiActionsApi: UiActionsStart;
overlays: OverlayStart;
notifications: CoreStart['notifications'];
inspector: InspectorStartContract;
savedObject: SavedObjectsStart;
uiSettingsClient: IUiSettingsClient;
}
const EmbeddableExplorerApp = ({ const EmbeddableExplorerApp = ({
basename, basename,
navigateToApp, navigateToApp,
embeddableApi, embeddableApi,
}: { inspector,
basename: string; uiSettingsClient,
navigateToApp: CoreStart['application']['navigateToApp']; savedObject,
embeddableApi: IEmbeddableStart; overlays,
}) => { uiActionsApi,
notifications,
}: Props) => {
const pages: PageDef[] = [ const pages: PageDef[] = [
{ {
title: 'Hello world embeddable', title: 'Hello world embeddable',
@ -90,6 +114,22 @@ const EmbeddableExplorerApp = ({
id: 'listContainerSection', id: 'listContainerSection',
component: <ListContainerExample getEmbeddableFactory={embeddableApi.getEmbeddableFactory} />, component: <ListContainerExample getEmbeddableFactory={embeddableApi.getEmbeddableFactory} />,
}, },
{
title: 'Dynamically adding children to a container',
id: 'embeddablePanelExamplae',
component: (
<EmbeddablePanelExample
uiActionsApi={uiActionsApi}
getAllEmbeddableFactories={embeddableApi.getEmbeddableFactories}
getEmbeddableFactory={embeddableApi.getEmbeddableFactory}
overlays={overlays}
uiSettingsClient={uiSettingsClient}
savedObject={savedObject}
notifications={notifications}
inspector={inspector}
/>
),
},
]; ];
const routes = pages.map((page, i) => ( const routes = pages.map((page, i) => (
@ -108,19 +148,8 @@ const EmbeddableExplorerApp = ({
); );
}; };
export const renderApp = ( export const renderApp = (props: Props, element: AppMountParameters['element']) => {
core: CoreStart, ReactDOM.render(<EmbeddableExplorerApp {...props} />, element);
embeddableApi: IEmbeddableStart,
{ appBasePath, element }: AppMountParameters
) => {
ReactDOM.render(
<EmbeddableExplorerApp
basename={appBasePath}
navigateToApp={core.application.navigateToApp}
embeddableApi={embeddableApi}
/>,
element
);
return () => ReactDOM.unmountComponentAtNode(element); return () => ReactDOM.unmountComponentAtNode(element);
}; };

View file

@ -0,0 +1,164 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState, useEffect, useRef } from 'react';
import {
EuiPanel,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
EuiPageHeader,
EuiPageHeaderSection,
EuiTitle,
EuiText,
} from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import { OverlayStart, CoreStart, SavedObjectsStart, IUiSettingsClient } from 'kibana/public';
import {
GetEmbeddableFactory,
EmbeddablePanel,
IEmbeddableStart,
IEmbeddable,
} from '../../../src/plugins/embeddable/public';
import {
HELLO_WORLD_EMBEDDABLE,
TODO_EMBEDDABLE,
MULTI_TASK_TODO_EMBEDDABLE,
SEARCHABLE_LIST_CONTAINER,
} from '../../embeddable_examples/public';
import { UiActionsStart } from '../../../src/plugins/ui_actions/public';
import { Start as InspectorStartContract } from '../../../src/plugins/inspector/public';
import { getSavedObjectFinder } from '../../../src/plugins/saved_objects/public';
interface Props {
getAllEmbeddableFactories: IEmbeddableStart['getEmbeddableFactories'];
getEmbeddableFactory: GetEmbeddableFactory;
uiActionsApi: UiActionsStart;
overlays: OverlayStart;
notifications: CoreStart['notifications'];
inspector: InspectorStartContract;
savedObject: SavedObjectsStart;
uiSettingsClient: IUiSettingsClient;
}
export function EmbeddablePanelExample({
inspector,
notifications,
overlays,
getAllEmbeddableFactories,
getEmbeddableFactory,
uiActionsApi,
savedObject,
uiSettingsClient,
}: Props) {
const searchableInput = {
id: '1',
title: 'My searchable todo list',
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',
},
},
'3': {
type: MULTI_TASK_TODO_EMBEDDABLE,
explicitInput: {
id: '3',
icon: 'searchProfilerApp',
title: 'Learn more',
tasks: ['Go to school', 'Watch planet earth', 'Read the encyclopedia'],
},
},
},
};
const [embeddable, setEmbeddable] = useState<IEmbeddable | undefined>(undefined);
const ref = useRef(false);
useEffect(() => {
ref.current = true;
if (!embeddable) {
const factory = getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER);
const promise = factory?.create(searchableInput);
if (promise) {
promise.then(e => {
if (ref.current) {
setEmbeddable(e);
}
});
}
}
return () => {
ref.current = false;
};
});
return (
<EuiPageBody>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>The embeddable panel component</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText>
You can render your embeddable inside the EmbeddablePanel component. This adds some
extra rendering and offers a context menu with pluggable actions. Using EmbeddablePanel
to render your embeddable means you get access to the &quote;Add panel flyout&quote;.
Now you can see how to add embeddables to your container, and how
&quote;getExplicitInput&quote; is used to grab input not provided by the container.
</EuiText>
<EuiPanel data-test-subj="embeddedPanelExample" paddingSize="none" role="figure">
{embeddable ? (
<EmbeddablePanel
embeddable={embeddable}
getActions={uiActionsApi.getTriggerCompatibleActions}
getEmbeddableFactory={getEmbeddableFactory}
getAllEmbeddableFactories={getAllEmbeddableFactories}
overlays={overlays}
notifications={notifications}
inspector={inspector}
SavedObjectFinder={getSavedObjectFinder(savedObject, uiSettingsClient)}
/>
) : (
<EuiText>Loading...</EuiText>
)}
</EuiPanel>
<EuiSpacer />
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
);
}

View file

@ -60,7 +60,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {
type: TODO_EMBEDDABLE, type: TODO_EMBEDDABLE,
explicitInput: { explicitInput: {
id: '2', id: '2',
task: 'Goes out on Wenesdays!', task: 'Goes out on Wednesdays!',
icon: 'broom', icon: 'broom',
title: 'Take out the trash', title: 'Take out the trash',
}, },
@ -91,7 +91,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {
type: TODO_EMBEDDABLE, type: TODO_EMBEDDABLE,
explicitInput: { explicitInput: {
id: '2', id: '2',
task: 'Goes out on Wenesdays!', task: 'Goes out on Wednesdays!',
icon: 'broom', icon: 'broom',
title: 'Take out the trash', title: 'Take out the trash',
}, },
@ -102,7 +102,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {
id: '3', id: '3',
icon: 'searchProfilerApp', icon: 'searchProfilerApp',
title: 'Learn more', title: 'Learn more',
tasks: ['Go to school', 'Watch planet earth', 'Read the encylopedia'], tasks: ['Go to school', 'Watch planet earth', 'Read the encyclopedia'],
}, },
}, },
}, },
@ -151,6 +151,11 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {
The first HelloWorldEmbeddable does not emit the hasMatch output variable, so the The first HelloWorldEmbeddable does not emit the hasMatch output variable, so the
container chooses to hide it. container chooses to hide it.
</p> </p>
<p>
Check out the &quote;Dynamically adding children&quote; section, to see how to add
children to this container, and see it rendered inside an `EmbeddablePanel` component.
</p>
</EuiText> </EuiText>
<EuiSpacer /> <EuiSpacer />

View file

@ -18,17 +18,38 @@
*/ */
import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public';
import { UiActionsService } from '../../../src/plugins/ui_actions/public';
import { IEmbeddableStart } from '../../../src/plugins/embeddable/public'; import { IEmbeddableStart } from '../../../src/plugins/embeddable/public';
import { Start as InspectorStart } from '../../../src/plugins/inspector/public';
export class EmbeddableExplorerPlugin implements Plugin { interface StartDeps {
public setup(core: CoreSetup<{ embeddable: IEmbeddableStart }>) { uiActions: UiActionsService;
embeddable: IEmbeddableStart;
inspector: InspectorStart;
}
export class EmbeddableExplorerPlugin implements Plugin<void, void, {}, StartDeps> {
public setup(core: CoreSetup<StartDeps>) {
core.application.register({ core.application.register({
id: 'embeddableExplorer', id: 'embeddableExplorer',
title: 'Embeddable explorer', title: 'Embeddable explorer',
async mount(params: AppMountParameters) { async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices(); const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app'); const { renderApp } = await import('./app');
return renderApp(coreStart, depsStart.embeddable, params); return renderApp(
{
notifications: coreStart.notifications,
inspector: depsStart.inspector,
embeddableApi: depsStart.embeddable,
uiActionsApi: depsStart.uiActions,
basename: params.appBasePath,
uiSettingsClient: coreStart.uiSettings,
savedObject: coreStart.savedObjects,
overlays: coreStart.overlays,
navigateToApp: coreStart.application.navigateToApp,
},
params.element
);
}, },
}); });
} }

View file

@ -24,7 +24,10 @@ export interface EmbeddableApi {
getEmbeddableFactory: (embeddableFactoryId: string) => EmbeddableFactory; getEmbeddableFactory: (embeddableFactoryId: string) => EmbeddableFactory;
getEmbeddableFactories: GetEmbeddableFactories; getEmbeddableFactories: GetEmbeddableFactories;
// TODO: Make `registerEmbeddableFactory` receive only `factory` argument. // TODO: Make `registerEmbeddableFactory` receive only `factory` argument.
registerEmbeddableFactory: (id: string, factory: EmbeddableFactory) => void; registerEmbeddableFactory: <TEmbeddableFactory extends EmbeddableFactory>(
id: string,
factory: TEmbeddableFactory
) => void;
} }
export interface EmbeddableDependencies { export interface EmbeddableDependencies {

View file

@ -33,6 +33,13 @@ export default async function({ readConfigFile }) {
...functionalConfig.get('services'), ...functionalConfig.get('services'),
...services, ...services,
}, },
uiSettings: {
defaults: {
'accessibility:disableAnimations': true,
'dateFormat:tz': 'UTC',
'telemetry:optIn': false,
},
},
pageObjects: functionalConfig.get('pageObjects'), pageObjects: functionalConfig.get('pageObjects'),
servers: functionalConfig.get('servers'), servers: functionalConfig.get('servers'),
esTestCluster: functionalConfig.get('esTestCluster'), esTestCluster: functionalConfig.get('esTestCluster'),

View file

@ -0,0 +1,43 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from 'test/plugin_functional/services';
// eslint-disable-next-line import/no-default-export
export default function({ getService }: PluginFunctionalProviderContext) {
const testSubjects = getService('testSubjects');
describe('creating and adding children', () => {
before(async () => {
await testSubjects.click('embeddablePanelExamplae');
});
it('Can create a new child', async () => {
await testSubjects.click('embeddablePanelToggleMenuIcon');
await testSubjects.click('embeddablePanelAction-ADD_PANEL_ACTION_ID');
await testSubjects.click('createNew');
await testSubjects.click('createNew-TODO_EMBEDDABLE');
await testSubjects.setValue('taskInputField', 'new task');
await testSubjects.click('createTodoEmbeddable');
const tasks = await testSubjects.getVisibleTextAll('todoEmbeddableTask');
expect(tasks).to.eql(['Goes out on Wednesdays!', 'new task']);
});
});
}

View file

@ -39,5 +39,6 @@ export default function({
loadTestFile(require.resolve('./hello_world_embeddable')); loadTestFile(require.resolve('./hello_world_embeddable'));
loadTestFile(require.resolve('./todo_embeddable')); loadTestFile(require.resolve('./todo_embeddable'));
loadTestFile(require.resolve('./list_container')); loadTestFile(require.resolve('./list_container'));
loadTestFile(require.resolve('./adding_children'));
}); });
} }

View file

@ -45,7 +45,7 @@ export default function({ getService }: PluginFunctionalProviderContext) {
expect(text).to.eql(['HELLO WORLD!', 'HELLO WORLD!']); expect(text).to.eql(['HELLO WORLD!', 'HELLO WORLD!']);
const tasks = await testSubjects.getVisibleTextAll('multiTaskTodoTask'); const tasks = await testSubjects.getVisibleTextAll('multiTaskTodoTask');
expect(tasks).to.eql(['Go to school', 'Watch planet earth', 'Read the encylopedia']); expect(tasks).to.eql(['Go to school', 'Watch planet earth', 'Read the encyclopedia']);
}); });
}); });