Skip to content
Snippets Groups Projects
Unverified Commit 6c22ee00 authored by Nick Fitzpatrick's avatar Nick Fitzpatrick Committed by GitHub
Browse files

Adding beforeUnload hook to Status Listing (#30372)

* Adding beforeUnload hook to Status Listing

* Adding callMockEvent function, updating tests

* running prettier

* linting
parent 239847ef
No related branches found
No related tags found
No related merge requests found
import { FileUpload } from "../upload";
export const createMockUploadState = () => {
return {};
export const createMockUploadState = (uploads = {}) => {
return { ...uploads };
};
export const createMockUpload = (props?: Partial<FileUpload>): FileUpload => {
......
......@@ -34,8 +34,10 @@ const uploadEnd = createAction(UPLOAD_FILE_TO_COLLECTION_END);
const uploadError = createAction(UPLOAD_FILE_TO_COLLECTION_ERROR);
const clearUpload = createAction(UPLOAD_FILE_TO_COLLECTION_CLEAR);
export const getAllUploads = (state: State) =>
Object.keys(state.upload).map(key => state.upload[key]);
export const getAllUploads = (state: State) => Object.values(state.upload);
export const hasActiveUploads = (state: State) =>
getAllUploads(state).some(upload => upload.status === "in-progress");
export const uploadFile = createThunkAction(
UPLOAD_FILE_TO_COLLECTION,
......
import React from "react";
import { connect } from "react-redux";
import { t } from "ttag";
import { useBeforeUnload } from "react-use";
import { useSelector } from "metabase/lib/redux";
import { getUserIsAdmin, getUser } from "metabase/selectors/user";
import type { State } from "metabase-types/store";
import { hasActiveUploads } from "metabase/redux/uploads";
import DatabaseStatus from "../../containers/DatabaseStatus";
import FileUploadStatus from "../FileUploadStatus";
import { StatusListingRoot } from "./StatusListing.styled";
const mapStateToProps = (state: State) => ({
isAdmin: getUserIsAdmin(state),
isLoggedIn: !!getUser(state),
});
const StatusListingView = () => {
const isLoggedIn = !!useSelector(getUser);
const isAdmin = useSelector(getUserIsAdmin);
const uploadInProgress = useSelector(hasActiveUploads);
export interface StatusListingProps {
isAdmin: boolean;
isLoggedIn: boolean;
}
useBeforeUnload(
uploadInProgress,
t`CSV Upload in progress. Are you sure you want to leave?`,
);
export const StatusListingView = ({
isAdmin,
isLoggedIn,
}: StatusListingProps) => {
if (!isLoggedIn) {
return null;
}
......@@ -34,4 +34,4 @@ export const StatusListingView = ({
);
};
export default connect(mapStateToProps)(StatusListingView);
export default StatusListingView;
import React from "react";
import { renderWithProviders, screen } from "__support__/ui";
import { setupCollectionsEndpoints } from "__support__/server-mocks";
import { createMockCollection } from "metabase-types/api/mocks";
import { callMockEvent } from "__support__/events";
import { createMockCollection, createMockUser } from "metabase-types/api/mocks";
import {
StatusListingView as StatusListing,
StatusListingProps,
} from "./StatusListing";
import { createMockState, createMockUpload } from "metabase-types/store/mocks";
import { FileUploadState } from "metabase-types/store/upload";
import StatusListing from "./StatusListing";
const DatabaseStatusMock = () => <div>DatabaseStatus</div>;
jest.mock("../../containers/DatabaseStatus", () => DatabaseStatusMock);
const setup = (options?: Partial<StatusListingProps>) => {
setupCollectionsEndpoints([createMockCollection()]);
interface setupProps {
isAdmin?: boolean;
upload?: FileUploadState;
}
return renderWithProviders(<StatusListing isAdmin isLoggedIn {...options} />);
const setup = ({ isAdmin = false, upload = {} }: setupProps = {}) => {
setupCollectionsEndpoints([createMockCollection({})]);
return renderWithProviders(<StatusListing />, {
storeInitialState: createMockState({
currentUser: createMockUser({
is_superuser: isAdmin,
}),
upload,
}),
});
};
describe("StatusListing", () => {
it("should render database statuses for admins", () => {
setup({
isAdmin: true,
isLoggedIn: true,
});
afterEach(() => {
jest.resetAllMocks();
});
it("should render database statuses for admins", () => {
setup({ isAdmin: true });
expect(screen.getByText("DatabaseStatus")).toBeInTheDocument();
});
it("should not render database statuses for non-admins", () => {
renderWithProviders(<StatusListing isAdmin={false} isLoggedIn />);
setup();
expect(screen.queryByText("DatabaseStatus")).not.toBeInTheDocument();
});
it("should not render if no one is logged in", () => {
setupCollectionsEndpoints([createMockCollection({})]);
renderWithProviders(<StatusListing />);
expect(screen.queryByText("DatabaseStatus")).not.toBeInTheDocument();
});
it("should give an alert if a user navigates away from the page during an upload", () => {
const mockEventListener = jest.spyOn(window, "addEventListener");
const mockUpload = createMockUpload();
setup({ isAdmin: true, upload: { [mockUpload.id]: mockUpload } });
const mockEvent = callMockEvent(mockEventListener, "beforeunload");
expect(mockEvent.returnValue).toEqual(
"CSV Upload in progress. Are you sure you want to leave?",
);
expect(mockEvent.preventDefault).toHaveBeenCalled();
});
it("should not give an alert if a user navigates away from the page while no uploads are in progress", () => {
const mockEventListener = jest.spyOn(window, "addEventListener");
setup({ isAdmin: true });
const mockEvent = callMockEvent(mockEventListener, "beforeunload");
expect(mockEvent.returnValue).toBeUndefined();
});
});
type MockEvent = {
preventDefault: jest.Mock;
returnValue?: string;
};
type CallMockEventType = (
mockEventListener: jest.SpyInstance,
eventName: string,
) => MockEvent;
// calls event handler in the mockEventListener that matches the eventName
// and uses the mockEvent to hold the callback's return value
export const callMockEvent: CallMockEventType = (
mockEventListener: jest.SpyInstance,
eventName: string,
) => {
const mockEvent = {
preventDefault: jest.fn(),
};
mockEventListener.mock.calls
.filter(([event]) => eventName === event)
.forEach(([_, callback]) => callback(mockEvent));
return mockEvent;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment