Skip to content

Commit

Permalink
Merge branch 'master' into usage_collection/schema/kibana_usage_colle…
Browse files Browse the repository at this point in the history
…ction
  • Loading branch information
elasticmachine authored Sep 23, 2020
2 parents e15ebab + e0f9323 commit 68e4afc
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,36 @@
* under the License.
*/
import React from 'react';
import { wait } from '@testing-library/dom';
import { cleanup, render } from '@testing-library/react/pure';
import { ErrorEmbeddable } from './error_embeddable';
import { EmbeddableRoot } from './embeddable_root';
import { mount } from 'enzyme';

afterEach(cleanup);

test('ErrorEmbeddable renders an embeddable', async () => {
const embeddable = new ErrorEmbeddable('some error occurred', { id: '123', title: 'Error' });
const component = mount(<EmbeddableRoot embeddable={embeddable} />);
expect(
component.getDOMNode().querySelectorAll('[data-test-subj="embeddableStackError"]').length
).toBe(1);
expect(
component.getDOMNode().querySelectorAll('[data-test-subj="errorMessageMarkdown"]').length
).toBe(1);
expect(
component
.getDOMNode()
.querySelectorAll('[data-test-subj="errorMessageMarkdown"]')[0]
.innerHTML.includes('some error occurred')
).toBe(true);
const { getByTestId, getByText } = render(<EmbeddableRoot embeddable={embeddable} />);

expect(getByTestId('embeddableStackError')).toBeVisible();
await wait(() => getByTestId('errorMessageMarkdown')); // wait for lazy markdown component
expect(getByText(/some error occurred/i)).toBeVisible();
});

test('ErrorEmbeddable renders an embeddable with markdown message', async () => {
const error = '[some link](http://localhost:5601/takeMeThere)';
const embeddable = new ErrorEmbeddable(error, { id: '123', title: 'Error' });
const component = mount(<EmbeddableRoot embeddable={embeddable} />);
expect(
component.getDOMNode().querySelectorAll('[data-test-subj="embeddableStackError"]').length
).toBe(1);
expect(
component.getDOMNode().querySelectorAll('[data-test-subj="errorMessageMarkdown"]').length
).toBe(1);
expect(
component
.getDOMNode()
.querySelectorAll('[data-test-subj="errorMessageMarkdown"]')[0]
.innerHTML.includes(
'<a href="http://localhost:5601/takeMeThere" target="_blank" rel="noopener noreferrer">some link</a>'
)
).toBe(true);
const { getByTestId, getByText } = render(<EmbeddableRoot embeddable={embeddable} />);

expect(getByTestId('embeddableStackError')).toBeVisible();
await wait(() => getByTestId('errorMessageMarkdown')); // wait for lazy markdown component
expect(getByText(/some link/i)).toMatchInlineSnapshot(`
<a
href="http://localhost:5601/takeMeThere"
rel="noopener noreferrer"
target="_blank"
>
some link
</a>
`);
});
4 changes: 4 additions & 0 deletions src/plugins/kibana_react/public/code_editor/code_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,7 @@ export class CodeEditor extends React.Component<Props, {}> {
}
};
}

// React.lazy requires default export
// eslint-disable-next-line import/no-default-export
export default CodeEditor;
18 changes: 15 additions & 3 deletions src/plugins/kibana_react/public/code_editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,23 @@
* under the License.
*/
import React from 'react';
import { EuiDelayRender, EuiLoadingContent } from '@elastic/eui';
import { useUiSetting } from '../ui_settings';
import { CodeEditor as BaseEditor, Props } from './code_editor';
import type { Props } from './code_editor';

const LazyBaseEditor = React.lazy(() => import('./code_editor'));

const Fallback = () => (
<EuiDelayRender>
<EuiLoadingContent lines={3} />
</EuiDelayRender>
);

export const CodeEditor: React.FunctionComponent<Props> = (props) => {
const darkMode = useUiSetting<boolean>('theme:darkMode');

return <BaseEditor {...props} useDarkTheme={darkMode} />;
return (
<React.Suspense fallback={<Fallback />}>
<LazyBaseEditor {...props} useDarkTheme={darkMode} />
</React.Suspense>
);
};
26 changes: 24 additions & 2 deletions src/plugins/kibana_react/public/markdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,27 @@
* under the License.
*/

export { MarkdownSimple } from './markdown_simple';
export { Markdown } from './markdown';
import React from 'react';
import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui';
import type { MarkdownSimpleProps } from './markdown_simple';
import type { MarkdownProps } from './markdown';

const Fallback = () => (
<EuiDelayRender>
<EuiLoadingContent lines={3} />
</EuiDelayRender>
);

const LazyMarkdownSimple = React.lazy(() => import('./markdown_simple'));
export const MarkdownSimple = (props: MarkdownSimpleProps) => (
<React.Suspense fallback={<Fallback />}>
<LazyMarkdownSimple {...props} />
</React.Suspense>
);

const LazyMarkdown = React.lazy(() => import('./markdown'));
export const Markdown = (props: MarkdownProps) => (
<React.Suspense fallback={<Fallback />}>
<LazyMarkdown {...props} />
</React.Suspense>
);
6 changes: 5 additions & 1 deletion src/plugins/kibana_react/public/markdown/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const markdownFactory = memoize(
}
);

interface MarkdownProps extends React.HTMLAttributes<HTMLDivElement> {
export interface MarkdownProps extends React.HTMLAttributes<HTMLDivElement> {
className?: string;
markdown?: string;
openLinksInNewTab?: boolean;
Expand Down Expand Up @@ -112,3 +112,7 @@ export class Markdown extends PureComponent<MarkdownProps> {
);
}
}

// Needed for React.lazy
// eslint-disable-next-line import/no-default-export
export default Markdown;
6 changes: 5 additions & 1 deletion src/plugins/kibana_react/public/markdown/markdown_simple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ const markdownRenderers = {
root: Fragment,
};

interface MarkdownSimpleProps {
export interface MarkdownSimpleProps {
children: string;
}

// Render markdown string into JSX inside of a Fragment.
export const MarkdownSimple = ({ children }: MarkdownSimpleProps) => (
<ReactMarkdown renderers={markdownRenderers}>{children}</ReactMarkdown>
);

// Needed for React.lazy
// eslint-disable-next-line import/no-default-export
export default MarkdownSimple;
Original file line number Diff line number Diff line change
Expand Up @@ -556,3 +556,6 @@ class TableListView extends React.Component<TableListViewProps, TableListViewSta
}

export { TableListView };

// eslint-disable-next-line import/no-default-export
export default TableListView;
20 changes: 9 additions & 11 deletions src/plugins/kibana_utils/public/history/redirect_when_missing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@
import React, { Fragment } from 'react';
import { History } from 'history';
import { i18n } from '@kbn/i18n';
import { EuiLoadingSpinner } from '@elastic/eui';
import ReactDOM from 'react-dom';
import ReactMarkdown from 'react-markdown';

import { ApplicationStart, HttpStart, ToastsSetup } from 'kibana/public';
import { SavedObjectNotFound } from '..';

const ReactMarkdown = React.lazy(() => import('react-markdown'));
const ErrorRenderer = (props: { children: string }) => (
<React.Suspense fallback={<EuiLoadingSpinner />}>
<ReactMarkdown renderers={{ root: Fragment }} {...props} />
</React.Suspense>
);

interface Mapping {
[key: string]: string | { app: string; path: string };
}
Expand Down Expand Up @@ -96,16 +103,7 @@ export function redirectWhenMissing({
defaultMessage: 'Saved object is missing',
}),
text: (element: HTMLElement) => {
ReactDOM.render(
<ReactMarkdown
renderers={{
root: Fragment,
}}
>
{error.message}
</ReactMarkdown>,
element
);
ReactDOM.render(<ErrorRenderer>{error.message}</ErrorRenderer>, element);
return () => ReactDOM.unmountComponentAtNode(element);
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
*/

import React from 'react';
import { render, mount } from 'enzyme';
import { wait } from '@testing-library/dom';
import { render, cleanup } from '@testing-library/react/pure';
import { MarkdownVisWrapper } from './markdown_vis_controller';

afterEach(cleanup);

describe('markdown vis controller', () => {
it('should set html from markdown params', () => {
it('should set html from markdown params', async () => {
const vis = {
params: {
openLinksInNewTab: false,
Expand All @@ -32,13 +35,22 @@ describe('markdown vis controller', () => {
},
};

const wrapper = render(
const { getByTestId, getByText } = render(
<MarkdownVisWrapper visParams={vis.params} renderComplete={jest.fn()} fireEvent={jest.fn()} />
);
expect(wrapper.find('a').text()).toBe('markdown');

await wait(() => getByTestId('markdownBody'));

expect(getByText('markdown')).toMatchInlineSnapshot(`
<a
href="http://daringfireball.net/projects/markdown"
>
markdown
</a>
`);
});

it('should not render the html', () => {
it('should not render the html', async () => {
const vis = {
params: {
openLinksInNewTab: false,
Expand All @@ -47,13 +59,20 @@ describe('markdown vis controller', () => {
},
};

const wrapper = render(
const { getByTestId, getByText } = render(
<MarkdownVisWrapper visParams={vis.params} renderComplete={jest.fn()} fireEvent={jest.fn()} />
);
expect(wrapper.text()).toBe('Testing <a>html</a>\n');

await wait(() => getByTestId('markdownBody'));

expect(getByText(/testing/i)).toMatchInlineSnapshot(`
<p>
Testing &lt;a&gt;html&lt;/a&gt;
</p>
`);
});

it('should update the HTML when render again with changed params', () => {
it('should update the HTML when render again with changed params', async () => {
const vis = {
params: {
openLinksInNewTab: false,
Expand All @@ -62,13 +81,20 @@ describe('markdown vis controller', () => {
},
};

const wrapper = mount(
const { getByTestId, getByText, rerender } = render(
<MarkdownVisWrapper visParams={vis.params} renderComplete={jest.fn()} fireEvent={jest.fn()} />
);
expect(wrapper.text().trim()).toBe('Initial');

await wait(() => getByTestId('markdownBody'));

expect(getByText(/initial/i)).toBeInTheDocument();

vis.params.markdown = 'Updated';
wrapper.setProps({ vis });
expect(wrapper.text().trim()).toBe('Updated');
rerender(
<MarkdownVisWrapper visParams={vis.params} renderComplete={jest.fn()} fireEvent={jest.fn()} />
);

expect(getByText(/Updated/i)).toBeInTheDocument();
});

describe('renderComplete', () => {
Expand All @@ -86,56 +112,71 @@ describe('markdown vis controller', () => {
renderComplete.mockClear();
});

it('should be called on initial rendering', () => {
mount(
it('should be called on initial rendering', async () => {
const { getByTestId } = render(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
);
expect(renderComplete.mock.calls.length).toBe(1);

await wait(() => getByTestId('markdownBody'));

expect(renderComplete).toHaveBeenCalledTimes(1);
});

it('should be called on successive render when params change', () => {
mount(
it('should be called on successive render when params change', async () => {
const { getByTestId, rerender } = render(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
);
expect(renderComplete.mock.calls.length).toBe(1);

await wait(() => getByTestId('markdownBody'));

expect(renderComplete).toHaveBeenCalledTimes(1);

renderComplete.mockClear();
vis.params.markdown = 'changed';
mount(

rerender(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
);
expect(renderComplete.mock.calls.length).toBe(1);

expect(renderComplete).toHaveBeenCalledTimes(1);
});

it('should be called on successive render even without data change', () => {
mount(
it('should be called on successive render even without data change', async () => {
const { getByTestId, rerender } = render(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
);
expect(renderComplete.mock.calls.length).toBe(1);

await wait(() => getByTestId('markdownBody'));

expect(renderComplete).toHaveBeenCalledTimes(1);

renderComplete.mockClear();
mount(

rerender(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
);
expect(renderComplete.mock.calls.length).toBe(1);

expect(renderComplete).toHaveBeenCalledTimes(1);
});
});
});
Loading

0 comments on commit 68e4afc

Please sign in to comment.