mirror of https://github.com/apache/superset.git
test: Add tests for Dashboard Header and HeaderActionsDropdown components (#13973)
* Add tests for HeaderActionsDropdown - WIP * Fix trigger node * Add types * Clean up * Add tests for Header * Delete obsolete tests * Add factory and clean up * Add opposite case * Fix file name * Include latest changes
This commit is contained in:
parent
a548b69201
commit
474f1e2044
|
@ -1,174 +0,0 @@
|
|||
/**
|
||||
* 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 { shallow } from 'enzyme';
|
||||
import { Menu, NoAnimationDropdown } from 'src/common/components';
|
||||
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
|
||||
import HeaderActionsDropdown from 'src/dashboard/components/HeaderActionsDropdown';
|
||||
import SaveModal from 'src/dashboard/components/SaveModal';
|
||||
import CssEditor from 'src/dashboard/components/CssEditor';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
|
||||
|
||||
fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
|
||||
|
||||
describe('HeaderActionsDropdown', () => {
|
||||
const props = {
|
||||
addSuccessToast: () => {},
|
||||
addDangerToast: () => {},
|
||||
customCss: '',
|
||||
dashboardId: 1,
|
||||
dashboardInfo: {},
|
||||
dashboardTitle: 'Title',
|
||||
editMode: false,
|
||||
expandedSlices: {},
|
||||
filters: {},
|
||||
forceRefreshAllCharts: () => {},
|
||||
hasUnsavedChanges: false,
|
||||
isLoading: false,
|
||||
layout: {},
|
||||
onChange: () => {},
|
||||
onSave: () => {},
|
||||
refreshFrequency: 200,
|
||||
setRefreshFrequency: () => {},
|
||||
shouldPersistRefreshFrequency: true,
|
||||
showPropertiesModal: () => {},
|
||||
startPeriodicRender: () => {},
|
||||
updateCss: () => {},
|
||||
userCanEdit: false,
|
||||
userCanSave: false,
|
||||
lastModifiedTime: 0,
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
const wrapper = shallow(
|
||||
<HeaderActionsDropdown {...props} {...overrideProps} />,
|
||||
);
|
||||
const menu = shallow(
|
||||
<div>{wrapper.find(NoAnimationDropdown).props().overlay}</div>,
|
||||
);
|
||||
return { wrapper, menu };
|
||||
}
|
||||
|
||||
describe('readonly-user', () => {
|
||||
const overrideProps = { userCanSave: false, userCanShare: false };
|
||||
|
||||
it('should render the DropdownButton', () => {
|
||||
const { wrapper } = setup(overrideProps);
|
||||
expect(wrapper.find(NoAnimationDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should not render the SaveModal', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(SaveModal)).not.toExist();
|
||||
});
|
||||
|
||||
it('should render available Menu items', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(Menu.Item)).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('should render the RefreshIntervalModal', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(RefreshIntervalModal)).toExist();
|
||||
});
|
||||
|
||||
it('should not render the ShareMenuItems', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(ShareMenuItems)).not.toExist();
|
||||
});
|
||||
|
||||
it('should not render the CssEditor', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(CssEditor)).not.toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('write-user', () => {
|
||||
const overrideProps = { userCanSave: true, userCanShare: true };
|
||||
|
||||
it('should render the DropdownButton', () => {
|
||||
const { wrapper } = setup(overrideProps);
|
||||
expect(wrapper.find(NoAnimationDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should render the SaveModal', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(SaveModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render available Menu items', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(Menu.Item)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('should render the RefreshIntervalModal', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(RefreshIntervalModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render the ShareMenuItems', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(ShareMenuItems)).toExist();
|
||||
});
|
||||
|
||||
it('should not render the CssEditor', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(CssEditor)).not.toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('write-user-with-edit-mode', () => {
|
||||
const overrideProps = {
|
||||
userCanSave: true,
|
||||
editMode: true,
|
||||
userCanShare: true,
|
||||
};
|
||||
|
||||
it('should render the DropdownButton', () => {
|
||||
const { wrapper } = setup(overrideProps);
|
||||
expect(wrapper.find(NoAnimationDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should render the SaveModal', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(SaveModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render available MenuItems', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(Menu.Item)).toHaveLength(6);
|
||||
});
|
||||
|
||||
it('should render the RefreshIntervalModal', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(RefreshIntervalModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render the ShareMenuItems', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(ShareMenuItems)).toExist();
|
||||
});
|
||||
|
||||
it('should render the CssEditor', () => {
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(CssEditor)).toExist();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,244 +0,0 @@
|
|||
/**
|
||||
* 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 { styledMount as mount } from 'spec/helpers/theming';
|
||||
import Header from 'src/dashboard/components/Header';
|
||||
import EditableTitle from 'src/components/EditableTitle';
|
||||
import FaveStar from 'src/components/FaveStar';
|
||||
import PublishedStatus from 'src/dashboard/components/PublishedStatus';
|
||||
import HeaderActionsDropdown from 'src/dashboard/components/HeaderActionsDropdown';
|
||||
import Button from 'src/components/Button';
|
||||
import UndoRedoKeyListeners from 'src/dashboard/components/UndoRedoKeyListeners';
|
||||
|
||||
describe('Header', () => {
|
||||
const props = {
|
||||
addSuccessToast: () => {},
|
||||
addDangerToast: () => {},
|
||||
addWarningToast: () => {},
|
||||
dashboardInfo: {
|
||||
id: 1,
|
||||
dash_edit_perm: true,
|
||||
dash_save_perm: true,
|
||||
userId: 1,
|
||||
metadata: {},
|
||||
common: {
|
||||
conf: {},
|
||||
},
|
||||
},
|
||||
dashboardTitle: 'title',
|
||||
charts: {},
|
||||
layout: {},
|
||||
filters: {},
|
||||
expandedSlices: {},
|
||||
css: '',
|
||||
customCss: '',
|
||||
isStarred: false,
|
||||
isLoading: false,
|
||||
lastModifiedTime: 0,
|
||||
refreshFrequency: 0,
|
||||
shouldPersistRefreshFrequency: false,
|
||||
onSave: () => {},
|
||||
onChange: () => {},
|
||||
fetchFaveStar: () => {},
|
||||
fetchCharts: () => {},
|
||||
saveFaveStar: () => {},
|
||||
savePublished: () => {},
|
||||
isPublished: false,
|
||||
updateDashboardTitle: () => {},
|
||||
editMode: false,
|
||||
setEditMode: () => {},
|
||||
showBuilderPane: () => {},
|
||||
updateCss: () => {},
|
||||
setColorSchemeAndUnsavedChanges: () => {},
|
||||
logEvent: () => {},
|
||||
setRefreshFrequency: () => {},
|
||||
hasUnsavedChanges: false,
|
||||
maxUndoHistoryExceeded: false,
|
||||
|
||||
// redux
|
||||
onUndo: () => {},
|
||||
onRedo: () => {},
|
||||
undoLength: 0,
|
||||
redoLength: 0,
|
||||
setMaxUndoHistoryExceeded: () => {},
|
||||
maxUndoHistoryToast: () => {},
|
||||
dashboardInfoChanged: () => {},
|
||||
dashboardTitleChanged: () => {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
const wrapper = mount(<Header {...props} {...overrideProps} />);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
describe('read-only-user', () => {
|
||||
const overrideProps = {
|
||||
dashboardInfo: {
|
||||
...props.dashboardInfo,
|
||||
id: 1,
|
||||
dash_edit_perm: false,
|
||||
dash_save_perm: false,
|
||||
userId: 1,
|
||||
},
|
||||
};
|
||||
|
||||
it('should render the EditableTitle', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(EditableTitle)).toExist();
|
||||
});
|
||||
|
||||
it('should render the PublishedStatus', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(PublishedStatus)).toExist();
|
||||
});
|
||||
|
||||
it('should render the FaveStar', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(FaveStar)).toExist();
|
||||
});
|
||||
|
||||
it('should render the HeaderActionsDropdown', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(HeaderActionsDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should not set up undo/redo', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(UndoRedoKeyListeners)).not.toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('write-user', () => {
|
||||
const overrideProps = {
|
||||
editMode: false,
|
||||
dashboardInfo: {
|
||||
...props.dashboardInfo,
|
||||
id: 1,
|
||||
dash_edit_perm: true,
|
||||
dash_save_perm: true,
|
||||
userId: 1,
|
||||
},
|
||||
};
|
||||
|
||||
it('should render the EditableTitle', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(EditableTitle)).toExist();
|
||||
});
|
||||
|
||||
it('should render the PublishedStatus', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(PublishedStatus)).toExist();
|
||||
});
|
||||
|
||||
it('should render the FaveStar', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(FaveStar)).toExist();
|
||||
});
|
||||
|
||||
it('should render the HeaderActionsDropdown', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(HeaderActionsDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should not set up undo/redo', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(UndoRedoKeyListeners)).not.toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('write-user-with-edit-mode', () => {
|
||||
const overrideProps = {
|
||||
editMode: true,
|
||||
dashboardInfo: {
|
||||
...props.dashboardInfo,
|
||||
id: 1,
|
||||
dash_edit_perm: true,
|
||||
dash_save_perm: true,
|
||||
userId: 1,
|
||||
},
|
||||
};
|
||||
|
||||
it('should render the EditableTitle', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(EditableTitle)).toExist();
|
||||
});
|
||||
|
||||
it('should render the FaveStar', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(FaveStar)).toExist();
|
||||
});
|
||||
|
||||
it('should render the PublishedStatus', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(PublishedStatus)).toExist();
|
||||
});
|
||||
|
||||
it('should render the HeaderActionsDropdown', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(HeaderActionsDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should render five Buttons', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(Button)).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('should set up undo/redo', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(UndoRedoKeyListeners)).toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('logged-out-user', () => {
|
||||
const overrideProps = {
|
||||
dashboardInfo: {
|
||||
...props.dashboardInfo,
|
||||
id: 1,
|
||||
dash_edit_perm: false,
|
||||
dash_save_perm: false,
|
||||
userId: null,
|
||||
},
|
||||
};
|
||||
|
||||
it('should render the EditableTitle', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(EditableTitle)).toExist();
|
||||
});
|
||||
|
||||
it('should render the PublishedStatus', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(PublishedStatus)).toExist();
|
||||
});
|
||||
|
||||
it('should not render the FaveStar', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(FaveStar)).not.toExist();
|
||||
});
|
||||
|
||||
it('should render the HeaderActionsDropdown', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(HeaderActionsDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should not set up undo/redo', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(UndoRedoKeyListeners)).not.toExist();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,288 @@
|
|||
/**
|
||||
* 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, fireEvent } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { HeaderProps } from './types';
|
||||
import Header from '.';
|
||||
|
||||
const createProps = () => ({
|
||||
addSuccessToast: jest.fn(),
|
||||
addDangerToast: jest.fn(),
|
||||
addWarningToast: jest.fn(),
|
||||
dashboardInfo: {
|
||||
id: 1,
|
||||
dash_edit_perm: false,
|
||||
dash_save_perm: false,
|
||||
dash_share_perm: false,
|
||||
userId: 1,
|
||||
metadata: {},
|
||||
common: {
|
||||
conf: {},
|
||||
},
|
||||
},
|
||||
dashboardTitle: 'Dashboard Title',
|
||||
charts: {},
|
||||
layout: {},
|
||||
expandedSlices: {},
|
||||
css: '',
|
||||
customCss: '',
|
||||
isStarred: false,
|
||||
isLoading: false,
|
||||
lastModifiedTime: 0,
|
||||
refreshFrequency: 0,
|
||||
shouldPersistRefreshFrequency: false,
|
||||
onSave: jest.fn(),
|
||||
onChange: jest.fn(),
|
||||
fetchFaveStar: jest.fn(),
|
||||
fetchCharts: jest.fn(),
|
||||
saveFaveStar: jest.fn(),
|
||||
savePublished: jest.fn(),
|
||||
isPublished: false,
|
||||
updateDashboardTitle: jest.fn(),
|
||||
editMode: false,
|
||||
setEditMode: jest.fn(),
|
||||
showBuilderPane: jest.fn(),
|
||||
updateCss: jest.fn(),
|
||||
setColorSchemeAndUnsavedChanges: jest.fn(),
|
||||
logEvent: jest.fn(),
|
||||
setRefreshFrequency: jest.fn(),
|
||||
hasUnsavedChanges: false,
|
||||
maxUndoHistoryExceeded: false,
|
||||
onUndo: jest.fn(),
|
||||
onRedo: jest.fn(),
|
||||
undoLength: 0,
|
||||
redoLength: 0,
|
||||
setMaxUndoHistoryExceeded: jest.fn(),
|
||||
maxUndoHistoryToast: jest.fn(),
|
||||
dashboardInfoChanged: jest.fn(),
|
||||
dashboardTitleChanged: jest.fn(),
|
||||
});
|
||||
const props = createProps();
|
||||
const editableProps = {
|
||||
...props,
|
||||
editMode: true,
|
||||
dashboardInfo: {
|
||||
...props.dashboardInfo,
|
||||
dash_edit_perm: true,
|
||||
dash_save_perm: true,
|
||||
},
|
||||
};
|
||||
const undoProps = {
|
||||
...editableProps,
|
||||
undoLength: 1,
|
||||
};
|
||||
const redoProps = {
|
||||
...editableProps,
|
||||
redoLength: 1,
|
||||
};
|
||||
|
||||
fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
|
||||
|
||||
function setup(props: HeaderProps) {
|
||||
return (
|
||||
<div className="dashboard">
|
||||
<Header {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function openActionsDropdown() {
|
||||
const btn = screen.getByRole('img', { name: 'more-horiz' });
|
||||
userEvent.click(btn);
|
||||
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||
}
|
||||
|
||||
test('should render', () => {
|
||||
const mockedProps = createProps();
|
||||
const { container } = render(setup(mockedProps));
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the title', () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
expect(screen.getByText('Dashboard Title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the editable title', () => {
|
||||
render(setup(editableProps));
|
||||
expect(screen.getByDisplayValue('Dashboard Title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should edit the title', () => {
|
||||
render(setup(editableProps));
|
||||
const editableTitle = screen.getByDisplayValue('Dashboard Title');
|
||||
expect(editableProps.onChange).not.toHaveBeenCalled();
|
||||
userEvent.click(editableTitle);
|
||||
userEvent.clear(editableTitle);
|
||||
userEvent.type(editableTitle, 'New Title');
|
||||
userEvent.click(document.body);
|
||||
expect(editableProps.onChange).toHaveBeenCalled();
|
||||
expect(screen.getByDisplayValue('New Title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the "Draft" status', () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
expect(screen.getByText('Draft')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should publish', () => {
|
||||
render(setup(editableProps));
|
||||
const draft = screen.getByText('Draft');
|
||||
expect(editableProps.savePublished).not.toHaveBeenCalled();
|
||||
userEvent.click(draft);
|
||||
expect(editableProps.savePublished).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should render the "Undo" action as disabled', () => {
|
||||
render(setup(editableProps));
|
||||
expect(screen.getByTitle('Undo').parentElement).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should undo', () => {
|
||||
render(setup(undoProps));
|
||||
const undo = screen.getByTitle('Undo');
|
||||
expect(undoProps.onUndo).not.toHaveBeenCalled();
|
||||
userEvent.click(undo);
|
||||
expect(undoProps.onUndo).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should undo with key listener', () => {
|
||||
undoProps.onUndo.mockReset();
|
||||
render(setup(undoProps));
|
||||
expect(undoProps.onUndo).not.toHaveBeenCalled();
|
||||
fireEvent.keyDown(document.body, { key: 'z', code: 'KeyZ', ctrlKey: true });
|
||||
expect(undoProps.onUndo).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should render the "Redo" action as disabled', () => {
|
||||
render(setup(editableProps));
|
||||
expect(screen.getByTitle('Redo').parentElement).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should redo', () => {
|
||||
render(setup(redoProps));
|
||||
const redo = screen.getByTitle('Redo');
|
||||
expect(redoProps.onRedo).not.toHaveBeenCalled();
|
||||
userEvent.click(redo);
|
||||
expect(redoProps.onRedo).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should redo with key listener', () => {
|
||||
redoProps.onRedo.mockReset();
|
||||
render(setup(redoProps));
|
||||
expect(redoProps.onRedo).not.toHaveBeenCalled();
|
||||
fireEvent.keyDown(document.body, { key: 'y', code: 'KeyY', ctrlKey: true });
|
||||
expect(redoProps.onRedo).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should render the "Discard changes" button', () => {
|
||||
render(setup(editableProps));
|
||||
expect(screen.getByText('Discard changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the "Save" button as disabled', () => {
|
||||
render(setup(editableProps));
|
||||
expect(screen.getByText('Save').parentElement).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should save', () => {
|
||||
const unsavedProps = {
|
||||
...editableProps,
|
||||
hasUnsavedChanges: true,
|
||||
};
|
||||
render(setup(unsavedProps));
|
||||
const save = screen.getByText('Save');
|
||||
expect(unsavedProps.onSave).not.toHaveBeenCalled();
|
||||
userEvent.click(save);
|
||||
expect(unsavedProps.onSave).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should NOT render the "Draft" status', () => {
|
||||
const mockedProps = createProps();
|
||||
const publishedProps = {
|
||||
...mockedProps,
|
||||
isPublished: true,
|
||||
};
|
||||
render(setup(publishedProps));
|
||||
expect(screen.queryByText('Draft')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the unselected fave icon', () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
expect(mockedProps.fetchFaveStar).toHaveBeenCalled();
|
||||
expect(
|
||||
screen.getByRole('img', { name: 'favorite-unselected' }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the selected fave icon', () => {
|
||||
const mockedProps = createProps();
|
||||
const favedProps = {
|
||||
...mockedProps,
|
||||
isStarred: true,
|
||||
};
|
||||
render(setup(favedProps));
|
||||
expect(
|
||||
screen.getByRole('img', { name: 'favorite-selected' }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should fave', async () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
const fave = screen.getByRole('img', { name: 'favorite-unselected' });
|
||||
expect(mockedProps.saveFaveStar).not.toHaveBeenCalled();
|
||||
userEvent.click(fave);
|
||||
expect(mockedProps.saveFaveStar).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should toggle the edit mode', () => {
|
||||
const mockedProps = createProps();
|
||||
const canEditProps = {
|
||||
...mockedProps,
|
||||
dashboardInfo: {
|
||||
...mockedProps.dashboardInfo,
|
||||
dash_edit_perm: true,
|
||||
},
|
||||
};
|
||||
render(setup(canEditProps));
|
||||
const editDashboard = screen.getByTitle('Edit dashboard');
|
||||
expect(screen.queryByTitle('Edit dashboard')).toBeInTheDocument();
|
||||
userEvent.click(editDashboard);
|
||||
expect(mockedProps.logEvent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should render the dropdown icon', () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
expect(screen.getByRole('img', { name: 'more-horiz' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should refresh the charts', async () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
await openActionsDropdown();
|
||||
userEvent.click(screen.getByText('Refresh dashboard'));
|
||||
expect(mockedProps.fetchCharts).toHaveBeenCalledTimes(1);
|
||||
});
|
|
@ -0,0 +1,200 @@
|
|||
/**
|
||||
* 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 userEvent from '@testing-library/user-event';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { HeaderDropdownProps } from 'src/dashboard/components/Header/types';
|
||||
import HeaderActionsDropdown from '.';
|
||||
|
||||
const createProps = () => ({
|
||||
addSuccessToast: jest.fn(),
|
||||
addDangerToast: jest.fn(),
|
||||
customCss: '#save-dash-split-button{margin-left: 100px;}',
|
||||
dashboardId: 1,
|
||||
dashboardInfo: {
|
||||
id: 1,
|
||||
dash_edit_perm: true,
|
||||
dash_save_perm: true,
|
||||
userId: 1,
|
||||
metadata: {},
|
||||
common: {
|
||||
conf: {},
|
||||
},
|
||||
},
|
||||
dashboardTitle: 'Title',
|
||||
editMode: false,
|
||||
expandedSlices: {},
|
||||
forceRefreshAllCharts: jest.fn(),
|
||||
hasUnsavedChanges: false,
|
||||
isLoading: false,
|
||||
layout: {},
|
||||
onChange: jest.fn(),
|
||||
onSave: jest.fn(),
|
||||
refreshFrequency: 200,
|
||||
setRefreshFrequency: jest.fn(),
|
||||
shouldPersistRefreshFrequency: false,
|
||||
showPropertiesModal: jest.fn(),
|
||||
startPeriodicRender: jest.fn(),
|
||||
updateCss: jest.fn(),
|
||||
userCanEdit: false,
|
||||
userCanSave: false,
|
||||
userCanShare: false,
|
||||
lastModifiedTime: 0,
|
||||
});
|
||||
const editModeOnProps = {
|
||||
...createProps(),
|
||||
editMode: true,
|
||||
};
|
||||
|
||||
function setup(props: HeaderDropdownProps) {
|
||||
return (
|
||||
<div className="dashboard">
|
||||
<HeaderActionsDropdown {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
|
||||
|
||||
async function openDropdown() {
|
||||
const btn = screen.getByRole('img', { name: 'more-horiz' });
|
||||
userEvent.click(btn);
|
||||
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||
}
|
||||
|
||||
test('should render', () => {
|
||||
const mockedProps = createProps();
|
||||
const { container } = render(setup(mockedProps));
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the dropdown button', () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
expect(screen.getByRole('button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the dropdown icon', () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
expect(screen.getByRole('img', { name: 'more-horiz' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should open the dropdown', async () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
await openDropdown();
|
||||
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the menu items', async () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
await openDropdown();
|
||||
expect(screen.getAllByRole('menuitem')).toHaveLength(4);
|
||||
expect(screen.getByText('Refresh dashboard')).toBeInTheDocument();
|
||||
expect(screen.getByText('Set auto-refresh interval')).toBeInTheDocument();
|
||||
expect(screen.getByText('Download as image')).toBeInTheDocument();
|
||||
expect(screen.getByText('Toggle fullscreen')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the menu items in edit mode', async () => {
|
||||
render(setup(editModeOnProps));
|
||||
await openDropdown();
|
||||
expect(screen.getAllByRole('menuitem')).toHaveLength(5);
|
||||
expect(screen.getByText('Refresh dashboard')).toBeInTheDocument();
|
||||
expect(screen.getByText('Set auto-refresh interval')).toBeInTheDocument();
|
||||
expect(screen.getByText('Set filter mapping')).toBeInTheDocument();
|
||||
expect(screen.getByText('Edit dashboard properties')).toBeInTheDocument();
|
||||
expect(screen.getByText('Edit CSS')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show the share actions', async () => {
|
||||
const mockedProps = createProps();
|
||||
const canShareProps = {
|
||||
...mockedProps,
|
||||
userCanShare: true,
|
||||
};
|
||||
render(setup(canShareProps));
|
||||
await openDropdown();
|
||||
expect(screen.getByText('Copy dashboard URL')).toBeInTheDocument();
|
||||
expect(screen.getByText('Share dashboard by email')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the "Save Modal" when user can save', async () => {
|
||||
const mockedProps = createProps();
|
||||
const canSaveProps = {
|
||||
...mockedProps,
|
||||
userCanSave: true,
|
||||
};
|
||||
render(setup(canSaveProps));
|
||||
await openDropdown();
|
||||
expect(screen.getByText('Save as')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should NOT render the "Save Modal" menu item when user cannot save', async () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
await openDropdown();
|
||||
expect(screen.queryByText('Save as')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the "Refresh dashboard" menu item as disabled when loading', async () => {
|
||||
const mockedProps = createProps();
|
||||
const loadingProps = {
|
||||
...mockedProps,
|
||||
isLoading: true,
|
||||
};
|
||||
render(setup(loadingProps));
|
||||
await openDropdown();
|
||||
expect(screen.getByText('Refresh dashboard')).toHaveClass(
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
});
|
||||
|
||||
test('should NOT render the "Refresh dashboard" menu item as disabled', async () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
await openDropdown();
|
||||
expect(screen.getByText('Refresh dashboard')).not.toHaveClass(
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
});
|
||||
|
||||
test('should render with custom css', () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
expect(screen.getByRole('button')).toHaveStyle('margin-left: 100px');
|
||||
});
|
||||
|
||||
test('should refresh the charts', async () => {
|
||||
const mockedProps = createProps();
|
||||
render(setup(mockedProps));
|
||||
await openDropdown();
|
||||
userEvent.click(screen.getByText('Refresh dashboard'));
|
||||
expect(mockedProps.forceRefreshAllCharts).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should show the properties modal', async () => {
|
||||
render(setup(editModeOnProps));
|
||||
await openDropdown();
|
||||
userEvent.click(screen.getByText('Edit dashboard properties'));
|
||||
expect(editModeOnProps.showPropertiesModal).toHaveBeenCalledTimes(1);
|
||||
});
|
|
@ -25,16 +25,16 @@ import { Menu, NoAnimationDropdown } from 'src/common/components';
|
|||
import Icon from 'src/components/Icon';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
|
||||
import CssEditor from './CssEditor';
|
||||
import RefreshIntervalModal from './RefreshIntervalModal';
|
||||
import SaveModal from './SaveModal';
|
||||
import injectCustomCss from '../util/injectCustomCss';
|
||||
import { SAVE_TYPE_NEWDASHBOARD } from '../util/constants';
|
||||
import FilterScopeModal from './filterscope/FilterScopeModal';
|
||||
import downloadAsImage from '../../utils/downloadAsImage';
|
||||
import getDashboardUrl from '../util/getDashboardUrl';
|
||||
import { getActiveFilters } from '../util/activeDashboardFilters';
|
||||
import { getUrlParam } from '../../utils/urlUtils';
|
||||
import CssEditor from 'src/dashboard/components/CssEditor';
|
||||
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
|
||||
import SaveModal from 'src/dashboard/components/SaveModal';
|
||||
import injectCustomCss from 'src/dashboard/util/injectCustomCss';
|
||||
import { SAVE_TYPE_NEWDASHBOARD } from 'src/dashboard/util/constants';
|
||||
import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeModal';
|
||||
import downloadAsImage from 'src/utils/downloadAsImage';
|
||||
import getDashboardUrl from 'src/dashboard/util/getDashboardUrl';
|
||||
import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
|
||||
const propTypes = {
|
||||
addSuccessToast: PropTypes.func.isRequired,
|
|
@ -34,19 +34,18 @@ import Button from 'src/components/Button';
|
|||
import EditableTitle from 'src/components/EditableTitle';
|
||||
import FaveStar from 'src/components/FaveStar';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
import HeaderActionsDropdown from 'src/dashboard/components/Header/HeaderActionsDropdown';
|
||||
import PublishedStatus from 'src/dashboard/components/PublishedStatus';
|
||||
import UndoRedoKeyListeners from 'src/dashboard/components/UndoRedoKeyListeners';
|
||||
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
|
||||
import { chartPropShape } from 'src/dashboard/util/propShapes';
|
||||
import HeaderActionsDropdown from './HeaderActionsDropdown';
|
||||
import PublishedStatus from './PublishedStatus';
|
||||
import UndoRedoKeyListeners from './UndoRedoKeyListeners';
|
||||
import PropertiesModal from './PropertiesModal';
|
||||
|
||||
import {
|
||||
UNDO_LIMIT,
|
||||
SAVE_TYPE_OVERWRITE,
|
||||
DASHBOARD_POSITION_DATA_LIMIT,
|
||||
} from '../util/constants';
|
||||
import setPeriodicRunner from '../util/setPeriodicRunner';
|
||||
import { options as PeriodicRefreshOptions } from './RefreshIntervalModal';
|
||||
} from 'src/dashboard/util/constants';
|
||||
import setPeriodicRunner from 'src/dashboard/util/setPeriodicRunner';
|
||||
import { options as PeriodicRefreshOptions } from 'src/dashboard/components/RefreshIntervalModal';
|
||||
|
||||
const propTypes = {
|
||||
addSuccessToast: PropTypes.func.isRequired,
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* 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 { Layout } from 'src/dashboard/types';
|
||||
import { ChartState } from 'src/explore/types';
|
||||
|
||||
interface DashboardInfo {
|
||||
id: number;
|
||||
userId: number;
|
||||
dash_edit_perm: boolean;
|
||||
dash_save_perm: boolean;
|
||||
metadata?: Record<string, any>;
|
||||
common?: { conf: Record<string, any> };
|
||||
}
|
||||
|
||||
export interface HeaderDropdownProps {
|
||||
addSuccessToast: () => void;
|
||||
addDangerToast: () => void;
|
||||
customCss: string;
|
||||
colorNamespace?: string;
|
||||
colorScheme?: string;
|
||||
dashboardId: number;
|
||||
dashboardInfo: DashboardInfo;
|
||||
dashboardTitle: string;
|
||||
editMode: boolean;
|
||||
expandedSlices: Record<number, boolean>;
|
||||
forceRefreshAllCharts: () => void;
|
||||
hasUnsavedChanges: boolean;
|
||||
isLoading: boolean;
|
||||
layout: Layout;
|
||||
onChange: () => void;
|
||||
onSave: () => void;
|
||||
refreshFrequency: number;
|
||||
setRefreshFrequency: () => void;
|
||||
shouldPersistRefreshFrequency: boolean;
|
||||
showPropertiesModal: () => void;
|
||||
startPeriodicRender: () => void;
|
||||
updateCss: () => void;
|
||||
userCanEdit: boolean;
|
||||
userCanSave: boolean;
|
||||
lastModifiedTime: number;
|
||||
}
|
||||
|
||||
export interface HeaderProps {
|
||||
addSuccessToast: () => void;
|
||||
addDangerToast: () => void;
|
||||
addWarningToast: () => void;
|
||||
colorNamespace?: string;
|
||||
charts: ChartState | {};
|
||||
colorScheme?: string;
|
||||
customCss: string;
|
||||
dashboardInfo: DashboardInfo;
|
||||
dashboardTitle: string;
|
||||
setColorSchemeAndUnsavedChanges: () => void;
|
||||
isStarred: boolean;
|
||||
isPublished: boolean;
|
||||
onChange: () => void;
|
||||
onSave: () => void;
|
||||
fetchFaveStar: () => void;
|
||||
saveFaveStar: () => void;
|
||||
savePublished: () => void;
|
||||
updateDashboardTitle: () => void;
|
||||
editMode: boolean;
|
||||
setEditMode: () => void;
|
||||
showBuilderPane: () => void;
|
||||
updateCss: () => void;
|
||||
logEvent: () => void;
|
||||
hasUnsavedChanges: boolean;
|
||||
maxUndoHistoryExceeded: boolean;
|
||||
lastModifiedTime: number;
|
||||
onUndo: () => void;
|
||||
onRedo: () => void;
|
||||
undoLength: number;
|
||||
redoLength: number;
|
||||
setMaxUndoHistoryExceeded: () => void;
|
||||
maxUndoHistoryToast: () => void;
|
||||
refreshFrequency: number;
|
||||
shouldPersistRefreshFrequency: boolean;
|
||||
setRefreshFrequency: () => void;
|
||||
dashboardInfoChanged: () => void;
|
||||
dashboardTitleChanged: () => void;
|
||||
}
|
Loading…
Reference in New Issue