From 345dbbdb284969c415b1a21997869bdd9aa1de88 Mon Sep 17 00:00:00 2001 From: Wallace Breza Date: Fri, 1 Mar 2019 10:12:44 -0800 Subject: [PATCH] fix: Fixes issue where user is unable to navigate to new project screen (#629) Resolves the issue where user is unable to navigate to new project screen from the homepage. This also addresses other strange behavior related to routing in the app. -User being navigated to the homepage after creating a connection -Refresh application button not working Resolves AB#17258, 17263, 17058 --- .env | 6 +-- config/webpack.common.js | 53 ++++++++++--------- package.json | 4 ++ src/App.test.tsx | 31 ++++++++--- src/App.tsx | 4 +- src/electron/main.ts | 25 +++------ .../pages/homepage/homePage.test.tsx | 21 +++++--- .../components/pages/homepage/homePage.tsx | 18 ++++--- .../shell/mainContentRouter.test.tsx | 6 +-- 9 files changed, 93 insertions(+), 75 deletions(-) diff --git a/.env b/.env index c6891804e8..3323e230fd 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ -# react-scripts build use this to generate the right path for assets +# react-scripts build use this to generate the right path for assets # relative to index.html # without it, you'll see error like this -# Failed to load resource: net::ERR_FILE_NOT_FOUND /favicon.ico:1 -PUBLIC_URL=. \ No newline at end of file +# Failed to load resource: net::ERR_FILE_NOT_FOUND /favicon.ico:1 +PUBLIC_URL=. diff --git a/config/webpack.common.js b/config/webpack.common.js index 93eee8643e..cf19765e68 100644 --- a/config/webpack.common.js +++ b/config/webpack.common.js @@ -1,29 +1,32 @@ const path = require("path"); module.exports = { - target: "electron-main", - entry: "./src/electron/main.ts", - module: { - rules: [ - { - test: /\.ts?$/, - use: [{ - loader: "ts-loader", - options: { - compilerOptions: { - noEmit: false + node: { + __dirname: false, + }, + target: "electron-main", + entry: "./src/electron/main.ts", + module: { + rules: [ + { + test: /\.ts?$/, + use: [{ + loader: "ts-loader", + options: { + compilerOptions: { + noEmit: false + } + } + }], + exclude: /node_modules/ } - } - }], - exclude: /node_modules/ - } - ] - }, - resolve: { - extensions: [".ts", ".js"] - }, - output: { - filename: "main.js", - path: path.resolve(__dirname, "../build") - } -}; \ No newline at end of file + ] + }, + resolve: { + extensions: [".ts", ".js"] + }, + output: { + filename: "main.js", + path: path.resolve(__dirname, "../build") + } +}; diff --git a/package.json b/package.json index 2a95feea9a..afd86a981d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { "name": "vott", "version": "2.0.0-preview.1", + "author": { + "name": "Microsoft", + "url": "https://github.com/Microsoft/VoTT" + }, "description": "Visual Object Tagging Tool (VoTT) - an annotation and labeling tool for images and video.", "homepage": "https://github.com/Microsoft/VoTT", "repository": { diff --git a/src/App.test.tsx b/src/App.test.tsx index d19b0729c0..53fa3907da 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,19 +1,34 @@ import React from "react"; -import ReactDOM from "react-dom"; import App from "./App"; import { Provider } from "react-redux"; import createReduxStore from "./redux/store/store"; import initialState from "./redux/store/initialState"; import { IApplicationState } from "./models//applicationState"; +import { mount } from "enzyme"; +import { HashRouter } from "react-router-dom"; +import { KeyboardManager } from "./react/components/common/keyboardManager/keyboardManager"; +import { ErrorHandler } from "./react/components/common/errorHandler/errorHandler"; -it("renders without crashing", () => { +describe("App Component", () => { const defaultState: IApplicationState = initialState; const store = createReduxStore(defaultState); - const div = document.createElement("div"); - ReactDOM.render( - - - , div); - ReactDOM.unmountComponentAtNode(div); + function createComponent() { + return mount( + + + , + ); + } + + it("renders without crashing", () => { + createComponent(); + }); + + it("renders required top level components", () => { + const wrapper = createComponent(); + expect(wrapper.find(HashRouter).exists()).toBe(true); + expect(wrapper.find(KeyboardManager).exists()).toEqual(true); + expect(wrapper.find(ErrorHandler).exists()).toEqual(true); + }); }); diff --git a/src/App.tsx b/src/App.tsx index 79a299b99b..b2df5eac14 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,11 @@ import React, { Fragment } from "react"; import { connect } from "react-redux"; -import { BrowserRouter as Router } from "react-router-dom"; +import { HashRouter as Router } from "react-router-dom"; import { ToastContainer } from "react-toastify"; import Navbar from "./react/components/shell/navbar"; import Sidebar from "./react/components/shell/sidebar"; import MainContentRouter from "./react/components/shell/mainContentRouter"; -import { IAppError, IApplicationState, IProject, AppError, ErrorCode } from "./models/applicationState"; +import { IAppError, IApplicationState, IProject, ErrorCode } from "./models/applicationState"; import "./App.scss"; import "react-toastify/dist/ReactToastify.css"; import IAppErrorActions, * as appErrorActions from "./redux/actions/appErrorActions"; diff --git a/src/electron/main.ts b/src/electron/main.ts index 792af6ca20..48208dc440 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -1,4 +1,4 @@ -import { app, ipcMain, BrowserWindow, dialog, BrowserWindowConstructorOptions, Menu } from "electron"; +import { app, ipcMain, BrowserWindow, BrowserWindowConstructorOptions, Menu } from "electron"; import { IpcMainProxy } from "./common/ipcMainProxy"; import LocalFileSystem from "./providers/storage/localFileSystem"; @@ -8,28 +8,21 @@ let mainWindow: BrowserWindow; let ipcMainProxy: IpcMainProxy; function createWindow() { - // and load the index.html of the app. - const windowOptions: BrowserWindowConstructorOptions = { width: 1024, height: 768, }; - // Create the browser window. + const staticUrl = process.env.ELECTRON_START_URL || `file:///${__dirname}/index.html`; if (process.env.ELECTRON_START_URL) { - // Disable web security to support loading in local file system resources - // TODO: Look into defined local security policy windowOptions.webPreferences = { webSecurity: false, }; - mainWindow = new BrowserWindow(windowOptions); - mainWindow.loadURL(process.env.ELECTRON_START_URL); - } else { - // When running in production mode or with static files use loadFile api vs. loadUrl api. - mainWindow = new BrowserWindow(windowOptions); - mainWindow.loadFile("build/index.html"); } + mainWindow = new BrowserWindow(windowOptions); + mainWindow.loadURL(staticUrl); + // Emitted when the window is closed. mainWindow.on("closed", () => { // Dereference the window object, usually you would store windows @@ -53,12 +46,8 @@ function onReloadApp() { return true; } -function onToggleDevTools(sender: any, show: boolean) { - if (show) { - mainWindow.webContents.openDevTools(); - } else { - mainWindow.webContents.closeDevTools(); - } +function onToggleDevTools() { + mainWindow.webContents.toggleDevTools(); } /** diff --git a/src/react/components/pages/homepage/homePage.test.tsx b/src/react/components/pages/homepage/homePage.test.tsx index 658b176cf5..70b73c170a 100644 --- a/src/react/components/pages/homepage/homePage.test.tsx +++ b/src/react/components/pages/homepage/homePage.test.tsx @@ -10,7 +10,7 @@ import createReduxStore from "../../../../redux/store/store"; import ProjectService from "../../../../services/projectService"; import CondensedList from "../../common/condensedList/condensedList"; import FilePicker, { IFilePickerProps } from "../../common/filePicker/filePicker"; -import HomePage, { IHomepageProps, IHomepageState } from "./homePage"; +import HomePage, { IHomePageProps, IHomePageState } from "./homePage"; jest.mock("../../common/cloudFilePicker/cloudFilePicker"); import { CloudFilePicker, ICloudFilePickerProps } from "../../common/cloudFilePicker/cloudFilePicker"; @@ -19,13 +19,13 @@ jest.mock("../../../../services/projectService"); describe("Homepage Component", () => { let store: Store = null; - let props: IHomepageProps = null; + let props: IHomePageProps = null; let wrapper: ReactWrapper = null; let deleteProjectSpy: jest.SpyInstance = null; let closeProjectSpy: jest.SpyInstance = null; const recentProjects = MockFactory.createTestProjects(2); - function createComponent(store, props: IHomepageProps): ReactWrapper { + function createComponent(store, props: IHomePageProps): ReactWrapper { return mount( @@ -79,7 +79,7 @@ describe("Homepage Component", () => { it("should render a list of recent projects", () => { expect(wrapper).not.toBeNull(); - const homePage = wrapper.find(HomePage).childAt(0) as ReactWrapper; + const homePage = wrapper.find(HomePage).childAt(0) as ReactWrapper; if (homePage.props().recentProjects && homePage.props().recentProjects.length > 0) { expect(wrapper.find(CondensedList).exists()).toBeTruthy(); } @@ -99,7 +99,7 @@ describe("Homepage Component", () => { await MockFactory.flushUi(); wrapper.update(); - const homePage = wrapper.find(HomePage).childAt(0) as ReactWrapper; + const homePage = wrapper.find(HomePage).childAt(0) as ReactWrapper; expect(deleteProjectSpy).toBeCalledWith(recentProjects[0]); expect(homePage.props().recentProjects.length).toEqual(recentProjects.length - 1); @@ -148,13 +148,18 @@ describe("Homepage Component", () => { }); it("closes any open project and navigates to the new project screen", () => { - const homepage = wrapper.find(HomePage).childAt(0) as ReactWrapper; - homepage.find("a.new-project").simulate("click"); + const eventMock = { + preventDefault: jest.fn(), + }; + + const homepage = wrapper.find(HomePage).childAt(0) as ReactWrapper; + homepage.find("a.new-project").simulate("click", eventMock); expect(closeProjectSpy).toBeCalled(); expect(homepage.props().history.push).toBeCalledWith("/projects/create"); + expect(eventMock.preventDefault).toBeCalled(); }); - function createProps(): IHomepageProps { + function createProps(): IHomePageProps { return { recentProjects: [], connections: MockFactory.createTestConnections(), diff --git a/src/react/components/pages/homepage/homePage.tsx b/src/react/components/pages/homepage/homePage.tsx index f26a8948cd..0b95e7a7fc 100644 --- a/src/react/components/pages/homepage/homePage.tsx +++ b/src/react/components/pages/homepage/homePage.tsx @@ -1,6 +1,6 @@ -import React from "react"; +import React, { SyntheticEvent } from "react"; import { connect } from "react-redux"; -import { Link, RouteComponentProps } from "react-router-dom"; +import { RouteComponentProps } from "react-router-dom"; import { bindActionCreators } from "redux"; import { strings } from "../../../../common/strings"; import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions"; @@ -13,16 +13,16 @@ import RecentProjectItem from "./recentProjectItem"; import { constants } from "../../../../common/constants"; import { IApplicationState, IConnection, IProject, - ErrorCode, AppError, IAppError, + ErrorCode, AppError, } from "../../../../models/applicationState"; -export interface IHomepageProps extends RouteComponentProps, React.Props { +export interface IHomePageProps extends RouteComponentProps, React.Props { recentProjects: IProject[]; connections: IConnection[]; actions: IProjectActions; } -export interface IHomepageState { +export interface IHomePageState { cloudPickerOpen: boolean; } @@ -40,8 +40,8 @@ function mapDispatchToProps(dispatch) { } @connect(mapStateToProps, mapDispatchToProps) -export default class HomePage extends React.Component { - public state: IHomepageState = { +export default class HomePage extends React.Component { + public state: IHomePageState = { cloudPickerOpen: false, }; @@ -103,9 +103,11 @@ export default class HomePage extends React.Component { ); } - private createNewProject = () => { + private createNewProject = (e: SyntheticEvent) => { this.props.actions.closeProject(); this.props.history.push("/projects/create"); + + e.preventDefault(); } private handleOpenCloudProjectClick = () => { diff --git a/src/react/components/shell/mainContentRouter.test.tsx b/src/react/components/shell/mainContentRouter.test.tsx index c09d2f11b2..876336c257 100644 --- a/src/react/components/shell/mainContentRouter.test.tsx +++ b/src/react/components/shell/mainContentRouter.test.tsx @@ -8,7 +8,7 @@ import { AnyAction, Store } from "redux"; import createReduxStore from "../../../redux/store/store"; import MainContentRouter from "./mainContentRouter"; -import HomePage, { IHomepageProps } from "./../pages/homepage/homePage"; +import HomePage, { IHomePageProps } from "./../pages/homepage/homePage"; import SettingsPage from "./../pages/appSettings/appSettingsPage"; import ConnectionsPage from "./../pages/connections/connectionsPage"; import ProfilePage from "./../pages/profileSettingsPage"; @@ -17,7 +17,7 @@ import { IApplicationState } from "./../../../models/applicationState"; describe("Main Content Router", () => { const badRoute: string = "/index.html"; - function createComponent(routerContext, route, store, props: IHomepageProps): ReactWrapper { + function createComponent(routerContext, route, store, props: IHomePageProps): ReactWrapper { return mount( @@ -51,7 +51,7 @@ describe("Main Content Router", () => { const homePage = wrapper.find(HomePage); expect(homePage.find(".app-homepage").exists()).toEqual(true); - }); + }); }); function createStore(state?: IApplicationState): Store {