diff --git a/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx new file mode 100644 index 0000000000..99db37d927 --- /dev/null +++ b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx @@ -0,0 +1,96 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { render, screen } from 'spec/helpers/testing-library'; +import { supersetTheme } from '@superset-ui/core'; +import BasicErrorAlert from './BasicErrorAlert'; +import { ErrorLevel } from './types'; + +const mockedProps = { + body: 'Error body', + level: 'warning' as ErrorLevel, + title: 'Error title', +}; + +jest.mock('../Icon', () => ({ + __esModule: true, + default: ({ name }: { name: string }) => ( +
+ ), +})); + +test('should render', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); +}); + +test('should render warning icon', () => { + render(); + expect(screen.getByTestId('icon')).toBeInTheDocument(); + expect(screen.getByTestId('icon')).toHaveAttribute( + 'data-name', + 'warning-solid', + ); +}); + +test('should render error icon', () => { + const errorProps = { + ...mockedProps, + level: 'error' as ErrorLevel, + }; + render(); + expect(screen.getByTestId('icon')).toBeInTheDocument(); + expect(screen.getByTestId('icon')).toHaveAttribute( + 'data-name', + 'error-solid', + ); +}); + +test('should render the error title', () => { + render(); + expect(screen.getByText('Error title')).toBeInTheDocument(); +}); + +test('should render the error body', () => { + render(); + expect(screen.getByText('Error body')).toBeInTheDocument(); +}); + +test('should render with warning theme', () => { + render(); + expect(screen.getByRole('alert')).toHaveStyle( + ` + backgroundColor: ${supersetTheme.colors.warning.light2}; + `, + ); +}); + +test('should render with error theme', () => { + const errorProps = { + ...mockedProps, + level: 'error' as ErrorLevel, + }; + render(); + expect(screen.getByRole('alert')).toHaveStyle( + ` + backgroundColor: ${supersetTheme.colors.error.light2}; + `, + ); +}); diff --git a/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.tsx b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.tsx index b66bebf3c2..6c460096ad 100644 --- a/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.tsx +++ b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.tsx @@ -55,7 +55,7 @@ export default function BasicErrorAlert({ title, }: BasicErrorAlertProps) { return ( - + { + const { container } = render(); + expect(container).toBeInTheDocument(); +}); + +test('should render the error message', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText('Error message')).toBeInTheDocument(); +}); + +test('should render the issue codes', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText(/This may be triggered by:/)).toBeInTheDocument(); + expect(screen.getByText(/Issue code message A/)).toBeInTheDocument(); + expect(screen.getByText(/Issue code message B/)).toBeInTheDocument(); +}); + +test('should render the engine name', () => { + render(); + expect(screen.getByText(/Engine name/)).toBeInTheDocument(); +}); + +test('should render the owners', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect( + screen.getByText('Please reach out to the Chart Owners for assistance.'), + ).toBeInTheDocument(); + expect( + screen.getByText('Chart Owners: Owner A, Owner B'), + ).toBeInTheDocument(); +}); + +test('should NOT render the owners', () => { + const noVisualizationProps = { + ...mockedProps, + source: 'sqllab' as ErrorSource, + }; + render(, { + useRedux: true, + }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect( + screen.queryByText('Chart Owners: Owner A, Owner B'), + ).not.toBeInTheDocument(); +}); diff --git a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx index c552d3a360..0454aca59e 100644 --- a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx @@ -46,7 +46,9 @@ function DatabaseErrorMessage({ {t('This may be triggered by:')}
{extra.issue_codes - .map(issueCode => ) + .map(issueCode => ( + + )) .reduce((prev, curr) => [prev,
, curr])}

{isVisualization && extra.owners && ( diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx new file mode 100644 index 0000000000..f9c54e9be7 --- /dev/null +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx @@ -0,0 +1,161 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from 'spec/helpers/testing-library'; +import { supersetTheme } from '@superset-ui/core'; +import ErrorAlert from './ErrorAlert'; +import { ErrorLevel, ErrorSource } from './types'; + +const mockedProps = { + body: 'Error body', + level: 'warning' as ErrorLevel, + copyText: 'Copy text', + subtitle: 'Error subtitle', + title: 'Error title', + source: 'dashboard' as ErrorSource, +}; + +jest.mock('../Icon', () => ({ + __esModule: true, + default: ({ name }: { name: string }) => ( +
+ ), +})); + +test('should render', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); +}); + +test('should render warning icon', () => { + render(); + expect(screen.getByTestId('icon')).toBeInTheDocument(); + expect(screen.getByTestId('icon')).toHaveAttribute( + 'data-name', + 'warning-solid', + ); +}); + +test('should render error icon', () => { + const errorProps = { + ...mockedProps, + level: 'error' as ErrorLevel, + }; + render(); + expect(screen.getByTestId('icon')).toBeInTheDocument(); + expect(screen.getByTestId('icon')).toHaveAttribute( + 'data-name', + 'error-solid', + ); +}); + +test('should render the error title', () => { + const titleProps = { + ...mockedProps, + source: 'explore' as ErrorSource, + }; + render(); + expect(screen.getByText('Error title')).toBeInTheDocument(); +}); + +test('should render the error subtitle', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText('Error subtitle')).toBeInTheDocument(); +}); + +test('should render the error body', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText('Error body')).toBeInTheDocument(); +}); + +test('should render the See more button', () => { + const seemoreProps = { + ...mockedProps, + source: 'explore' as ErrorSource, + }; + render(); + expect(screen.getByRole('button')).toBeInTheDocument(); + expect(screen.getByText('See more')).toBeInTheDocument(); +}); + +test('should render the modal', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByText('Close')).toBeInTheDocument(); +}); + +test('should NOT render the modal', () => { + const expandableProps = { + ...mockedProps, + source: 'explore' as ErrorSource, + }; + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); +}); + +test('should render the See less button', () => { + const expandableProps = { + ...mockedProps, + source: 'explore' as ErrorSource, + }; + render(); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText('See less')).toBeInTheDocument(); + expect(screen.queryByText('See more')).not.toBeInTheDocument(); +}); + +test('should render the Copy button', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText('Copy message')).toBeInTheDocument(); +}); + +test('should render with warning theme', () => { + render(); + expect(screen.getByRole('alert')).toHaveStyle( + ` + backgroundColor: ${supersetTheme.colors.warning.light2}; + `, + ); +}); + +test('should render with error theme', () => { + const errorProps = { + ...mockedProps, + level: 'error' as ErrorLevel, + }; + render(); + expect(screen.getByRole('alert')).toHaveStyle( + ` + backgroundColor: ${supersetTheme.colors.error.light2}; + `, + ); +}); diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx index ce5ebe6926..d78f616d5b 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx @@ -103,7 +103,7 @@ export default function ErrorAlert({ const isExpandable = ['explore', 'sqllab'].includes(source); return ( - +
{ + const { container } = render(); + expect(container).toBeInTheDocument(); +}); + +test('should render the stacktrace', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText('Stacktrace')).toBeInTheDocument(); +}); + +test('should render the link', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + const link = screen.getByRole('link'); + expect(link).toHaveTextContent('(Request Access)'); + expect(link).toHaveAttribute('href', mockedProps.link); +}); diff --git a/superset-frontend/src/components/ErrorMessage/IssueCode.test.tsx b/superset-frontend/src/components/ErrorMessage/IssueCode.test.tsx new file mode 100644 index 0000000000..e4be3bfa70 --- /dev/null +++ b/superset-frontend/src/components/ErrorMessage/IssueCode.test.tsx @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { render, screen } from 'spec/helpers/testing-library'; +import IssueCode from './IssueCode'; + +const mockedProps = { + code: 1, + message: 'Error message', +}; + +test('should render', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); +}); + +test('should render the message', () => { + render(); + expect(screen.getByText('Error message')).toBeInTheDocument(); +}); + +test('should render the link', () => { + render(); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute( + 'href', + `https://superset.apache.org/docs/miscellaneous/issue-codes#issue-${mockedProps.code}`, + ); +}); diff --git a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx new file mode 100644 index 0000000000..d4664d53c6 --- /dev/null +++ b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx @@ -0,0 +1,82 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { render, screen } from 'spec/helpers/testing-library'; +import ParameterErrorMessage from './ParameterErrorMessage'; +import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types'; + +const mockedProps = { + error: { + error_type: ErrorTypeEnum.MISSING_TEMPLATE_PARAMS_ERROR, + extra: { + template_parameters: { state: 'CA', country: 'ITA' }, + undefined_parameters: ['stat', 'count'], + issue_codes: [ + { + code: 1, + message: 'Issue code message A', + }, + { + code: 2, + message: 'Issue code message B', + }, + ], + }, + level: 'error' as ErrorLevel, + message: 'Error message', + }, + source: 'dashboard' as ErrorSource, +}; + +test('should render', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); +}); + +test('should render the default title', () => { + render(); + expect(screen.getByText('Parameter error')).toBeInTheDocument(); +}); + +test('should render the error message', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText('Error message')).toBeInTheDocument(); +}); + +test('should render the issue codes', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText(/This may be triggered by:/)).toBeInTheDocument(); + expect(screen.getByText(/Issue code message A/)).toBeInTheDocument(); + expect(screen.getByText(/Issue code message B/)).toBeInTheDocument(); +}); + +test('should render the suggestions', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText(/Did you mean:/)).toBeInTheDocument(); + expect(screen.getByText('"state" instead of "stat?"')).toBeInTheDocument(); + expect(screen.getByText('"country" instead of "count?"')).toBeInTheDocument(); +}); diff --git a/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.test.tsx new file mode 100644 index 0000000000..e41308f538 --- /dev/null +++ b/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.test.tsx @@ -0,0 +1,104 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { render, screen } from 'spec/helpers/testing-library'; +import TimeoutErrorMessage from './TimeoutErrorMessage'; +import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types'; + +const mockedProps = { + error: { + error_type: ErrorTypeEnum.FRONTEND_TIMEOUT_ERROR, + extra: { + issue_codes: [ + { + code: 1, + message: 'Issue code message A', + }, + { + code: 2, + message: 'Issue code message B', + }, + ], + owners: ['Owner A', 'Owner B'], + timeout: 30, + }, + level: 'error' as ErrorLevel, + message: 'Error message', + }, + source: 'dashboard' as ErrorSource, +}; + +test('should render', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); +}); + +test('should render the default title', () => { + render(); + expect(screen.getByText('Timeout error')).toBeInTheDocument(); +}); + +test('should render the issue codes', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect(screen.getByText(/This may be triggered by:/)).toBeInTheDocument(); + expect(screen.getByText(/Issue code message A/)).toBeInTheDocument(); + expect(screen.getByText(/Issue code message B/)).toBeInTheDocument(); +}); + +test('should render the owners', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect( + screen.getByText('Please reach out to the Chart Owners for assistance.'), + ).toBeInTheDocument(); + expect( + screen.getByText('Chart Owners: Owner A, Owner B'), + ).toBeInTheDocument(); +}); + +test('should NOT render the owners', () => { + const noVisualizationProps = { + ...mockedProps, + source: 'sqllab' as ErrorSource, + }; + render(, { + useRedux: true, + }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect( + screen.queryByText('Chart Owners: Owner A, Owner B'), + ).not.toBeInTheDocument(); +}); + +test('should render the timeout message', () => { + render(, { useRedux: true }); + const button = screen.getByText('See more'); + userEvent.click(button); + expect( + screen.getByText( + /We’re having trouble loading this visualization. Queries are set to timeout after 30 seconds./, + ), + ).toBeInTheDocument(); +}); diff --git a/superset-frontend/spec/javascripts/components/ErrorMessage/getErrorMessageComponentRegistry_spec.tsx b/superset-frontend/src/components/ErrorMessage/getErrorMessageComponentRegistry.test.tsx similarity index 59% rename from superset-frontend/spec/javascripts/components/ErrorMessage/getErrorMessageComponentRegistry_spec.tsx rename to superset-frontend/src/components/ErrorMessage/getErrorMessageComponentRegistry.test.tsx index ebc278b6bd..6cff2de4d4 100644 --- a/superset-frontend/spec/javascripts/components/ErrorMessage/getErrorMessageComponentRegistry_spec.tsx +++ b/superset-frontend/src/components/ErrorMessage/getErrorMessageComponentRegistry.test.tsx @@ -30,37 +30,35 @@ const OVERRIDE_ERROR_MESSAGE_COMPONENT = (_: ErrorMessageComponentProps) => (
Custom error
); -describe('getErrorMessageComponentRegistry', () => { - it('returns undefined for a non existent key', () => { - expect(getErrorMessageComponentRegistry().get('INVALID_KEY')).toEqual( - undefined, - ); - }); - - it('returns a component for a set key', () => { - getErrorMessageComponentRegistry().registerValue( - 'VALID_KEY', - ERROR_MESSAGE_COMPONENT, - ); - - expect(getErrorMessageComponentRegistry().get('VALID_KEY')).toEqual( - ERROR_MESSAGE_COMPONENT, - ); - }); - - it('returns the correct component for an overridden key', () => { - getErrorMessageComponentRegistry().registerValue( - 'OVERRIDE_KEY', - ERROR_MESSAGE_COMPONENT, - ); - - getErrorMessageComponentRegistry().registerValue( - 'OVERRIDE_KEY', - OVERRIDE_ERROR_MESSAGE_COMPONENT, - ); - - expect(getErrorMessageComponentRegistry().get('OVERRIDE_KEY')).toEqual( - OVERRIDE_ERROR_MESSAGE_COMPONENT, - ); - }); +test('should return undefined for a non existent key', () => { + expect(getErrorMessageComponentRegistry().get('INVALID_KEY')).toEqual( + undefined, + ); +}); + +test('should return a component for a set key', () => { + getErrorMessageComponentRegistry().registerValue( + 'VALID_KEY', + ERROR_MESSAGE_COMPONENT, + ); + + expect(getErrorMessageComponentRegistry().get('VALID_KEY')).toEqual( + ERROR_MESSAGE_COMPONENT, + ); +}); + +test('should return the correct component for an overridden key', () => { + getErrorMessageComponentRegistry().registerValue( + 'OVERRIDE_KEY', + ERROR_MESSAGE_COMPONENT, + ); + + getErrorMessageComponentRegistry().registerValue( + 'OVERRIDE_KEY', + OVERRIDE_ERROR_MESSAGE_COMPONENT, + ); + + expect(getErrorMessageComponentRegistry().get('OVERRIDE_KEY')).toEqual( + OVERRIDE_ERROR_MESSAGE_COMPONENT, + ); });