diff --git a/src/dev/jest/mocks/css_modules_mock.js b/src/dev/jest/mocks/css_modules_mock.js new file mode 100644 index 0000000000000..b1aaa58307478 --- /dev/null +++ b/src/dev/jest/mocks/css_modules_mock.js @@ -0,0 +1,30 @@ +/* + * 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. + */ + +module.exports = new Proxy( + {}, + { + get: function getter(target, key) { + if (key === '__esModule') { + return false; + } + return key; + }, + } +); diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index e9ed43e81780b..8a4072e6aec46 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -24,20 +24,20 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { '^plugins/([^/.]*)(.*)': `${kibanaDirectory}/src/legacy/core_plugins/$1/public$2`, '^legacy/plugins/xpack_main/(.*);': `${xPackKibanaDirectory}/legacy/plugins/xpack_main/public/$1`, '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath, + '\\.module.(css|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/css_modules_mock.js`, '\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`, '^test_utils/enzyme_helpers': `${xPackKibanaDirectory}/test_utils/enzyme_helpers.tsx`, '^test_utils/find_test_subject': `${xPackKibanaDirectory}/test_utils/find_test_subject.ts`, }, coverageDirectory: '/../target/kibana-coverage/jest', - coverageReporters: [ - 'html', - ], + coverageReporters: ['html'], setupFiles: [ `${kibanaDirectory}/src/dev/jest/setup/babel_polyfill.js`, `/dev-tools/jest/setup/polyfills.js`, `/dev-tools/jest/setup/enzyme.js`, ], setupFilesAfterEnv: [`${kibanaDirectory}/src/dev/jest/setup/mocks.js`], + testEnvironment: 'jest-environment-jsdom-fourteen', testMatch: ['**/*.test.{js,ts,tsx}'], transform: { '^.+\\.(js|tsx?)$': `${kibanaDirectory}/src/dev/jest/babel_transform.js`, diff --git a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js index c02309f3ae757..f0271e30f9bb0 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js +++ b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import path from 'path'; import moment from 'moment'; import 'moment-timezone'; @@ -61,6 +62,11 @@ jest.mock( } ); +// This element uses a `ref` and cannot be rendered by Jest snapshots. +import { RenderedElement } from '../external_runtime/components/rendered_element'; +jest.mock('../external_runtime/components/rendered_element'); +RenderedElement.mockImplementation(() => 'RenderedElement'); + addSerializer(styleSheetSerializer); // Initialize Storyshots and build the Jest Snapshots diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js index 6d28d9d97b23f..f7c41757ee89f 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js @@ -57,6 +57,46 @@ module.exports = async ({ config }) => { ], }); + config.module.rules.push({ + test: /\.scss$/, + exclude: /\.module.(s(a|c)ss)$/, + use: [ + { loader: 'style-loader' }, + { loader: 'css-loader', options: { importLoaders: 2 } }, + { + loader: 'postcss-loader', + options: { + path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), + }, + }, + { loader: 'sass-loader' }, + ], + }); + + config.module.rules.push({ + test: /\.module\.s(a|c)ss$/, + loader: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 2, + modules: true, + localIdentName: '[name]__[local]___[hash:base64:5]', + }, + }, + { + loader: 'postcss-loader', + options: { + path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), + }, + }, + { + loader: 'sass-loader', + }, + ], + }); + // Reference the built DLL file of static(ish) dependencies, which are removed // during kbn:bootstrap and rebuilt if missing. config.plugins.push( @@ -109,8 +149,8 @@ module.exports = async ({ config }) => { }) ); - // Tell Webpack about the ts/x extensions - config.resolve.extensions.push('.ts', '.tsx'); + // Tell Webpack about the extensions + config.resolve.extensions.push('.ts', '.tsx', '.scss'); // Alias imports to either a mock or the proper module or directory. // NOTE: order is important here - `ui/notify` will override `ui/notify/foo` if it diff --git a/x-pack/legacy/plugins/canvas/external_runtime/__mocks__/supported_renderers.js b/x-pack/legacy/plugins/canvas/external_runtime/__mocks__/supported_renderers.js new file mode 100644 index 0000000000000..937299ee85f87 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/__mocks__/supported_renderers.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import ReactDOM from 'react-dom'; +import React from 'react'; + +const renderers = [ + 'debug', + 'error', + 'image', + 'repeatImage', + 'revealImage', + 'markdown', + 'metric', + 'pie', + 'plot', + 'progress', + 'shape', + 'table', + 'text', +]; + +export const renderFunctions = renderers.map(fn => () => ({ + name: fn, + displayName: fn, + help: fn, + reuseDomNode: true, + render: domNode => { + ReactDOM.render(
{fn} mock
, domNode); + }, +})); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx b/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx index 23fc23195a474..a6a8ebc96ba6a 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx @@ -74,7 +74,7 @@ const updateArea = async (area: Element) => { height = workpad.height * (width / workpad.width); } - const options = { + const stage = { height: height || workpad.height, width: width || workpad.width, page: page ? page : workpad.page, @@ -83,7 +83,7 @@ const updateArea = async (area: Element) => { area.classList.add('kbnCanvas'); area.removeAttribute(EMBED); - render(, area); + render(, area); } } }; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/runtime.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/runtime.examples.storyshot new file mode 100644 index 0000000000000..0765538eb679f --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/runtime.examples.storyshot @@ -0,0 +1,322 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime Canvas 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ My Canvas Workpad +
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Storyshots runtime Page 1`] = ` +
+
+
+`; + +exports[`Storyshots runtime RenderedElement 1`] = ` +
+`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/runtime.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/runtime.examples.tsx new file mode 100644 index 0000000000000..a66e85c9afa70 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/runtime.examples.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { Context } from '../../context/mock'; + +import hello from '../../test/hello.json'; +import { Canvas } from '../canvas.container'; +import { Page } from '../page.container'; +import { RenderedElement } from '../rendered_element.container'; + +storiesOf('runtime', module) + .add('Canvas', () => ( + + + + )) + .add('Page', () => ( + + + + )) + .add('RenderedElement', () => ( + + + + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__tests__/__snapshots__/app.test.tsx.snap b/x-pack/legacy/plugins/canvas/external_runtime/components/__tests__/__snapshots__/app.test.tsx.snap new file mode 100644 index 0000000000000..3201c6f3ef284 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__tests__/__snapshots__/app.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` App renders properly 1`] = ` +"
markdown mock
markdown mock
My Canvas Workpad
" +`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__tests__/app.test.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/__tests__/app.test.tsx new file mode 100644 index 0000000000000..cb45d8704a71f --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__tests__/app.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { App } from '../app'; +import { snapshots, SnapshotNames } from '../../test'; +import { previousPage, currentPage, nextPage } from '../footer/__tests__/page_controls.test'; + +jest.mock('../../supported_renderers'); + +const getWrapper: (name?: SnapshotNames) => ReactWrapper = (name = 'hello') => { + const workpad = snapshots[name]; + const { height, width } = workpad; + const stage = { + height, + width, + page: 0, + }; + + return mount(); +}; + +describe('', () => { + test('App renders properly', () => { + expect(getWrapper().html()).toMatchSnapshot(); + }); + + test('App can be navigated', () => { + const wrapper = getWrapper('austin'); + nextPage(wrapper).simulate('click'); + expect(currentPage(wrapper).text()).toEqual('Page 2 of 28'); + previousPage(wrapper).simulate('click'); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx index 936349155ff90..fcf0505bd3044 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx @@ -5,22 +5,14 @@ */ import React from 'react'; -// @ts-ignore Untyped package -import { RenderFunctionsRegistry } from 'data/interpreter'; -import { Canvas } from './canvas'; -import { - initialExternalEmbedState, - ExternalEmbedStateProvider, - ExternalEmbedState, -} from '../context'; -// @ts-ignore Untyped local -import { renderFunctions } from '../../canvas_plugin_src/renderers'; -import { CanvasRenderedWorkpad } from '../types'; +import { CanvasRenderedWorkpad, ExternalEmbedState, Stage } from '../types'; +import { RendererSpec } from '../../types'; +import { initialExternalEmbedState, ExternalEmbedStateProvider } from '../context'; +import { Canvas } from './canvas.container'; +import { renderFunctions } from '../supported_renderers'; interface Props { - height: number; - width: number; - page: number; + stage: Stage; workpad: CanvasRenderedWorkpad; } @@ -28,23 +20,19 @@ interface Props { * The overall Embedded Workpad app; the highest-layer component. */ export const App = (props: Props) => { - const { workpad, page, height, width } = props; + const { workpad, stage } = props; - // Register all of the rendering experessions with a bespoke registry. - const renderersRegistry = new RenderFunctionsRegistry(); + const renderers: { [key: string]: RendererSpec } = {}; - renderFunctions.forEach((fn: Function | undefined) => { - if (fn) { - renderersRegistry.register(fn); - } + renderFunctions.forEach(fn => { + const func = fn(); + renderers[func.name] = func; }); const initialState: ExternalEmbedState = { ...initialExternalEmbedState, - height, - page, - renderersRegistry, - width, + stage, + renderers, workpad, }; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.container.tsx new file mode 100644 index 0000000000000..b15b91cb09b36 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.container.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState, setPageAction, setScrubberVisibleAction } from '../context'; +import { Canvas as CanvasComponent, onSetPageProp, onSetScrubberVisibleProp } from './canvas'; + +export const Canvas = () => { + const [{ workpad, stage, settings, refs }, dispatch] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const onSetPage: onSetPageProp = (page: number) => { + dispatch(setPageAction(page)); + }; + + const onSetScrubberVisible: onSetScrubberVisibleProp = (visible: boolean) => { + dispatch(setScrubberVisibleAction(visible)); + }; + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx index 7a999157f2fd9..d7d3b4e6bab34 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx @@ -5,42 +5,52 @@ */ import React, { useState } from 'react'; -import { useExternalEmbedState, setPage, setScrubberVisible } from '../context'; -import { Page } from './page'; +import { Page } from './page.container'; import { Footer, FOOTER_HEIGHT } from './footer'; import { getTimeInterval } from '../../public/lib/time_interval'; -// @ts-ignore CSS Module -import css from './canvas.module'; +import css from './canvas.module.scss'; +import { CanvasRenderedWorkpad, Stage, Settings, Refs } from '../types'; let timeout: number = 0; +export type onSetPageProp = (page: number) => void; +export type onSetScrubberVisibleProp = (visible: boolean) => void; + +interface Props { + onSetPage: onSetPageProp; + onSetScrubberVisible: onSetScrubberVisibleProp; + refs: Pick; + settings: Settings; + stage: Stage; + workpad: Pick; +} + /** * The "stage" for a workpad, which composes the toolbar and other components. */ -export const Canvas = () => { - const [ - { workpad, height: containerHeight, width: containerWidth, page, settings, refs }, - dispatch, - ] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - +export const Canvas = ({ + onSetPage = () => {}, + onSetScrubberVisible = () => {}, + refs, + settings, + stage, + workpad, +}: Props) => { const { toolbar, autoplay } = settings; - const { height, width, pages } = workpad; - const ratio = Math.max(width / containerWidth, height / containerHeight); - const transform = `scale3d(${containerHeight / (containerHeight * ratio)}, ${containerWidth / - (containerWidth * ratio)}, 1)`; + const { height: stageHeight, width: stageWidth, page } = stage; + const { height: workpadHeight, width: workpadWidth } = workpad; + const ratio = Math.max(workpadWidth / stageWidth, workpadHeight / stageHeight); + const transform = `scale3d(${stageHeight / (stageHeight * ratio)}, ${stageWidth / + (stageWidth * ratio)}, 1)`; const pageStyle = { - height, + height: workpadHeight, transform, - width, + width: workpadWidth, }; - if (autoplay.enabled && autoplay.interval) { + if (autoplay.isEnabled && autoplay.interval) { // We need to clear the timeout every time, even if it doesn't need to be or // it's null. Since one could select a different page from the scrubber at // any point, or change the interval, we need to make sure the interval is @@ -49,19 +59,19 @@ export const Canvas = () => { clearTimeout(timeout); timeout = setTimeout( - () => dispatch(setPage(page >= workpad.pages.length - 1 ? 0 : page + 1)), + () => onSetPage(page >= workpad.pages.length - 1 ? 0 : page + 1), getTimeInterval(autoplay.interval) ); } - const [toolbarHidden, setToolbarHidden] = useState(toolbar.autohide); - const rootHeight = containerHeight + (toolbar.autohide ? 0 : FOOTER_HEIGHT); + const [toolbarHidden, setToolbarHidden] = useState(toolbar.isAutohide); + const rootHeight = stageHeight + (toolbar.isAutohide ? 0 : FOOTER_HEIGHT); const hideToolbar = (hidden: boolean) => { - if (settings.toolbar.autohide) { + if (toolbar.isAutohide) { if (hidden) { // Hide the scrubber if we hide the toolbar. - dispatch(setScrubberVisible(false)); + onSetScrubberVisible(false); } setToolbarHidden(hidden); } @@ -70,17 +80,17 @@ export const Canvas = () => { return (
hideToolbar(false)} onMouseLeave={() => hideToolbar(true)} ref={refs.stage} > -
+
- +
-
+
); }; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/__snapshots__/footer.components.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/__snapshots__/footer.components.examples.storyshot new file mode 100644 index 0000000000000..f32259cc4bdb3 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/__snapshots__/footer.components.examples.storyshot @@ -0,0 +1,137 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime/Footer/components PageControls 1`] = ` +
+
+ +
+
+ +
+
+ +
+
+`; + +exports[`Storyshots runtime/Footer/components Title 1`] = ` +
+
+
+ +
+
+
+
+
+ This is a test title. +
+
+
+
+
+
+`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot new file mode 100644 index 0000000000000..14e32b6bb1e3c --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot @@ -0,0 +1,496 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime/Footer Footer 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ My Canvas Workpad +
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+`; + +exports[`Storyshots runtime/Footer PageControls 1`] = ` +
+
+
+ +
+
+ +
+
+ +
+
+
+`; + +exports[`Storyshots runtime/Footer PagePreview 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`Storyshots runtime/Footer Scrubber 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Storyshots runtime/Footer Title 1`] = ` +
+
+
+ +
+
+
+
+
+ My Canvas Workpad +
+
+
+
+
+
+`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/footer.components.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/footer.components.examples.tsx new file mode 100644 index 0000000000000..32895a35caa51 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/footer.components.examples.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { Title } from '../title'; +import { PageControls } from '../page_controls'; + +storiesOf('runtime/Footer/components', module) + .add('Title', () => ( +
+ + </div> + )) + .add('PageControls', () => ( + <PageControls + page={0} + totalPages={1} + onSetPageNumber={action('onSetPageNumber')} + onToggleScrubber={action('onToggleScrubber')} + /> + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/footer.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/footer.examples.tsx new file mode 100644 index 0000000000000..76d93adf70ccf --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__examples__/footer.examples.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { Context } from '../../../context/mock'; + +import { Footer } from '../footer.container'; +import { Title } from '../title.container'; +import { PageControls } from '../page_controls.container'; +import { PagePreview } from '../page_preview.container'; +import { Scrubber } from '../scrubber.container'; + +storiesOf('runtime/Footer', module) + .add('Footer', () => ( + <Context height={172}> + <Footer /> + </Context> + )) + .add('Title', () => ( + <Context style={{ background: '#333', padding: 10 }}> + <Title /> + </Context> + )) + .add('Scrubber', () => ( + <Context height={172} isScrubberVisible={true}> + <Scrubber /> + </Context> + )) + .add('PageControls', () => ( + <Context style={{ background: '#333', padding: 10 }}> + <PageControls /> + </Context> + )) + .add('PagePreview', () => ( + <Context> + <PagePreview height={172} index={0} /> + </Context> + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/footer.test.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/footer.test.tsx new file mode 100644 index 0000000000000..4eba1c24fcdd6 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/footer.test.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; +import { Context } from '../../../context/mock'; +import { Footer } from '../footer.container'; + +describe('<Footer />', () => { + test('null workpad renders nothing', () => { + expect(mount(<Footer />).isEmptyRender()); + }); + + const wrapper = mount( + <Context> + <Footer /> + </Context> + ); + + const scrubber = () => wrapper.find('Scrubber').slice(1, 2); + const center = () => wrapper.find('EuiButtonEmpty[data-test-subj="pageControlsCurrentPage"]'); + + test('scrubber functions properly', () => { + expect(scrubber().prop('isScrubberVisible')).toBeFalsy(); + center().simulate('click'); + expect(scrubber().prop('isScrubberVisible')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/page_controls.test.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/page_controls.test.tsx new file mode 100644 index 0000000000000..f42c25c0f662b --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/page_controls.test.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { Context } from '../../../context/mock'; +import { PageControls } from '../page_controls.container'; + +export const previousPage = (wrapper: ReactWrapper) => + wrapper.find('EuiButtonIcon[data-test-subj="pageControlsPrevPage"]'); +export const nextPage = (wrapper: ReactWrapper) => + wrapper.find('EuiButtonIcon[data-test-subj="pageControlsNextPage"]'); +export const currentPage = (wrapper: ReactWrapper) => + wrapper.find('EuiButtonEmpty[data-test-subj="pageControlsCurrentPage"]'); + +describe('<PageControls />', () => { + test('null workpad renders nothing', () => { + expect(mount(<PageControls />).isEmptyRender()); + }); + + const hello = mount( + <Context source="hello"> + <PageControls /> + </Context> + ); + const austin = mount( + <Context source="austin"> + <PageControls /> + </Context> + ); + + test('hello: renders as expected', () => { + expect(previousPage(hello).props().disabled).toBeTruthy(); + expect(nextPage(hello).props().disabled).toBeTruthy(); + expect(currentPage(hello).text()).toEqual('Page 1'); + }); + + test('austin: renders as expected', () => { + expect(previousPage(austin).props().disabled).toBeTruthy(); + expect(nextPage(austin).props().disabled).toBeFalsy(); + expect(currentPage(austin).text()).toEqual('Page 1 of 28'); + }); + + test('austin: moves between pages', () => { + nextPage(austin).simulate('click'); + expect(currentPage(austin).text()).toEqual('Page 2 of 28'); + nextPage(austin).simulate('click'); + expect(currentPage(austin).text()).toEqual('Page 3 of 28'); + previousPage(austin).simulate('click'); + expect(currentPage(austin).text()).toEqual('Page 2 of 28'); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/page_preview.test.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/page_preview.test.tsx new file mode 100644 index 0000000000000..02cc81f2d1f61 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/page_preview.test.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; +import { Context } from '../../../context/mock'; +import { PagePreview } from '../page_preview.container'; + +describe('<PagePreview />', () => { + test('null workpad renders nothing', () => { + expect(mount(<PagePreview height={100} index={0} />).isEmptyRender()); + }); + + const wrapper = mount( + <Context> + <PagePreview height={100} index={0} /> + </Context> + ); + + const markdown = () => wrapper.find('.render'); + + test('renders as expected', () => { + expect(markdown().text()).toEqual('markdown mock'); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/scrubber.test.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/scrubber.test.tsx new file mode 100644 index 0000000000000..bc3f6d0147d80 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/scrubber.test.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; +import { Context } from '../../../context/mock'; +import { Scrubber } from '../scrubber.container'; + +describe('<Scrubber />', () => { + test('null workpad renders nothing', () => { + expect(mount(<Scrubber />).isEmptyRender()); + }); + + const wrapper = mount( + <Context> + <Scrubber /> + </Context> + ); + + const container = () => wrapper.find('.slideContainer'); + const markdown = () => wrapper.find('.render'); + + test('renders as expected', () => { + expect(container().children().length === 1); + expect(markdown().text()).toEqual('markdown mock'); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/title.test.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/title.test.tsx new file mode 100644 index 0000000000000..12a1401c443fb --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/__tests__/title.test.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; +import { Context } from '../../../context/mock'; +import { Title } from '../title.container'; + +describe('<Title />', () => { + test('null workpad renders nothing', () => { + expect(mount(<Title />).isEmptyRender()); + }); + + const wrapper = mount( + <Context> + <Title /> + </Context> + ); + + test('renders as expected', () => { + expect(wrapper.text()).toEqual('My Canvas Workpad'); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.container.tsx new file mode 100644 index 0000000000000..9fe43496092ff --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.container.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState, setScrubberVisibleAction } from '../../context'; +import { Footer as FooterComponent } from './footer'; +export { FOOTER_HEIGHT } from './footer'; + +interface Props { + isHidden?: boolean; +} + +/** + * The footer of the Embedded Workpad. + */ +export const Footer = ({ isHidden = false }: Props) => { + const [{ workpad, settings, footer }, dispatch] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { toolbar } = settings; + const { isAutohide } = toolbar; + const { isScrubberVisible } = footer; + + // If autohide is enabled, and the toolbar is hidden, set the scrubber + // visibility to hidden. This is useful for state changes where one + // sets the footer to hidden, and the scrubber would be left open with + // no toolbar. + if (isAutohide && isHidden && isScrubberVisible) { + dispatch(setScrubberVisibleAction(false)); + } + + return <FooterComponent {...{ isHidden, isAutohide }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx index 6716d46fba52a..04ca1a270fdcf 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx @@ -6,46 +6,32 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useExternalEmbedState, setScrubberVisible } from '../../context'; -import { Scrubber } from './scrubber'; -import { Title } from './title'; -import { PageControls } from './page_controls'; +import { Scrubber } from './scrubber.container'; +import { Title } from './title.container'; +import { PageControls } from './page_controls.container'; import { Settings } from './settings'; -// @ts-ignore CSS Module -import css from './footer.module'; +import css from './footer.module.scss'; export const FOOTER_HEIGHT = 48; interface Props { - hidden?: boolean; + isAutohide?: boolean; + isHidden?: boolean; } /** * The footer of the Embedded Workpad. */ -export const Footer = ({ hidden = false }: Props) => { - const [{ workpad, settings }] = useExternalEmbedState(); - if (!workpad) { - return null; - } - - const { autohide } = settings.toolbar; - - // If autohide is enabled, and the toolbar is hidden, set the scrubber - // visibility to hidden. This is useful for state changes where one - // sets the footer to hidden, and the scrubber would be left open with - // no toolbar. - if (autohide && hidden) { - setScrubberVisible(false); - } +export const Footer = ({ isAutohide = false, isHidden = false }: Props) => { + const { root, bar, title } = css; return ( - <div className={css.root} style={{ height: FOOTER_HEIGHT }}> + <div className={root} style={{ height: FOOTER_HEIGHT }}> <Scrubber /> - <div className={css.bar} style={{ bottom: autohide && hidden ? -FOOTER_HEIGHT : 0 }}> + <div className={bar} style={{ bottom: isAutohide && isHidden ? -FOOTER_HEIGHT : 0 }}> <EuiFlexGroup gutterSize="none"> - <EuiFlexItem className={css.title}> + <EuiFlexItem className={title}> <Title /> </EuiFlexItem> <EuiFlexItem grow={false}> diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/index.ts b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/index.ts index 72acba529b25b..42fc2a38e5909 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/index.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './footer'; +export * from './footer.container'; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.container.tsx new file mode 100644 index 0000000000000..e7aaafd394702 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.container.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + useExternalEmbedState, + setScrubberVisibleAction, + setPageAction, + setAutoplayAction, +} from '../../context'; +import { PageControls as PageControlsComponent } from './page_controls'; + +/** + * The page count and paging controls within the footer of the Embedded Workpad. + */ +export const PageControls = () => { + const [{ workpad, footer, stage }, dispatch] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { isScrubberVisible } = footer; + const { page } = stage; + const totalPages = workpad.pages.length; + + const onToggleScrubber = () => { + dispatch(setAutoplayAction(false)); + dispatch(setScrubberVisibleAction(!isScrubberVisible)); + }; + const onSetPageNumber = (number: number) => dispatch(setPageAction(number)); + + return <PageControlsComponent {...{ onToggleScrubber, onSetPageNumber, page, totalPages }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.tsx index 86075c274740d..8bf480c33b322 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.tsx @@ -6,42 +6,42 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiButtonEmpty, EuiText } from '@elastic/eui'; -import { useExternalEmbedState, setScrubberVisible, setPage, setAutoplay } from '../../context'; + +export type onSetPageNumberProp = (page: number) => void; +export type onToggleScrubberProp = () => void; + +interface Props { + page: number; + totalPages: number; + onSetPageNumber: onSetPageNumberProp; + onToggleScrubber: onToggleScrubberProp; +} /** * The page count and paging controls within the footer of the Embedded Workpad. */ -export const PageControls = () => { - const [{ workpad, footer, page }, dispatch] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - - const { isScrubberVisible } = footer; - - const toggleScrubber = () => { - dispatch(setAutoplay(false)); - dispatch(setScrubberVisible(!isScrubberVisible)); - }; - - const setPageNumber = (number: number) => dispatch(setPage(number)); +export const PageControls = ({ onSetPageNumber, page, totalPages, onToggleScrubber }: Props) => { const currentPage = page + 1; - const totalPages = workpad.pages.length; return ( <EuiFlexGroup alignItems="center" gutterSize="none" style={{ margin: '0 12px' }}> <EuiFlexItem grow={false}> <EuiButtonIcon color="ghost" - onClick={() => setPageNumber(page - 1)} + data-test-subj="pageControlsPrevPage" + onClick={() => onSetPageNumber(page - 1)} iconType="arrowLeft" disabled={currentPage <= 1} aria-label="Previous Page" /> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButtonEmpty color="ghost" size="s" onClick={toggleScrubber}> + <EuiButtonEmpty + color="ghost" + size="s" + onClick={onToggleScrubber} + data-test-subj="pageControlsCurrentPage" + > <EuiText color="ghost" size="s"> Page {currentPage} {totalPages > 1 ? ` of ${totalPages}` : null} @@ -51,7 +51,8 @@ export const PageControls = () => { <EuiFlexItem grow={false}> <EuiButtonIcon color="ghost" - onClick={() => setPageNumber(page + 1)} + data-test-subj="pageControlsNextPage" + onClick={() => onSetPageNumber(page + 1)} iconType="arrowRight" disabled={currentPage >= totalPages} aria-label="Next Page" diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.container.tsx new file mode 100644 index 0000000000000..1ecaf67195e80 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.container.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../../context'; +import { setPageAction } from '../../context/actions'; +import { PagePreview as PagePreviewComponent } from './page_preview'; + +interface Props { + index: number; + height: number; +} + +/** + * The small preview of the page shown within the `Scrubber`. + */ +export const PagePreview = ({ index, height }: Props) => { + const [{ workpad }, dispatch] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const page = workpad.pages[index]; + const onClick = (pageIndex: number) => dispatch(setPageAction(pageIndex)); + const { height: workpadHeight, width: workpadWidth } = workpad; + + return ( + <PagePreviewComponent {...{ onClick, height, workpadHeight, workpadWidth, page, index }} /> + ); +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.tsx index 805d68a9e2f2c..213cc94a865d1 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.tsx @@ -5,36 +5,39 @@ */ import React from 'react'; -import { useExternalEmbedState } from '../../context'; import { Page } from '../page'; -import { setPage } from '../../context/actions'; import { CanvasRenderedPage } from '../../types'; -// @ts-ignore CSS Module -import css from './page_preview.module'; +import css from './page_preview.module.scss'; -interface Props { - number: number; +export type onClickProp = (index: number) => void; + +export interface Props { height: number; + index: number; + onClick: onClickProp; page: CanvasRenderedPage; + workpadHeight: number; + workpadWidth: number; } /** * The small preview of the page shown within the `Scrubber`. */ -export const PagePreview = ({ number, page, height }: Props) => { - const [{ workpad }, dispatch] = useExternalEmbedState(); - if (!workpad) { - return null; - } - - const onClick = (index: number) => dispatch(setPage(index)); - const { height: workpadHeight, width: workpadWidth } = workpad; +export const PagePreview = ({ + height, + index, + onClick, + page, + workpadHeight, + workpadWidth, +}: Props) => { const scale = height / workpadHeight; const style = { height: workpadHeight * scale, width: workpadWidth * scale, }; + const transform = { ...style, transform: `scale3d(${scale}, ${scale}, 1)`, @@ -43,12 +46,12 @@ export const PagePreview = ({ number, page, height }: Props) => { return ( <div className={css.root} - onClick={() => onClick(number)} - onKeyPress={() => onClick(number)} + onClick={() => onClick(index)} + onKeyPress={() => onClick(index)} style={style} > <div className={css.preview} style={transform}> - <Page page={page} /> + <Page {...{ page }} height={workpadHeight} width={workpadWidth} /> </div> </div> ); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.container.tsx new file mode 100644 index 0000000000000..929a732040e0e --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.container.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../../context'; +import { Scrubber as ScrubberComponent } from './scrubber'; + +/** + * The panel of previews of the pages in the workpad, allowing one to select and + * navigate to a specific page. + */ +export const Scrubber = () => { + const [{ workpad, footer }] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { pages } = workpad; + const { isScrubberVisible } = footer; + + return <ScrubberComponent {...{ pages, isScrubberVisible }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.tsx index 140b45fc171cc..f5f0a8b43b143 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.tsx @@ -6,11 +6,15 @@ import React from 'react'; import classnames from 'classnames'; -import { useExternalEmbedState } from '../../context'; -import { PagePreview } from './page_preview'; +import { PagePreview } from './page_preview.container'; -// @ts-ignore CSS Module -import css from './scrubber.module'; +import css from './scrubber.module.scss'; +import { CanvasRenderedPage } from '../../types'; + +interface Props { + isScrubberVisible: boolean; + pages: CanvasRenderedPage[]; +} const THUMBNAIL_HEIGHT = 100; @@ -18,19 +22,11 @@ const THUMBNAIL_HEIGHT = 100; * The panel of previews of the pages in the workpad, allowing one to select and * navigate to a specific page. */ -export const Scrubber = () => { - const [{ workpad, footer }] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - - const { pages } = workpad; - const { isScrubberVisible } = footer; +export const Scrubber = ({ isScrubberVisible, pages }: Props) => { const className = isScrubberVisible ? classnames(css.root, css.visible) : css.root; const slides = pages.map((page, index) => ( - <PagePreview key={page.id} page={page} number={index} height={THUMBNAIL_HEIGHT} /> + <PagePreview key={page.id} height={THUMBNAIL_HEIGHT} {...{ index }} /> )); return ( diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/__snapshots__/settings.components.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/__snapshots__/settings.components.examples.storyshot new file mode 100644 index 0000000000000..8fb600f107448 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/__snapshots__/settings.components.examples.storyshot @@ -0,0 +1,445 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime/Settings/components AutoplaySettings, autoplay disabled 1`] = ` +<div + style={ + Object { + "padding": 16, + } + } +> + <div + className="euiSwitch" + > + <input + checked={false} + className="euiSwitch__input" + id="cycle" + name="cycle" + onChange={[Function]} + type="checkbox" + /> + <span + className="euiSwitch__body" + > + <span + className="euiSwitch__thumb" + /> + <span + className="euiSwitch__track" + > + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </span> + <label + className="euiSwitch__label" + htmlFor="cycle" + > + Cycle Slides + </label> + </div> + <hr + className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" + /> + <form + onSubmit={[Function]} + > + <div + className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive" + > + <div + className="euiFlexItem" + > + <div + className="euiFormRow euiFormRow--compressed" + id="generated-id-row" + > + <div> + <label + className="euiFormLabel" + htmlFor="generated-id" + > + Set a custom interval + </label> + </div> + <div + className="euiFormControlLayout euiFormControlLayout--compressed" + > + <div + className="euiFormControlLayout__childrenWrapper" + > + <input + aria-describedby="generated-id-help" + className="euiFieldText euiFieldText--compressed" + id="generated-id" + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + type="text" + value="5s" + /> + </div> + </div> + <div + className="euiFormHelpText euiFormRow__text" + id="generated-id-help" + > + Use shorthand notation, like 30s, 10m, or 1h + </div> + </div> + </div> + <div + className="euiFlexItem euiFlexItem--flexGrowZero" + > + <div + className="euiFormRow" + id="generated-id-row" + > + <div> + <label + className="euiFormLabel" + htmlFor="generated-id" + > +   + </label> + </div> + <button + className="euiButton euiButton--primary euiButton--small" + disabled={false} + id="generated-id" + onBlur={[Function]} + onFocus={[Function]} + style={ + Object { + "minWidth": "auto", + } + } + type="submit" + > + <span + className="euiButton__content" + > + <span + className="euiButton__text" + > + Set + </span> + </span> + </button> + </div> + </div> + </div> + </form> +</div> +`; + +exports[`Storyshots runtime/Settings/components AutoplaySettings, autoplay enabled 1`] = ` +<div + style={ + Object { + "padding": 16, + } + } +> + <div + className="euiSwitch" + > + <input + checked={true} + className="euiSwitch__input" + id="cycle" + name="cycle" + onChange={[Function]} + type="checkbox" + /> + <span + className="euiSwitch__body" + > + <span + className="euiSwitch__thumb" + /> + <span + className="euiSwitch__track" + > + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </span> + <label + className="euiSwitch__label" + htmlFor="cycle" + > + Cycle Slides + </label> + </div> + <hr + className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" + /> + <form + onSubmit={[Function]} + > + <div + className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive" + > + <div + className="euiFlexItem" + > + <div + className="euiFormRow euiFormRow--compressed" + id="generated-id-row" + > + <div> + <label + className="euiFormLabel" + htmlFor="generated-id" + > + Set a custom interval + </label> + </div> + <div + className="euiFormControlLayout euiFormControlLayout--compressed" + > + <div + className="euiFormControlLayout__childrenWrapper" + > + <input + aria-describedby="generated-id-help" + className="euiFieldText euiFieldText--compressed" + id="generated-id" + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + type="text" + value="5s" + /> + </div> + </div> + <div + className="euiFormHelpText euiFormRow__text" + id="generated-id-help" + > + Use shorthand notation, like 30s, 10m, or 1h + </div> + </div> + </div> + <div + className="euiFlexItem euiFlexItem--flexGrowZero" + > + <div + className="euiFormRow" + id="generated-id-row" + > + <div> + <label + className="euiFormLabel" + htmlFor="generated-id" + > +   + </label> + </div> + <button + className="euiButton euiButton--primary euiButton--small" + disabled={false} + id="generated-id" + onBlur={[Function]} + onFocus={[Function]} + style={ + Object { + "minWidth": "auto", + } + } + type="submit" + > + <span + className="euiButton__content" + > + <span + className="euiButton__text" + > + Set + </span> + </span> + </button> + </div> + </div> + </div> + </form> +</div> +`; + +exports[`Storyshots runtime/Settings/components ToolbarSettings, autohide disabled 1`] = ` +<div + style={ + Object { + "padding": 16, + } + } +> + <div + className="euiFormRow" + id="generated-id-row" + > + <div + className="euiSwitch" + > + <input + aria-describedby="generated-id-help" + checked={false} + className="euiSwitch__input" + id="generated-id" + name="toolbarHide" + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + type="checkbox" + /> + <span + className="euiSwitch__body" + > + <span + className="euiSwitch__thumb" + /> + <span + className="euiSwitch__track" + > + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </span> + <label + className="euiSwitch__label" + htmlFor="generated-id" + > + Hide Toolbar + </label> + </div> + <div + className="euiFormHelpText euiFormRow__text" + id="generated-id-help" + > + Hide the toolbar when the mouse is not within the Canvas? + </div> + </div> +</div> +`; + +exports[`Storyshots runtime/Settings/components ToolbarSettings, autohide enabled 1`] = ` +<div + style={ + Object { + "padding": 16, + } + } +> + <div + className="euiFormRow" + id="generated-id-row" + > + <div + className="euiSwitch" + > + <input + aria-describedby="generated-id-help" + checked={true} + className="euiSwitch__input" + id="generated-id" + name="toolbarHide" + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + type="checkbox" + /> + <span + className="euiSwitch__body" + > + <span + className="euiSwitch__thumb" + /> + <span + className="euiSwitch__track" + > + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </span> + <label + className="euiSwitch__label" + htmlFor="generated-id" + > + Hide Toolbar + </label> + </div> + <div + className="euiFormHelpText euiFormRow__text" + id="generated-id-help" + > + Hide the toolbar when the mouse is not within the Canvas? + </div> + </div> +</div> +`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/__snapshots__/settings.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/__snapshots__/settings.examples.storyshot new file mode 100644 index 0000000000000..2623c225abf09 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/__snapshots__/settings.examples.storyshot @@ -0,0 +1,305 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime/Settings AutoplaySettings 1`] = ` +<div + className="kbnCanvas" + style={ + Object { + "background": "#fff", + "height": undefined, + "overflow": "hidden", + "position": "relative", + "width": 250, + } + } +> + <div + style={ + Object { + "padding": 16, + } + } + > + <div + className="euiSwitch" + > + <input + checked={false} + className="euiSwitch__input" + id="cycle" + name="cycle" + onChange={[Function]} + type="checkbox" + /> + <span + className="euiSwitch__body" + > + <span + className="euiSwitch__thumb" + /> + <span + className="euiSwitch__track" + > + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </span> + <label + className="euiSwitch__label" + htmlFor="cycle" + > + Cycle Slides + </label> + </div> + <hr + className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" + /> + <form + onSubmit={[Function]} + > + <div + className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive" + > + <div + className="euiFlexItem" + > + <div + className="euiFormRow euiFormRow--compressed" + id="generated-id-row" + > + <div> + <label + className="euiFormLabel" + htmlFor="generated-id" + > + Set a custom interval + </label> + </div> + <div + className="euiFormControlLayout euiFormControlLayout--compressed" + > + <div + className="euiFormControlLayout__childrenWrapper" + > + <input + aria-describedby="generated-id-help" + className="euiFieldText euiFieldText--compressed" + id="generated-id" + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + type="text" + value="5s" + /> + </div> + </div> + <div + className="euiFormHelpText euiFormRow__text" + id="generated-id-help" + > + Use shorthand notation, like 30s, 10m, or 1h + </div> + </div> + </div> + <div + className="euiFlexItem euiFlexItem--flexGrowZero" + > + <div + className="euiFormRow" + id="generated-id-row" + > + <div> + <label + className="euiFormLabel" + htmlFor="generated-id" + > +   + </label> + </div> + <button + className="euiButton euiButton--primary euiButton--small" + disabled={false} + id="generated-id" + onBlur={[Function]} + onFocus={[Function]} + style={ + Object { + "minWidth": "auto", + } + } + type="submit" + > + <span + className="euiButton__content" + > + <span + className="euiButton__text" + > + Set + </span> + </span> + </button> + </div> + </div> + </div> + </form> + </div> +</div> +`; + +exports[`Storyshots runtime/Settings Settings 1`] = ` +<div + className="kbnCanvas" + style={ + Object { + "background": "#333", + "height": undefined, + "overflow": "hidden", + "padding": 10, + "position": "relative", + "width": undefined, + } + } +> + <div + className="euiFlexGroup euiFlexGroup--alignItemsFlexEnd euiFlexGroup--justifyContentCenter euiFlexGroup--directionColumn euiFlexGroup--responsive" + > + <div + className="euiFlexItem euiFlexItem--flexGrowZero" + > + <div + className="euiPopover euiPopover--anchorUpRight euiPopover--withTitle" + id="settings" + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseUp={[Function]} + onTouchEnd={[Function]} + onTouchStart={[Function]} + > + <div + className="euiPopover__anchor" + > + <button + aria-label="Settings" + className="euiButtonIcon euiButtonIcon--ghost" + onClick={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + </button> + </div> + </div> + </div> + </div> +</div> +`; + +exports[`Storyshots runtime/Settings ToolbarSettings 1`] = ` +<div + className="kbnCanvas" + style={ + Object { + "background": "#fff", + "height": undefined, + "overflow": "hidden", + "position": "relative", + "width": 250, + } + } +> + <div + style={ + Object { + "padding": 16, + } + } + > + <div + className="euiFormRow" + id="generated-id-row" + > + <div + className="euiSwitch" + > + <input + aria-describedby="generated-id-help" + checked={false} + className="euiSwitch__input" + id="generated-id" + name="toolbarHide" + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + type="checkbox" + /> + <span + className="euiSwitch__body" + > + <span + className="euiSwitch__thumb" + /> + <span + className="euiSwitch__track" + > + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + <svg + className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked" + focusable="false" + height={16} + style={null} + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </span> + <label + className="euiSwitch__label" + htmlFor="generated-id" + > + Hide Toolbar + </label> + </div> + <div + className="euiFormHelpText euiFormRow__text" + id="generated-id-help" + > + Hide the toolbar when the mouse is not within the Canvas? + </div> + </div> + </div> +</div> +`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/settings.components.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/settings.components.examples.tsx new file mode 100644 index 0000000000000..574e51692af8a --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/settings.components.examples.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { AutoplaySettings } from '../autoplay_settings'; +import { ToolbarSettings } from '../toolbar_settings'; + +storiesOf('runtime/Settings/components', module) + .add('AutoplaySettings, autoplay disabled', () => ( + <AutoplaySettings + onSetAutoplay={action('onSetAutoplay')} + isEnabled={false} + interval="5s" + onSetInterval={action('onSetInterval')} + /> + )) + .add('AutoplaySettings, autoplay enabled', () => ( + <AutoplaySettings + onSetAutoplay={action('onSetAutoplay')} + isEnabled={true} + interval="5s" + onSetInterval={action('onSetInterval')} + /> + )) + .add('ToolbarSettings, autohide enabled', () => ( + <ToolbarSettings onSetAutohide={action('onSetInterval')} isAutohide={true} /> + )) + .add('ToolbarSettings, autohide disabled', () => ( + <ToolbarSettings onSetAutohide={action('onSetInterval')} isAutohide={false} /> + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/settings.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/settings.examples.tsx new file mode 100644 index 0000000000000..851565ae751b0 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__examples__/settings.examples.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { Context } from '../../../../context/mock'; + +import { AutoplaySettings } from '../autoplay_settings.container'; +import { Settings } from '../settings.container'; +import { ToolbarSettings } from '../toolbar_settings.container'; + +storiesOf('runtime/Settings', module) + .add('Settings', () => ( + <Context style={{ background: '#333', padding: 10 }}> + <Settings /> + </Context> + )) + .add('AutoplaySettings', () => ( + <Context width={250} style={{ background: '#fff' }}> + <AutoplaySettings /> + </Context> + )) + .add('ToolbarSettings', () => ( + <Context width={250} style={{ background: '#fff' }}> + <ToolbarSettings /> + </Context> + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap new file mode 100644 index 0000000000000..52ed7019f7c9a --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap @@ -0,0 +1,553 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Settings /> can navigate Autoplay Settings 1`] = ` +<div> + <div + data-focus-guard="true" + style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" + tabindex="-1" + /> + <div + data-focus-guard="true" + style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" + tabindex="-1" + /> + <div + data-focus-lock-disabled="disabled" + > + <div + aria-live="assertive" + class="euiPanel euiPopover__panel euiPopover__panel--top euiPopover__panel-withTitle" + style="top: -16px; left: -22px; z-index: 0;" + > + <div + class="euiPopover__panelArrow euiPopover__panelArrow--top" + style="left: 10px; top: 0px;" + /> + <div> + <div + class="euiContextMenu" + style="height: 0px;" + > + <div + class="euiContextMenuPanel euiContextMenu__panel" + tabindex="0" + > + <div + class="euiPopoverTitle" + > + <span + class="euiContextMenu__itemLayout" + > + Settings + </span> + </div> + <div> + <div> + <button + class="euiContextMenuItem" + type="button" + > + <span + class="euiContextMenu__itemLayout" + > + <svg + class="euiIcon euiIcon--medium euiIcon-isLoading euiContextMenu__icon" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + /> + <span + class="euiContextMenuItem__text" + > + Auto Play + </span> + <svg + class="euiIcon euiIcon--medium euiIcon-isLoading euiContextMenu__arrow" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </button> + <button + class="euiContextMenuItem" + type="button" + > + <span + class="euiContextMenu__itemLayout" + > + <svg + class="euiIcon euiIcon--medium euiIcon-isLoading euiContextMenu__icon" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + /> + <span + class="euiContextMenuItem__text" + > + Toolbar + </span> + <svg + class="euiIcon euiIcon--medium euiIcon-isLoading euiContextMenu__arrow" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </button> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + <div + data-focus-guard="true" + style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" + tabindex="-1" + /> +</div> +`; + +exports[`<Settings /> can navigate Autoplay Settings 2`] = ` +<div> + <div + data-focus-guard="true" + style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" + tabindex="-1" + /> + <div + data-focus-guard="true" + style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" + tabindex="-1" + /> + <div + data-focus-lock-disabled="disabled" + > + <div + aria-live="assertive" + class="euiPanel euiPopover__panel euiPopover__panel--top euiPopover__panel-isOpen euiPopover__panel-withTitle" + style="top: -16px; left: -22px; z-index: 0;" + > + <div + class="euiPopover__panelArrow euiPopover__panelArrow--top" + style="left: 10px; top: 0px;" + /> + <div> + <div + class="euiContextMenu" + style="height: 0px;" + > + <div + class="euiContextMenuPanel euiContextMenu__panel euiContextMenuPanel-txOutLeft" + tabindex="0" + > + <div + class="euiPopoverTitle" + > + <span + class="euiContextMenu__itemLayout" + > + Settings + </span> + </div> + <div> + <div> + <button + class="euiContextMenuItem" + type="button" + > + <span + class="euiContextMenu__itemLayout" + > + <svg + class="euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M4.608 3.063C4.345 2.895 4 3.089 4 3.418v9.167c0 .329.345.523.608.356l7.2-4.584a.426.426 0 0 0 0-.711l-7.2-4.583zm.538-.844l7.2 4.583a1.426 1.426 0 0 1 0 2.399l-7.2 4.583C4.21 14.38 3 13.696 3 12.585V3.418C3 2.307 4.21 1.624 5.146 2.22z" + /> + </svg> + <span + class="euiContextMenuItem__text" + > + Auto Play + </span> + <svg + class="euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__arrow" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5.157 13.069l4.611-4.685a.546.546 0 0 0 0-.768L5.158 2.93a.552.552 0 0 1 0-.771.53.53 0 0 1 .759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 0 1-.76 0 .552.552 0 0 1 0-.771z" + fill-rule="nonzero" + /> + </svg> + </span> + </button> + <button + class="euiContextMenuItem" + type="button" + > + <span + class="euiContextMenu__itemLayout" + > + <svg + class="euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M0 6h4v4H0V6zm1 1v2h2V7H1zm5-1h4v4H6V6zm1 1v2h2V7H7zm5-1h4v4h-4V6zm1 3h2V7h-2v2z" + /> + </svg> + <span + class="euiContextMenuItem__text" + > + Toolbar + </span> + <svg + class="euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__arrow" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5.157 13.069l4.611-4.685a.546.546 0 0 0 0-.768L5.158 2.93a.552.552 0 0 1 0-.771.53.53 0 0 1 .759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 0 1-.76 0 .552.552 0 0 1 0-.771z" + fill-rule="nonzero" + /> + </svg> + </span> + </button> + </div> + </div> + </div> + <div + class="euiContextMenuPanel euiContextMenu__panel euiContextMenuPanel-txInLeft" + tabindex="0" + > + <button + class="euiContextMenuPanelTitle" + data-test-subj="contextMenuPanelTitleButton" + type="button" + > + <span + class="euiContextMenu__itemLayout" + > + <svg + class="euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M10.843 13.069L6.232 8.384a.546.546 0 0 1 0-.768l4.61-4.685a.552.552 0 0 0 0-.771.53.53 0 0 0-.759 0l-4.61 4.684a1.65 1.65 0 0 0 0 2.312l4.61 4.684a.53.53 0 0 0 .76 0 .552.552 0 0 0 0-.771z" + fill-rule="nonzero" + /> + </svg> + <span + class="euiContextMenu__text" + > + Auto Play + </span> + </span> + </button> + <div> + <div> + <div + style="padding: 16px;" + > + <div + class="euiSwitch" + > + <input + class="euiSwitch__input" + id="cycle" + name="cycle" + type="checkbox" + /> + <span + class="euiSwitch__body" + > + <span + class="euiSwitch__thumb" + /> + <span + class="euiSwitch__track" + > + <svg + class="euiIcon euiIcon--medium euiIcon-isLoaded euiSwitch__icon" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M7.293 8L3.146 3.854a.5.5 0 1 1 .708-.708L8 7.293l4.146-4.147a.5.5 0 0 1 .708.708L8.707 8l4.147 4.146a.5.5 0 0 1-.708.708L8 8.707l-4.146 4.147a.5.5 0 0 1-.708-.708L7.293 8z" + /> + </svg> + <svg + class="euiIcon euiIcon--medium euiIcon-isLoaded euiSwitch__icon euiSwitch__icon--checked" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M6.5 12a.502.502 0 0 1-.354-.146l-4-4a.502.502 0 0 1 .708-.708L6.5 10.793l6.646-6.647a.502.502 0 0 1 .708.708l-7 7A.502.502 0 0 1 6.5 12" + fill-rule="evenodd" + /> + </svg> + </span> + </span> + <label + class="euiSwitch__label" + for="cycle" + > + Cycle Slides + </label> + </div> + <hr + class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" + /> + <form> + <div + class="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive" + > + <div + class="euiFlexItem" + > + <div + class="euiFormRow euiFormRow--compressed" + id="generated-id-row" + > + <div> + <label + class="euiFormLabel" + for="generated-id" + > + Set a custom interval + </label> + </div> + <div + class="euiFormControlLayout euiFormControlLayout--compressed" + > + <div + class="euiFormControlLayout__childrenWrapper" + > + <input + aria-describedby="generated-id-help" + class="euiFieldText euiFieldText--compressed" + id="generated-id" + type="text" + value="5s" + /> + </div> + </div> + <div + class="euiFormHelpText euiFormRow__text" + id="generated-id-help" + > + Use shorthand notation, like 30s, 10m, or 1h + </div> + </div> + </div> + <div + class="euiFlexItem euiFlexItem--flexGrowZero" + > + <div + class="euiFormRow" + id="generated-id-row" + > + <div> + <label + class="euiFormLabel" + for="generated-id" + > +   + </label> + </div> + <button + class="euiButton euiButton--primary euiButton--small" + id="generated-id" + style="min-width: auto;" + type="submit" + > + <span + class="euiButton__content" + > + <span + class="euiButton__text" + > + Set + </span> + </span> + </button> + </div> + </div> + </div> + </form> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + <div + data-focus-guard="true" + style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" + tabindex="-1" + /> +</div> +`; + +exports[`<Settings /> can navigate Toolbar Settings, closes when activated 1`] = ` +<div> + <div + data-focus-guard="true" + style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" + tabindex="-1" + /> + <div + data-focus-guard="true" + style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" + tabindex="-1" + /> + <div + data-focus-lock-disabled="disabled" + > + <div + aria-live="assertive" + class="euiPanel euiPopover__panel euiPopover__panel--top euiPopover__panel-withTitle" + style="top: -16px; left: -22px; z-index: 0;" + > + <div + class="euiPopover__panelArrow euiPopover__panelArrow--top" + style="left: 10px; top: 0px;" + /> + <div> + <div + class="euiContextMenu" + style="height: 0px;" + > + <div + class="euiContextMenuPanel euiContextMenu__panel" + tabindex="0" + > + <div + class="euiPopoverTitle" + > + <span + class="euiContextMenu__itemLayout" + > + Settings + </span> + </div> + <div> + <div> + <button + class="euiContextMenuItem" + type="button" + > + <span + class="euiContextMenu__itemLayout" + > + <svg + class="euiIcon euiIcon--medium euiIcon-isLoading euiContextMenu__icon" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + /> + <span + class="euiContextMenuItem__text" + > + Auto Play + </span> + <svg + class="euiIcon euiIcon--medium euiIcon-isLoading euiContextMenu__arrow" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </button> + <button + class="euiContextMenuItem" + type="button" + > + <span + class="euiContextMenu__itemLayout" + > + <svg + class="euiIcon euiIcon--medium euiIcon-isLoading euiContextMenu__icon" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + /> + <span + class="euiContextMenuItem__text" + > + Toolbar + </span> + <svg + class="euiIcon euiIcon--medium euiIcon-isLoading euiContextMenu__arrow" + focusable="false" + height="16" + viewBox="0 0 16 16" + width="16" + xmlns="http://www.w3.org/2000/svg" + /> + </span> + </button> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + <div + data-focus-guard="true" + style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" + tabindex="-1" + /> +</div> +`; + +exports[`<Settings /> can navigate Toolbar Settings, closes when activated 2`] = `"<div><div data-focus-guard=\\"true\\" tabindex=\\"-1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"-1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"disabled\\"><div class=\\"euiPanel euiPopover__panel euiPopover__panel--top euiPopover__panel-isOpen euiPopover__panel-withTitle\\" aria-live=\\"assertive\\" style=\\"top: -16px; left: -22px; z-index: 0;\\"><div class=\\"euiPopover__panelArrow euiPopover__panelArrow--top\\" style=\\"left: 10px; top: 0px;\\"></div><div><div class=\\"euiContextMenu\\" style=\\"height: 0px;\\"><div class=\\"euiContextMenuPanel euiContextMenu__panel euiContextMenuPanel-txOutLeft\\" tabindex=\\"0\\"><div class=\\"euiPopoverTitle\\"><span class=\\"euiContextMenu__itemLayout\\">Settings</span></div><div><div><button class=\\"euiContextMenuItem\\" type=\\"button\\"><span class=\\"euiContextMenu__itemLayout\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon\\" focusable=\\"false\\"><path d=\\"M4.608 3.063C4.345 2.895 4 3.089 4 3.418v9.167c0 .329.345.523.608.356l7.2-4.584a.426.426 0 0 0 0-.711l-7.2-4.583zm.538-.844l7.2 4.583a1.426 1.426 0 0 1 0 2.399l-7.2 4.583C4.21 14.38 3 13.696 3 12.585V3.418C3 2.307 4.21 1.624 5.146 2.22z\\"></path></svg><span class=\\"euiContextMenuItem__text\\">Auto Play</span><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__arrow\\" focusable=\\"false\\"><path fill-rule=\\"nonzero\\" d=\\"M5.157 13.069l4.611-4.685a.546.546 0 0 0 0-.768L5.158 2.93a.552.552 0 0 1 0-.771.53.53 0 0 1 .759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 0 1-.76 0 .552.552 0 0 1 0-.771z\\"></path></svg></span></button><button class=\\"euiContextMenuItem\\" type=\\"button\\"><span class=\\"euiContextMenu__itemLayout\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon\\" focusable=\\"false\\"><path d=\\"M0 6h4v4H0V6zm1 1v2h2V7H1zm5-1h4v4H6V6zm1 1v2h2V7H7zm5-1h4v4h-4V6zm1 3h2V7h-2v2z\\"></path></svg><span class=\\"euiContextMenuItem__text\\">Toolbar</span><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__arrow\\" focusable=\\"false\\"><path fill-rule=\\"nonzero\\" d=\\"M5.157 13.069l4.611-4.685a.546.546 0 0 0 0-.768L5.158 2.93a.552.552 0 0 1 0-.771.53.53 0 0 1 .759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 0 1-.76 0 .552.552 0 0 1 0-.771z\\"></path></svg></span></button></div></div></div><div class=\\"euiContextMenuPanel euiContextMenu__panel euiContextMenuPanel-txInLeft\\" tabindex=\\"0\\"><button class=\\"euiContextMenuPanelTitle\\" type=\\"button\\" data-test-subj=\\"contextMenuPanelTitleButton\\"><span class=\\"euiContextMenu__itemLayout\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon\\" focusable=\\"false\\"><path fill-rule=\\"nonzero\\" d=\\"M10.843 13.069L6.232 8.384a.546.546 0 0 1 0-.768l4.61-4.685a.552.552 0 0 0 0-.771.53.53 0 0 0-.759 0l-4.61 4.684a1.65 1.65 0 0 0 0 2.312l4.61 4.684a.53.53 0 0 0 .76 0 .552.552 0 0 0 0-.771z\\"></path></svg><span class=\\"euiContextMenu__text\\">Toolbar</span></span></button><div><div><div style=\\"padding: 16px;\\"><div class=\\"euiFormRow\\" id=\\"generated-id-row\\"><div class=\\"euiSwitch\\"><input class=\\"euiSwitch__input\\" name=\\"toolbarHide\\" id=\\"generated-id\\" type=\\"checkbox\\" data-test-subj=\\"hideToolbarSwitch\\" aria-describedby=\\"generated-id-help\\"><span class=\\"euiSwitch__body\\"><span class=\\"euiSwitch__thumb\\"></span><span class=\\"euiSwitch__track\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiSwitch__icon\\" focusable=\\"false\\"><path d=\\"M7.293 8L3.146 3.854a.5.5 0 1 1 .708-.708L8 7.293l4.146-4.147a.5.5 0 0 1 .708.708L8.707 8l4.147 4.146a.5.5 0 0 1-.708.708L8 8.707l-4.146 4.147a.5.5 0 0 1-.708-.708L7.293 8z\\"></path></svg><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiSwitch__icon euiSwitch__icon--checked\\" focusable=\\"false\\"><path fill-rule=\\"evenodd\\" d=\\"M6.5 12a.502.502 0 0 1-.354-.146l-4-4a.502.502 0 0 1 .708-.708L6.5 10.793l6.646-6.647a.502.502 0 0 1 .708.708l-7 7A.502.502 0 0 1 6.5 12\\"></path></svg></span></span><label class=\\"euiSwitch__label\\" for=\\"generated-id\\">Hide Toolbar</label></div><div class=\\"euiFormHelpText euiFormRow__text\\" id=\\"generated-id-help\\">Hide the toolbar when the mouse is not within the Canvas?</div></div></div></div></div></div></div></div></div></div><div data-focus-guard=\\"true\\" tabindex=\\"-1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div></div>"`; + +exports[`<Settings /> can navigate Toolbar Settings, closes when activated 3`] = `"<div><div data-focus-guard=\\"true\\" tabindex=\\"-1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"-1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"disabled\\"><div class=\\"euiPanel euiPopover__panel euiPopover__panel--top euiPopover__panel-withTitle\\" aria-live=\\"assertive\\" style=\\"top: -16px; left: -22px; z-index: 0;\\"><div class=\\"euiPopover__panelArrow euiPopover__panelArrow--top\\" style=\\"left: 10px; top: 0px;\\"></div><div><div class=\\"euiContextMenu\\" style=\\"height: 0px;\\"><div class=\\"euiContextMenuPanel euiContextMenu__panel euiContextMenuPanel-txOutLeft\\" tabindex=\\"0\\"><div class=\\"euiPopoverTitle\\"><span class=\\"euiContextMenu__itemLayout\\">Settings</span></div><div><div><button class=\\"euiContextMenuItem\\" type=\\"button\\"><span class=\\"euiContextMenu__itemLayout\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon\\" focusable=\\"false\\"><path d=\\"M4.608 3.063C4.345 2.895 4 3.089 4 3.418v9.167c0 .329.345.523.608.356l7.2-4.584a.426.426 0 0 0 0-.711l-7.2-4.583zm.538-.844l7.2 4.583a1.426 1.426 0 0 1 0 2.399l-7.2 4.583C4.21 14.38 3 13.696 3 12.585V3.418C3 2.307 4.21 1.624 5.146 2.22z\\"></path></svg><span class=\\"euiContextMenuItem__text\\">Auto Play</span><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__arrow\\" focusable=\\"false\\"><path fill-rule=\\"nonzero\\" d=\\"M5.157 13.069l4.611-4.685a.546.546 0 0 0 0-.768L5.158 2.93a.552.552 0 0 1 0-.771.53.53 0 0 1 .759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 0 1-.76 0 .552.552 0 0 1 0-.771z\\"></path></svg></span></button><button class=\\"euiContextMenuItem\\" type=\\"button\\"><span class=\\"euiContextMenu__itemLayout\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon\\" focusable=\\"false\\"><path d=\\"M0 6h4v4H0V6zm1 1v2h2V7H1zm5-1h4v4H6V6zm1 1v2h2V7H7zm5-1h4v4h-4V6zm1 3h2V7h-2v2z\\"></path></svg><span class=\\"euiContextMenuItem__text\\">Toolbar</span><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__arrow\\" focusable=\\"false\\"><path fill-rule=\\"nonzero\\" d=\\"M5.157 13.069l4.611-4.685a.546.546 0 0 0 0-.768L5.158 2.93a.552.552 0 0 1 0-.771.53.53 0 0 1 .759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 0 1-.76 0 .552.552 0 0 1 0-.771z\\"></path></svg></span></button></div></div></div><div class=\\"euiContextMenuPanel euiContextMenu__panel euiContextMenuPanel-txInLeft\\" tabindex=\\"0\\"><button class=\\"euiContextMenuPanelTitle\\" type=\\"button\\" data-test-subj=\\"contextMenuPanelTitleButton\\"><span class=\\"euiContextMenu__itemLayout\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon\\" focusable=\\"false\\"><path fill-rule=\\"nonzero\\" d=\\"M10.843 13.069L6.232 8.384a.546.546 0 0 1 0-.768l4.61-4.685a.552.552 0 0 0 0-.771.53.53 0 0 0-.759 0l-4.61 4.684a1.65 1.65 0 0 0 0 2.312l4.61 4.684a.53.53 0 0 0 .76 0 .552.552 0 0 0 0-.771z\\"></path></svg><span class=\\"euiContextMenu__text\\">Toolbar</span></span></button><div><div><div style=\\"padding: 16px;\\"><div class=\\"euiFormRow\\" id=\\"generated-id-row\\"><div class=\\"euiSwitch\\"><input class=\\"euiSwitch__input\\" name=\\"toolbarHide\\" id=\\"generated-id\\" type=\\"checkbox\\" data-test-subj=\\"hideToolbarSwitch\\" aria-describedby=\\"generated-id-help\\"><span class=\\"euiSwitch__body\\"><span class=\\"euiSwitch__thumb\\"></span><span class=\\"euiSwitch__track\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiSwitch__icon\\" focusable=\\"false\\"><path d=\\"M7.293 8L3.146 3.854a.5.5 0 1 1 .708-.708L8 7.293l4.146-4.147a.5.5 0 0 1 .708.708L8.707 8l4.147 4.146a.5.5 0 0 1-.708.708L8 8.707l-4.146 4.147a.5.5 0 0 1-.708-.708L7.293 8z\\"></path></svg><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiSwitch__icon euiSwitch__icon--checked\\" focusable=\\"false\\"><path fill-rule=\\"evenodd\\" d=\\"M6.5 12a.502.502 0 0 1-.354-.146l-4-4a.502.502 0 0 1 .708-.708L6.5 10.793l6.646-6.647a.502.502 0 0 1 .708.708l-7 7A.502.502 0 0 1 6.5 12\\"></path></svg></span></span><label class=\\"euiSwitch__label\\" for=\\"generated-id\\">Hide Toolbar</label></div><div class=\\"euiFormHelpText euiFormRow__text\\" id=\\"generated-id-help\\">Hide the toolbar when the mouse is not within the Canvas?</div></div></div></div></div></div></div></div></div></div><div data-focus-guard=\\"true\\" tabindex=\\"-1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div></div>"`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/autoplay_settings.test.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/autoplay_settings.test.tsx new file mode 100644 index 0000000000000..97a4432e3e377 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/autoplay_settings.test.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; +import { Context } from '../../../../context/mock'; +import { AutoplaySettings } from '../autoplay_settings.container'; + +describe('<AutoplaySettings />', () => { + const wrapper = mount( + <Context> + <AutoplaySettings /> + </Context> + ); + + const checkbox = () => wrapper.find('EuiSwitch').find('input[type="checkbox"]'); + const input = () => wrapper.find('EuiFieldText').find('input[type="text"]'); + const submit = () => wrapper.find('EuiButton'); + + test('renders as expected', () => { + expect(checkbox().props().checked).toBeFalsy(); + expect(input().props().value).toBe('5s'); + }); + + test('activates and deactivates', () => { + checkbox().simulate('change'); + expect(checkbox().props().checked).toBeTruthy(); + checkbox().simulate('change'); + expect(checkbox().props().checked).toBeFalsy(); + }); + + test('changes properly with input', () => { + input().simulate('change', { target: { value: '2asd' } }); + expect(submit().props().disabled).toBeTruthy(); + input().simulate('change', { target: { value: '2s' } }); + expect(submit().props().disabled).toBeFalsy(); + expect(input().props().value === '2s'); + submit().simulate('submit'); + expect(input().props().value === '2s'); + expect(submit().props().disabled).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/settings.test.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/settings.test.tsx new file mode 100644 index 0000000000000..dc99c188ee560 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/settings.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { Context } from '../../../../context/mock'; +import { Settings } from '../settings.container'; +import { takeMountedSnapshot, tick } from '../../../../test'; + +jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); + +describe('<Settings />', () => { + let wrapper: ReactWrapper; + + const settings = () => wrapper.find('EuiButtonIcon'); + const popover = () => wrapper.find('EuiPopover'); + const portal = () => wrapper.find('EuiPortal'); + const menuItems = () => wrapper.find('EuiContextMenuItem'); + + beforeEach(() => { + const ref = React.createRef<HTMLDivElement>(); + wrapper = mount( + <Context stageRef={ref}> + <div ref={ref}> + <Settings /> + </div> + </Context> + ); + }); + + test('renders as expected', () => { + expect(settings().exists()).toBeTruthy(); + expect(portal().exists()).toBeFalsy(); + }); + + test('clicking settings opens and closes the menu', () => { + settings().simulate('click'); + expect(portal().exists()).toBeTruthy(); + expect(popover().prop('isOpen')).toBeTruthy(); + expect(menuItems().length).toEqual(2); + expect(portal().text()).toEqual('SettingsAuto PlayToolbar'); + settings().simulate('click'); + expect(popover().prop('isOpen')).toBeFalsy(); + }); + + test('can navigate Autoplay Settings', async () => { + settings().simulate('click'); + expect(takeMountedSnapshot(portal())).toMatchSnapshot(); + await tick(20); + menuItems() + .slice(0, 1) + .simulate('click'); + await tick(20); + expect(takeMountedSnapshot(portal())).toMatchSnapshot(); + }); + + test('can navigate Toolbar Settings, closes when activated', async () => { + settings().simulate('click'); + expect(takeMountedSnapshot(portal())).toMatchSnapshot(); + menuItems() + .slice(1, 2) + .simulate('click'); + + // Wait for the animation and DOM update + await tick(20); + portal().update(); + expect(portal().html()).toMatchSnapshot(); + + // Click the Hide Toolbar switch + portal() + .find('input[data-test-subj="hideToolbarSwitch"]') + .simulate('change'); + + // Wait for the animation and DOM update + await tick(20); + portal().update(); + + // The Portal should not be open. + expect(popover().prop('isOpen')).toBeFalsy(); + expect(portal().html()).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/toolbar_settings.test.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/toolbar_settings.test.tsx new file mode 100644 index 0000000000000..42466f4513632 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/__tests__/toolbar_settings.test.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; +import { Context } from '../../../../context/mock'; +import { ToolbarSettings } from '../toolbar_settings.container'; + +describe('<ToolbarSettings />', () => { + const wrapper = mount( + <Context> + <ToolbarSettings /> + </Context> + ); + + const checkbox = () => wrapper.find('input[name="toolbarHide"].euiSwitch__input'); + + test('renders as expected', () => { + expect(checkbox().props().checked).toBeFalsy(); + }); + + test('activates and deactivates', () => { + checkbox().simulate('change'); + expect(checkbox().props().checked).toBeTruthy(); + checkbox().simulate('change'); + expect(checkbox().props().checked).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.container.tsx new file mode 100644 index 0000000000000..e26fc5e27b137 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.container.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + useExternalEmbedState, + setAutoplayAction, + setAutoplayIntervalAction, +} from '../../../context'; + +import { + AutoplaySettings as AutoplaySettingsComponent, + onSetAutoplayProp, + onSetIntervalProp, +} from './autoplay_settings'; + +/** + * The panel used to configure Autolay in Embedded Workpads. + */ +export const AutoplaySettings = () => { + const [{ settings }, dispatch] = useExternalEmbedState(); + + const { autoplay } = settings; + const { isEnabled, interval } = autoplay; + + const onSetInterval: onSetIntervalProp = (newInterval: string) => + dispatch(setAutoplayIntervalAction(newInterval)); + + const onSetAutoplay: onSetAutoplayProp = (enabled: boolean) => + dispatch(setAutoplayAction(enabled)); + + return <AutoplaySettingsComponent {...{ isEnabled, interval, onSetAutoplay, onSetInterval }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.tsx index d37bdaebefa51..f9543e85f32a7 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.tsx @@ -6,33 +6,41 @@ import React from 'react'; import { EuiHorizontalRule, EuiSwitch } from '@elastic/eui'; -import { useExternalEmbedState, setAutoplay, setAutoplayInterval } from '../../../context'; import { createTimeInterval } from '../../../../public/lib/time_interval'; // @ts-ignore Untyped local import { CustomInterval } from '../../../../public/components/workpad_header/control_settings/custom_interval'; +export type onSetAutoplayProp = (autoplay: boolean) => void; +export type onSetIntervalProp = (interval: string) => void; + +export interface Props { + isEnabled: boolean; + interval: string; + onSetAutoplay?: onSetAutoplayProp; + onSetInterval?: onSetIntervalProp; +} + /** * The panel used to configure Autolay in Embedded Workpads. */ -export const AutoplaySettings = () => { - const [{ settings }, dispatch] = useExternalEmbedState(); - - const { autoplay } = settings; - - return ( - <div style={{ padding: 16 }}> - <EuiSwitch - name="cycle" - id="cycle" - label="Cycle Slides" - checked={autoplay.enabled} - onChange={() => dispatch(setAutoplay(!autoplay.enabled))} - /> - <EuiHorizontalRule margin="m" /> - <CustomInterval - defaultValue={autoplay.interval} - onSubmit={(value: number) => dispatch(setAutoplayInterval(createTimeInterval(value)))} - /> - </div> - ); -}; +export const AutoplaySettings = ({ + isEnabled, + interval, + onSetAutoplay = () => {}, + onSetInterval = () => {}, +}: Props) => ( + <div style={{ padding: 16 }}> + <EuiSwitch + name="cycle" + id="cycle" + label="Cycle Slides" + checked={isEnabled} + onChange={() => onSetAutoplay(!isEnabled)} + /> + <EuiHorizontalRule margin="m" /> + <CustomInterval + defaultValue={interval} + onSubmit={(value: number) => onSetInterval(createTimeInterval(value))} + /> + </div> +); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/index.ts b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/index.ts index 87d5cf67e6965..0202e3d76e099 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/index.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './settings'; +export * from './settings.container'; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.container.tsx new file mode 100644 index 0000000000000..199defd464ba3 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.container.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../../../context'; +import { Settings as SettingsComponent } from './settings'; +/** + * The Settings Popover for External Workpads. + */ +export const Settings = () => { + const [{ refs }] = useExternalEmbedState(); + + return <SettingsComponent refs={refs} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.tsx index a4dad62b25bc1..8d35e85636cf3 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.tsx @@ -5,26 +5,26 @@ */ import React, { useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiPopover, EuiContextMenu } from '@elastic/eui'; -import { useExternalEmbedState } from '../../../context'; -// @ts-ignore Untyped local -import { CustomInterval } from '../../../../public/components/workpad_header/control_settings/custom_interval'; -import { ToolbarSettings } from './toolbar_settings'; -import { AutoplaySettings } from './autoplay_settings'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiButtonIcon, + EuiPopover, + EuiPopoverProps, + EuiContextMenu, +} from '@elastic/eui'; +import { Refs } from '../../../types'; +import { ToolbarSettings } from './toolbar_settings.container'; +import { AutoplaySettings } from './autoplay_settings.container'; -// @ts-ignore CSS Module -import css from './settings.module'; +interface Props { + refs: Refs; +} /** * The Settings Popover for External Workpads. */ -export const Settings = () => { - const [{ workpad, refs }] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - +export const Settings = ({ refs }: Props) => { const [isPopoverOpen, setPopoverOpen] = useState(false); const button = ( <EuiButtonIcon @@ -69,27 +69,32 @@ export const Settings = () => { panel: { id: 2, title: 'Toolbar', - content: <ToolbarSettings onChange={() => setPopoverOpen(false)} />, + content: <ToolbarSettings onSetAutohide={() => setPopoverOpen(false)} />, }, }, ], }); + const props: EuiPopoverProps = { + closePopover: () => setPopoverOpen(false), + isOpen: isPopoverOpen, + button, + panelPaddingSize: 'none', + withTitle: true, + anchorPosition: 'upRight', + }; + + if (refs.stage.current) { + props.insert = { + sibling: refs.stage.current, + position: 'after', + }; + } + return ( <EuiFlexGroup alignItems="flexEnd" justifyContent="center" direction="column" gutterSize="none"> <EuiFlexItem grow={false}> - {/* - //@ts-ignore EuiPopover missing insert property */} - <EuiPopover - closePopover={() => setPopoverOpen(false)} - id="settings" - isOpen={isPopoverOpen} - button={button} - panelPaddingSize="none" - withTitle - anchorPosition="upRight" - insert={{ sibling: refs.stage.current, position: 'after' }} - > + <EuiPopover id="settings" {...props}> <EuiContextMenu initialPanelId={0} panels={panels} /> </EuiPopover> </EuiFlexItem> diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.container.tsx new file mode 100644 index 0000000000000..af876e8708805 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.container.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState, setToolbarAutohideAction } from '../../../context'; +import { ToolbarSettings as ToolbarSettingsComponent, onSetAutohideProp } from './toolbar_settings'; + +interface Props { + onSetAutohide?: onSetAutohideProp; +} + +/** + * The settings panel for the Toolbar of an Embedded Workpad. + */ +export const ToolbarSettings = ({ onSetAutohide = () => {} }: Props) => { + const [{ settings }, dispatch] = useExternalEmbedState(); + + const { toolbar } = settings; + const { isAutohide } = toolbar; + + const onSetAutohideFn: onSetAutohideProp = (autohide: boolean) => { + onSetAutohide(autohide); + dispatch(setToolbarAutohideAction(autohide)); + }; + + return <ToolbarSettingsComponent onSetAutohide={onSetAutohideFn} {...{ isAutohide }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.tsx index 8e11e8a075acc..78ba0154aff94 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.tsx @@ -6,35 +6,28 @@ import React from 'react'; import { EuiSwitch, EuiFormRow } from '@elastic/eui'; -import { useExternalEmbedState, setToolbarAutohide } from '../../../context'; -// @ts-ignore CSS Module -import css from './settings.module'; +export type onSetAutohideProp = (isAutohide: boolean) => void; interface Props { - onChange?: () => void; + isAutohide: boolean; + onSetAutohide?: onSetAutohideProp; } /** * The settings panel for the Toolbar of an Embedded Workpad. */ -export const ToolbarSettings = ({ onChange = () => {} }: Props) => { - const [{ settings }, dispatch] = useExternalEmbedState(); - - const { toolbar } = settings; - +export const ToolbarSettings = ({ isAutohide, onSetAutohide = () => {} }: Props) => { return ( <div style={{ padding: 16 }}> <EuiFormRow helpText="Hide the toolbar when the mouse is not within the Canvas?"> <EuiSwitch + data-test-subj="hideToolbarSwitch" name="toolbarHide" id="toolbarHide" label="Hide Toolbar" - checked={toolbar.autohide} - onChange={() => { - onChange(); - dispatch(setToolbarAutohide(!toolbar.autohide)); - }} + checked={isAutohide} + onChange={() => onSetAutohide(!isAutohide)} /> </EuiFormRow> </div> diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.container.tsx new file mode 100644 index 0000000000000..96740d2e67978 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.container.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../../context'; +import { Title as TitleComponent } from './title'; + +/** + * The title of the workpad displayed in the right-hand of the footer. + */ +export const Title = () => { + const [{ workpad }] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { name: title } = workpad; + + return <TitleComponent {...{ title }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.tsx index 6296e55007ef6..c7339fe14d739 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.tsx @@ -6,28 +6,22 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; -import { useExternalEmbedState } from '../../context'; +interface Props { + title: string; +} /** * The title of the workpad displayed in the right-hand of the footer. */ -export const Title = () => { - const [{ workpad }] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - - return ( - <EuiFlexGroup gutterSize="s" justifyContent="flexStart" alignItems="center"> - <EuiFlexItem grow={false}> - <EuiIcon type="logoKibana" size="m" /> - </EuiFlexItem> - <EuiFlexItem grow={false} style={{ minWidth: 0 }}> - <EuiText color="ghost" size="s"> - <div className="eui-textTruncate">{workpad.name}</div> - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - ); -}; +export const Title = ({ title }: Props) => ( + <EuiFlexGroup gutterSize="s" justifyContent="flexStart" alignItems="center"> + <EuiFlexItem grow={false}> + <EuiIcon type="logoKibana" size="m" /> + </EuiFlexItem> + <EuiFlexItem grow={false} style={{ minWidth: 0, cursor: 'default' }}> + <EuiText color="ghost" size="s"> + <div className="eui-textTruncate">{title}</div> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> +); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/page.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/page.container.tsx new file mode 100644 index 0000000000000..9f10ac3fb543f --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/page.container.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../context'; +import { Page as PageComponent } from './page'; + +interface Props { + index: number; +} + +export const Page = ({ index }: Props) => { + const [{ workpad }] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { height, width, pages } = workpad; + const page = pages[index]; + + return <PageComponent {...{ page, height, width }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/page.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/page.tsx index 538bf885c566d..25bf07e5b8329 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/page.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/page.tsx @@ -5,28 +5,22 @@ */ import React from 'react'; -import { RenderedElement } from './rendered_element'; -import { useExternalEmbedState } from '../context'; +import { RenderedElement } from './rendered_element.container'; import { CanvasRenderedPage, CanvasRenderedElement } from '../types'; -// @ts-ignore CSS Module -import css from './page.module'; +import css from './page.module.scss'; interface Props { + height: number; + width: number; page: CanvasRenderedPage; } -export const Page = (props: Props) => { - const [{ workpad }] = useExternalEmbedState(); - if (!workpad) { - return null; - } +export const Page = ({ page, height, width }: Props) => { + const { elements, style, id } = page; - const { height, width, id } = workpad; - const { elements, style } = props.page; - - const output = elements.map((element: CanvasRenderedElement, index) => ( - <RenderedElement key={element.id} element={element} number={index + 1} /> + const output = elements.map((element: CanvasRenderedElement, i) => ( + <RenderedElement key={element.id} element={element} index={i + 1} /> )); return ( diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.container.tsx new file mode 100644 index 0000000000000..c6ce2230be8ca --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.container.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../context'; +import { CanvasRenderedElement } from '../types'; +import { RenderedElement as RenderedElementComponent } from './rendered_element'; + +interface Props { + element: CanvasRenderedElement; + index: number; +} + +export const RenderedElement = ({ index, element }: Props) => { + const [{ renderers }] = useExternalEmbedState(); + + const { expressionRenderable } = element; + const { value } = expressionRenderable; + const { as } = value; + const fn = renderers[as]; + + return <RenderedElementComponent {...{ element, fn, index }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.tsx index 2a415d71ec1e1..ca8659dfb6ca8 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.tsx @@ -14,12 +14,13 @@ import { elementToShape } from '../../public/components/workpad_page/utils'; import { CanvasRenderedElement } from '../types'; import { ExternalEmbedContext } from '../context'; -// @ts-ignore CSS Module -import css from './rendered_element.module'; +import css from './rendered_element.module.scss'; +import { RendererSpec } from '../../types'; interface Props { element: CanvasRenderedElement; - number?: number; + index: number; + fn: RendererSpec; } /** @@ -38,31 +39,34 @@ export class RenderedElement extends React.PureComponent<Props> { } componentDidMount() { - const [{ renderersRegistry }] = this.context; - const { element } = this.props; + const { element, fn } = this.props; const { expressionRenderable } = element; const { value } = expressionRenderable; const { as } = value; - const fn = renderersRegistry.get(as); + const { current } = this.ref; + + if (!current) { + return; + } try { // TODO: These are stubbed, but may need implementation. - fn.render(this.ref.current, value.value, { + fn.render(current, value.value, { done: () => {}, onDestroy: () => {}, onResize: () => {}, setFilter: () => {}, - getFilter: () => {}, + getFilter: () => '', }); } catch (e) { // eslint-disable-next-line no-console - console.log(e.message); + console.log(as, e.message); } } render() { - const { element, number } = this.props; - const shape = elementToShape(element, number || 1); + const { element, index } = this.props; + const shape = elementToShape(element, index || 1); const { id, expressionRenderable, position } = element; const { value } = expressionRenderable; const { as, css: elementCSS, containerStyle } = value; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/actions.ts b/x-pack/legacy/plugins/canvas/external_runtime/context/actions.ts index 37b1d082dd8f9..12faf8e4bd1f4 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/context/actions.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/actions.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CanvasRenderedWorkpad } from '../types'; - /** * This enumeration applies a strong type to all of the actions that can be * triggered from the interface. @@ -15,7 +13,6 @@ export enum ExternalEmbedActions { SET_PAGE = 'SET_PAGE', SET_SCRUBBER_VISIBLE = 'SET_SCRUBBER_VISIBLE', SET_AUTOPLAY = 'SET_AUTOPLAY', - SET_AUTOPLAY_ANIMATE = 'SET_AUTOPLAY_ANIMATE', SET_AUTOPLAY_INTERVAL = 'SET_AUTOPLAY_INTERVAL', SET_TOOLBAR_AUTOHIDE = 'SET_TOOLBAR_AUTOHIDE', } @@ -33,24 +30,18 @@ const createAction = <T extends ExternalEmbedActions, P>( payload, }); -/** - * Set the current `CanvasRenderedWorkpad`. - * @param workpad A `CanvasRenderedWorkpad` to display. - */ -export const setWorkpad = (workpad: CanvasRenderedWorkpad) => - createAction(ExternalEmbedActions.SET_WORKPAD, { workpad }); - /** * Set the current page to display * @param page The zero-indexed page to display. */ -export const setPage = (page: number) => createAction(ExternalEmbedActions.SET_PAGE, { page }); +export const setPageAction = (page: number) => + createAction(ExternalEmbedActions.SET_PAGE, { page }); /** * Set the visibility of the page scrubber. * @param visible True if it should be visible, false otherwise. */ -export const setScrubberVisible = (visible: boolean) => { +export const setScrubberVisibleAction = (visible: boolean) => { return createAction(ExternalEmbedActions.SET_SCRUBBER_VISIBLE, { visible }); }; @@ -58,22 +49,15 @@ export const setScrubberVisible = (visible: boolean) => { * Set whether the slides should automatically advance. * @param autoplay True if it should automatically advance, false otherwise. */ -export const setAutoplay = (autoplay: boolean) => - createAction(ExternalEmbedActions.SET_AUTOPLAY, { autoplay }); - -/** - * Set whether the slides should animate when advanced. - * @param animate True if it should animate when advanced, false otherwise. - */ -export const setAutoplayAnimate = (animate: boolean) => - createAction(ExternalEmbedActions.SET_AUTOPLAY_ANIMATE, { animate }); +export const setAutoplayAction = (isEnabled: boolean) => + createAction(ExternalEmbedActions.SET_AUTOPLAY, { isEnabled }); /** * Set the interval in which slide will advance. This is a `string` identical to * that used in Canvas proper: `1m`, `2s`, etc. * @param autoplay The interval in which slides should advance. */ -export const setAutoplayInterval = (interval: string) => +export const setAutoplayIntervalAction = (interval: string) => createAction(ExternalEmbedActions.SET_AUTOPLAY_INTERVAL, { interval }); /** @@ -81,17 +65,15 @@ export const setAutoplayInterval = (interval: string) => * embedded workpad. * @param autohide True if the toolbar should hide, false otherwise. */ -export const setToolbarAutohide = (autohide: boolean) => - createAction(ExternalEmbedActions.SET_TOOLBAR_AUTOHIDE, { autohide }); +export const setToolbarAutohideAction = (isAutohide: boolean) => + createAction(ExternalEmbedActions.SET_TOOLBAR_AUTOHIDE, { isAutohide }); const actions = { - setWorkpad, - setPage, - setScrubberVisible, - setAutoplay, - setAutoplayAnimate, - setAutoplayInterval, - setToolbarAutohide, + setPageAction, + setScrubberVisibleAction, + setAutoplayAction, + setAutoplayIntervalAction, + setToolbarAutohideAction, }; /** diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/mock/context.tsx b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/context.tsx new file mode 100644 index 0000000000000..17e73f2a4bed6 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/context.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { CSSProperties, RefObject } from 'react'; +import { + initialExternalEmbedState, + ExternalEmbedStateProvider, + useExternalEmbedState, +} from '../index'; +import { renderFunctions } from '../../supported_renderers'; +import { ExternalEmbedState } from '../../types'; +import { RendererSpec } from '../../../types'; +import { snapshots, SnapshotNames } from '../../test'; + +jest.mock('../../supported_renderers'); + +const Container = ({ + children, + height, + width, + style, +}: Pick<Props, 'children' | 'height' | 'width' | 'style'>) => { + const [{ refs }] = useExternalEmbedState(); + return ( + <div + className="kbnCanvas" + ref={refs.stage} + style={{ ...style, height, width, overflow: 'hidden', position: 'relative' }} + > + {children} + </div> + ); +}; + +interface Props { + children: any; + source?: SnapshotNames; + height?: number; + width?: number; + isScrubberVisible?: boolean; + style?: CSSProperties; + stageRef?: RefObject<HTMLDivElement>; +} + +export const Context = ({ + children, + height, + width, + isScrubberVisible, + style, + stageRef, + source = 'hello', +}: Props) => { + const renderers: { [key: string]: RendererSpec } = {}; + + renderFunctions.forEach(rendererFn => { + const renderer = rendererFn(); + renderers[renderer.name] = renderer; + }); + + const { footer } = initialExternalEmbedState; + + const initialState: ExternalEmbedState = { + ...initialExternalEmbedState, + footer: { + ...footer, + isScrubberVisible: isScrubberVisible || footer.isScrubberVisible, + }, + stage: { + height: 400, + page: 0, + width: 600, + }, + renderers, + workpad: snapshots[source], + refs: { + stage: stageRef || React.createRef(), + }, + }; + + return ( + <ExternalEmbedStateProvider initialState={initialState}> + <Container {...{ height, width, style }}>{children}</Container> + </ExternalEmbedStateProvider> + ); +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/mock/index.ts b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/index.ts new file mode 100644 index 0000000000000..94b6977050535 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './context'; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/reducer.ts b/x-pack/legacy/plugins/canvas/external_runtime/context/reducer.ts index bd575c2741d31..1415311532924 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/context/reducer.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/reducer.ts @@ -4,24 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExternalEmbedState } from './state'; +import { ExternalEmbedState } from '../types'; import { ExternalEmbedAction, ExternalEmbedActions } from './actions'; /** * The Action Reducer for the Embedded Canvas Workpad interface. */ -export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) => { +export const reducer = ( + state: ExternalEmbedState, + action: ExternalEmbedAction +): ExternalEmbedState => { switch (action.type) { - case ExternalEmbedActions.SET_WORKPAD: { - return { - ...state, - workpad: action.payload.workpad, - }; - } case ExternalEmbedActions.SET_PAGE: { + const { stage } = state; return { ...state, - page: action.payload.page, + stage: { + ...stage, + page: action.payload.page, + }, }; } case ExternalEmbedActions.SET_SCRUBBER_VISIBLE: { @@ -38,6 +39,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) case ExternalEmbedActions.SET_AUTOPLAY: { const { settings } = state; const { autoplay } = settings; + const { isEnabled } = action.payload; return { ...state, @@ -45,23 +47,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) ...settings, autoplay: { ...autoplay, - enabled: action.payload.autoplay, - }, - }, - }; - } - case ExternalEmbedActions.SET_AUTOPLAY_ANIMATE: { - const { settings } = state; - const { autoplay } = settings; - const { animate } = action.payload; - - return { - ...state, - settings: { - ...settings, - autoplay: { - ...autoplay, - animate, + isEnabled, }, }, }; @@ -85,7 +71,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) case ExternalEmbedActions.SET_TOOLBAR_AUTOHIDE: { const { settings } = state; const { toolbar } = settings; - const { autohide } = action.payload; + const { isAutohide } = action.payload; return { ...state, @@ -93,7 +79,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) ...settings, toolbar: { ...toolbar, - autohide, + isAutohide, }, }, }; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/state.tsx b/x-pack/legacy/plugins/canvas/external_runtime/context/state.tsx index 7d82b5ea6263f..4bd5cdba4c55c 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/context/state.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/state.tsx @@ -4,64 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { - createContext, - useContext, - Dispatch, - useReducer, - ReactChild, - RefObject, -} from 'react'; -import { CanvasRenderedWorkpad } from '../types'; +import React, { createContext, useContext, Dispatch, useReducer, ReactChild } from 'react'; +import { ExternalEmbedState } from '../types'; import { reducer } from './reducer'; import { ExternalEmbedAction } from './actions'; -export interface ExternalEmbedState { - renderersRegistry: { - register: (fn: Function) => void; - get: (name: string) => Function; - } | null; - workpad: CanvasRenderedWorkpad | null; - page: number; - height: number; - width: number; - footer: { - isScrubberVisible: boolean; - }; - settings: { - autoplay: { - enabled: boolean; - interval: string; - animate: boolean; - }; - toolbar: { - autohide: boolean; - }; - }; - refs: { - stage: RefObject<HTMLDivElement>; - }; -} - type StateType = [ExternalEmbedState, Dispatch<ExternalEmbedAction>]; export const initialExternalEmbedState: ExternalEmbedState = { - renderersRegistry: null, + renderers: {}, workpad: null, - page: 0, - height: 0, - width: 0, + stage: { + page: 0, + height: 0, + width: 0, + }, footer: { isScrubberVisible: false, }, settings: { autoplay: { - enabled: false, + isEnabled: false, interval: '5s', - animate: false, + isAnimated: false, }, toolbar: { - autohide: false, + isAutohide: false, }, }, refs: { diff --git a/x-pack/legacy/plugins/canvas/external_runtime/css_modules.d.ts b/x-pack/legacy/plugins/canvas/external_runtime/css_modules.d.ts new file mode 100644 index 0000000000000..99cc85e0c5d08 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/css_modules.d.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +declare module '*.module.scss' { + const styles: { [className: string]: string }; + // eslint-disable-next-line + export default styles; +} diff --git a/x-pack/legacy/plugins/canvas/external_runtime/index.html b/x-pack/legacy/plugins/canvas/external_runtime/index.html index d27356c97c860..a2d572ac2ce38 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/index.html +++ b/x-pack/legacy/plugins/canvas/external_runtime/index.html @@ -5,7 +5,7 @@ </head> <body> - <div kbn-canvas-embed="canvas" kbn-canvas-height="400" kbn-canvas-url="/test/austin.json"></div> + <div kbn-canvas-embed="canvas" kbn-canvas-height="400" kbn-canvas-url="/test/hello.json"></div> </body> <script type="text/javascript"> KbnCanvas.embed(); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/supported_renderers.d.ts b/x-pack/legacy/plugins/canvas/external_runtime/supported_renderers.d.ts new file mode 100644 index 0000000000000..837c2740c2ead --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/supported_renderers.d.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RendererFactory } from '../types'; + +export const renderFunctions: RendererFactory[]; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/supported_renderers.js b/x-pack/legacy/plugins/canvas/external_runtime/supported_renderers.js new file mode 100644 index 0000000000000..81078fe027b6f --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/supported_renderers.js @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { debug } from '../canvas_plugin_src/renderers/debug'; +import { error } from '../canvas_plugin_src/renderers/error'; +import { image } from '../canvas_plugin_src/renderers/image'; +import { repeatImage } from '../canvas_plugin_src/renderers/repeat_image'; +import { revealImage } from '../canvas_plugin_src/renderers/reveal_image'; +import { markdown } from '../canvas_plugin_src/renderers/markdown'; +import { metric } from '../canvas_plugin_src/renderers/metric'; +import { pie } from '../canvas_plugin_src/renderers/pie'; +import { plot } from '../canvas_plugin_src/renderers/plot'; +import { progress } from '../canvas_plugin_src/renderers/progress'; +import { shape } from '../canvas_plugin_src/renderers/shape'; +import { table } from '../canvas_plugin_src/renderers/table'; +import { text } from '../canvas_plugin_src/renderers/text'; + +export const renderFunctions = [ + debug, + error, + image, + repeatImage, + revealImage, + markdown, + metric, + pie, + plot, + progress, + shape, + table, + text, +]; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/test/hello.json b/x-pack/legacy/plugins/canvas/external_runtime/test/hello.json new file mode 100644 index 0000000000000..7357c363b3792 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/test/hello.json @@ -0,0 +1,88 @@ +{ + "pages": [ + { + "id": "page-7186b301-f8a7-4c65-8b89-38d68d31cfc4", + "style": { "background": "#777777" }, + "transition": {}, + "groups": [], + "elements": [ + { + "id": "element-e48b0339-4829-44eb-9f0c-8787e693a085", + "position": { + "left": -1, + "top": 264.78145695364236, + "width": 1082, + "height": 205.37748344370857, + "angle": 0, + "parent": null + }, + "expressionRenderable": { + "state": "ready", + "value": { + "type": "render", + "as": "markdown", + "value": { + "content": "# Hello, Canvas.", + "font": { + "type": "style", + "spec": { + "fontFamily": "'Open Sans', Helvetica, Arial, sans-serif", + "fontWeight": "normal", + "fontStyle": "normal", + "textDecoration": "none", + "textAlign": "left", + "fontSize": "14px", + "lineHeight": "1" + }, + "css": "font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:left;font-size:14px;line-height:1" + } + }, + "css": ".canvasRenderEl h1 {\n font-size: 150px;\n text-align: center;\n color: #d3d3d3;\n}", + "containerStyle": { "type": "containerStyle", "overflow": "hidden" } + }, + "error": null + } + } + ] + } + ], + "name": "My Canvas Workpad", + "id": "workpad-bde237d4-12d7-4c3b-9ce8-8646d999618e", + "width": 1080, + "height": 720, + "css": ".canvasPage {\n\n}", + "page": 0, + "colors": [ + "#37988d", + "#c19628", + "#b83c6f", + "#3f9939", + "#1785b0", + "#ca5f35", + "#45bdb0", + "#f2bc33", + "#e74b8b", + "#4fbf48", + "#1ea6dc", + "#fd7643", + "#72cec3", + "#f5cc5d", + "#ec77a8", + "#7acf74", + "#4cbce4", + "#fd986f", + "#a1ded7", + "#f8dd91", + "#f2a4c5", + "#a6dfa2", + "#86d2ed", + "#fdba9f", + "#000000", + "#444444", + "#777777", + "#BBBBBB", + "#FFFFFF", + "rgba(255,255,255,0)" + ], + "isWriteable": true +} diff --git a/x-pack/legacy/plugins/canvas/external_runtime/test/index.ts b/x-pack/legacy/plugins/canvas/external_runtime/test/index.ts new file mode 100644 index 0000000000000..8c5c1dc88fb1d --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/test/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import hello from './hello.json'; +import austin from './austin.json'; +import test from './test.json'; + +export * from './utils'; +export type SnapshotNames = 'hello' | 'austin' | 'test'; +export const snapshots = { hello, austin, test }; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/test/utils.ts b/x-pack/legacy/plugins/canvas/external_runtime/test/utils.ts new file mode 100644 index 0000000000000..2e7bc4b262b52 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/test/utils.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ReactWrapper } from 'enzyme'; +import { Component } from 'react'; +import { setTimeout } from 'timers'; + +export const tick = (ms = 0) => + new Promise(resolve => { + setTimeout(resolve, ms); + }); + +export const takeMountedSnapshot = (mountedComponent: ReactWrapper<{}, {}, Component>) => { + const html = mountedComponent.html(); + const template = document.createElement('template'); + template.innerHTML = html; + return template.content.firstChild; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/types.ts b/x-pack/legacy/plugins/canvas/external_runtime/types.ts index ad719b5cee9ab..79db5443a9a2f 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/types.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/types.ts @@ -4,18 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RefObject } from 'react'; // @ts-ignore Unlinked Webpack Type import ContainerStyle from 'types/interpreter'; -import { CSSProperties } from 'react'; import { SavedObject, SavedObjectAttributes } from 'src/core/server'; -import { CanvasElement, CanvasPage, CanvasWorkpad } from '../types'; +import { ElementPosition, CanvasPage, CanvasWorkpad, RendererSpec } from '../types'; /** * Represents a Canvas Element whose expression has been evaluated and now * exists in a transient, ready-to-render state. */ -export interface CanvasRenderedElement extends CanvasElement { +export interface CanvasRenderedElement { + id: string; + position: ElementPosition; expressionRenderable: CanvasRenderable; } @@ -23,7 +25,7 @@ export interface CanvasRenderedElement extends CanvasElement { * Represents a Page within a Canvas Workpad that is made up of ready-to- * render Elements. */ -export interface CanvasRenderedPage extends CanvasPage { +export interface CanvasRenderedPage extends Omit<Omit<CanvasPage, 'elements'>, 'groups'> { elements: CanvasRenderedElement[]; groups: CanvasRenderedElement[][]; } @@ -31,7 +33,7 @@ export interface CanvasRenderedPage extends CanvasPage { /** * A Canvas Workpad made up of ready-to-render Elements. */ -export interface CanvasRenderedWorkpad extends CanvasWorkpad { +export interface CanvasRenderedWorkpad extends Omit<CanvasWorkpad, 'pages'> { pages: CanvasRenderedPage[]; } @@ -45,13 +47,49 @@ export type CanvasRenderedWorkpadSavedObject = SavedObject< * upon a stage. */ export interface CanvasRenderable { - error: string; + error: string | null; state: 'ready' | 'error'; value: { as: string; containerStyle: ContainerStyle; - css: CSSProperties; + css: string; type: 'render'; value: any; }; } + +export interface ExternalEmbedState { + renderers: { [key: string]: RendererSpec }; + workpad: CanvasRenderedWorkpad | null; + stage: Stage; + footer: { + isScrubberVisible: boolean; + }; + settings: Settings; + refs: Refs; +} + +export interface Stage { + page: number; + height: number; + width: number; +} + +export interface Refs { + stage: RefObject<HTMLDivElement>; +} + +export interface Settings { + autoplay: AutoplaySettings; + toolbar: ToolbarSettings; +} + +export interface AutoplaySettings { + isEnabled: boolean; + interval: string; + isAnimated: boolean; +} + +export interface ToolbarSettings { + isAutohide: boolean; +} diff --git a/x-pack/legacy/plugins/canvas/external_runtime/webpack.config.js b/x-pack/legacy/plugins/canvas/external_runtime/webpack.config.js index ba59ba7624ed0..c75f436606e50 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/external_runtime/webpack.config.js @@ -27,10 +27,6 @@ module.exports = { resolve: { alias: { ui: path.resolve(KIBANA_ROOT, 'src/legacy/ui/public'), - 'data/interpreter': path.resolve( - KIBANA_ROOT, - 'src/plugins/data/public/expressions/interpreter' - ), 'kbn/interpreter': path.resolve(KIBANA_ROOT, 'packages/kbn-interpreter/target/common'), 'types/interpreter': path.resolve( KIBANA_ROOT, @@ -166,10 +162,6 @@ module.exports = { require.resolve('@elastic/eui/es/components/drag_and_drop'), require.resolve('@elastic/eui/packages/react-datepicker'), require.resolve('highlight.js'), - /canvas_plugin_src\/renderers\/advanced_filter/, - /canvas_plugin_src\/renderers\/dropdown_filter/, - /canvas_plugin_src\/renderers\/embeddable.tsx/, - /canvas_plugin_src\/renderers\/time_filter/, ], use: 'null-loader', }, diff --git a/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts b/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts index bcf3b8b7279fc..40df324f25a8d 100644 --- a/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts +++ b/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts @@ -422,9 +422,9 @@ export function getAutoplay(state: State): State['transient']['autoplay'] { return get(state, 'transient.autoplay'); } -export function getRenderedWorkpad(state) { +export function getRenderedWorkpad(state: State) { const currentPages = getPages(state); - const args = get(state, ['transient', 'resolvedArgs']); + const args = get<State, State['transient']['resolvedArgs']>(state, ['transient', 'resolvedArgs']); const renderedPages = currentPages.map(page => { const { elements, ...rest } = page; return { diff --git a/x-pack/legacy/plugins/canvas/scripts/jest.js b/x-pack/legacy/plugins/canvas/scripts/jest.js index 12a2b9921455b..cce1b8d355846 100644 --- a/x-pack/legacy/plugins/canvas/scripts/jest.js +++ b/x-pack/legacy/plugins/canvas/scripts/jest.js @@ -34,6 +34,8 @@ run( `!${path}/**/build/**`, '--collectCoverageFrom', // Ignore coverage on test files `!${path}/**/__tests__/**/*`, + '--collectCoverageFrom', // Ignore coverage on example files + `!${path}/**/__examples__/**/*`, '--collectCoverageFrom', // Include JS files `${path}/**/*.js`, '--collectCoverageFrom', // Include TS/X files @@ -41,26 +43,24 @@ run( '--coverageDirectory', // Output to canvas/coverage 'legacy/plugins/canvas/coverage', ]; - } else { - // Mitigation for https://github.com/facebook/jest/issues/7267 - if (all || storybook || update) { - options = options.concat(['--no-cache', '--no-watchman']); - } + } + // Mitigation for https://github.com/facebook/jest/issues/7267 + if (all || storybook) { + options = options.concat(['--no-cache', '--no-watchman']); + } - if (all) { - log.info('Running all available tests. This will take a while...'); - } else if (storybook || update) { - path = 'legacy/plugins/canvas/.storybook'; + if (all) { + log.info('Running all available tests. This will take a while...'); + } else if (storybook) { + path = 'legacy/plugins/canvas/.storybook'; + log.info('Running Storybook Snapshot tests...'); + } else { + log.info('Running tests. This does not include Storybook Snapshots...'); + } - if (update) { - log.info('Updating Storybook Snapshot tests...'); - options.push('-u'); - } else { - log.info('Running Storybook Snapshot tests...'); - } - } else { - log.info('Running tests. This does not include Storybook Snapshots...'); - } + if (update) { + log.info('Updating any Jest snapshots...'); + options.push('-u'); } runXPackScript('jest', [path].concat(options)); diff --git a/x-pack/package.json b/x-pack/package.json index fae171376215b..f4740449eb2cc 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -141,6 +141,7 @@ "hapi": "^17.5.3", "jest": "^24.8.0", "jest-cli": "^24.8.0", + "jest-environment-jsdom-fourteen": "^0.1.0", "jest-styled-components": "^6.2.2", "jsdom": "^12.0.0", "madge": "3.4.4", diff --git a/yarn.lock b/yarn.lock index 8e7afe3ef9311..79456a1dea004 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4430,6 +4430,14 @@ acorn-globals@^4.0.0, acorn-globals@^4.1.0: acorn "^6.0.1" acorn-walk "^6.0.1" +acorn-globals@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + acorn-jsx@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" @@ -4467,7 +4475,7 @@ acorn@^6.0.1, acorn@^6.0.7: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== -acorn@^6.0.5, acorn@^6.2.1: +acorn@^6.0.4, acorn@^6.0.5, acorn@^6.2.1: version "6.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== @@ -9119,6 +9127,11 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" integrity sha1-uANhcMefB6kP8vFuIihAJ6JDhIs= +cssom@^0.3.4: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + "cssstyle@>= 0.2.37 < 0.3.0": version "0.2.37" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" @@ -9133,6 +9146,13 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" +cssstyle@^1.1.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" + csstype@^2.2.0: version "2.6.2" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.2.tgz#3043d5e065454579afc7478a18de41909c8a2f01" @@ -9494,6 +9514,15 @@ data-urls@^1.0.1: whatwg-mimetype "^2.1.0" whatwg-url "^7.0.0" +data-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + dataloader@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" @@ -16593,6 +16622,15 @@ jest-each@^24.8.0: jest-util "^24.8.0" pretty-format "^24.8.0" +jest-environment-jsdom-fourteen@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom-fourteen/-/jest-environment-jsdom-fourteen-0.1.0.tgz#aad6393a9d4b565b69a609109bf469f62bf18ccc" + integrity sha512-4vtoRMg7jAstitRzL4nbw83VmGH8Rs13wrND3Ud2o1fczDhMUF32iIrNKwYGgeOPUdfvZU4oy8Bbv+ni1fgVCA== + dependencies: + jest-mock "^24.5.0" + jest-util "^24.5.0" + jsdom "^14.0.0" + jest-environment-jsdom@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857" @@ -16756,6 +16794,13 @@ jest-message-util@^24.9.0: slash "^2.0.0" stack-utils "^1.0.1" +jest-mock@^24.5.0, jest-mock@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" + integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== + dependencies: + "@jest/types" "^24.9.0" + jest-mock@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56" @@ -16763,13 +16808,6 @@ jest-mock@^24.8.0: dependencies: "@jest/types" "^24.8.0" -jest-mock@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" - integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== - dependencies: - "@jest/types" "^24.9.0" - jest-pnp-resolver@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" @@ -16936,16 +16974,16 @@ jest-styled-components@^6.2.2: dependencies: css "^2.2.4" -jest-util@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1" - integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA== +jest-util@^24.5.0, jest-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" + integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== dependencies: - "@jest/console" "^24.7.1" - "@jest/fake-timers" "^24.8.0" - "@jest/source-map" "^24.3.0" - "@jest/test-result" "^24.8.0" - "@jest/types" "^24.8.0" + "@jest/console" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/source-map" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" callsites "^3.0.0" chalk "^2.0.1" graceful-fs "^4.1.15" @@ -16954,16 +16992,16 @@ jest-util@^24.8.0: slash "^2.0.0" source-map "^0.6.0" -jest-util@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" - integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== +jest-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1" + integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA== dependencies: - "@jest/console" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/source-map" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" + "@jest/console" "^24.7.1" + "@jest/fake-timers" "^24.8.0" + "@jest/source-map" "^24.3.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" callsites "^3.0.0" chalk "^2.0.1" graceful-fs "^4.1.15" @@ -17185,6 +17223,38 @@ jsdom@^12.0.0: ws "^6.0.0" xml-name-validator "^3.0.0" +jsdom@^14.0.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-14.1.0.tgz#916463b6094956b0a6c1782c94e380cd30e1981b" + integrity sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng== + dependencies: + abab "^2.0.0" + acorn "^6.0.4" + acorn-globals "^4.3.0" + array-equal "^1.0.0" + cssom "^0.3.4" + cssstyle "^1.1.1" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.0" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.1.3" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.5" + saxes "^3.1.9" + symbol-tree "^3.2.2" + tough-cookie "^2.5.0" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^6.1.2" + xml-name-validator "^3.0.0" + jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -20434,6 +20504,11 @@ nwsapi@^2.0.8: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.9.tgz#77ac0cdfdcad52b6a1151a84e73254edc33ed016" integrity sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ== +nwsapi@^2.1.3: + version "2.1.4" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.4.tgz#e006a878db23636f8e8a67d33ca0e4edf61a842f" + integrity sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw== + nyc@^14.1.1: version "14.1.1" resolved "https://registry.yarnpkg.com/nyc/-/nyc-14.1.1.tgz#151d64a6a9f9f5908a1b73233931e4a0a3075eeb" @@ -22279,6 +22354,11 @@ psl@^1.1.24: resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== +psl@^1.1.28: + version "1.4.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" + integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw== + public-encrypt@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" @@ -22456,7 +22536,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@2.x.x, punycode@^2.1.0: +punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -25022,6 +25102,13 @@ sax@>=0.6.0, sax@^1.2.1, sax@^1.2.4, sax@~1.2.1, sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +saxes@^3.1.9: + version "3.1.11" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" + integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== + dependencies: + xmlchars "^2.1.1" + scheduler@^0.13.2, scheduler@^0.13.3, scheduler@^0.13.6: version "0.13.6" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" @@ -27420,6 +27507,14 @@ tough-cookie@>=2.3.3, tough-cookie@^2.0.0, tough-cookie@^2.3.3, tough-cookie@^2. psl "^1.1.24" punycode "^1.4.1" +tough-cookie@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tough-cookie@~2.3.0, tough-cookie@~2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" @@ -29463,6 +29558,15 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" +w3c-xmlserializer@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" + integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== + dependencies: + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + wait-for-expect@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.1.0.tgz#6607375c3f79d32add35cd2c87ce13f351a3d453" @@ -29807,6 +29911,13 @@ whatwg-encoding@^1.0.4: dependencies: iconv-lite "0.4.23" +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + whatwg-fetch@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" @@ -29822,6 +29933,11 @@ whatwg-mimetype@^2.1.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171" integrity sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw== +whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + whatwg-url@^6.3.0: version "6.4.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" @@ -30149,7 +30265,7 @@ ws@^6.1.0: dependencies: async-limiter "~1.0.0" -ws@^6.2.1: +ws@^6.1.2, ws@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== @@ -30248,6 +30364,11 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" integrity sha1-UZy0ymhtAFqEINNJbz8MruzKWA8= +xmlchars@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xmldom@0.1.27: version "0.1.27" resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"