1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Fix TopBar unit test to not require global shared state or mockFS

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-06-10 11:03:12 -04:00
parent b93124b352
commit fe14090d66
12 changed files with 304 additions and 183 deletions

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { WindowAction } from "../../../../common/ipc/window";
import { requestWindowAction } from "../../../ipc";
const closeWindowInjectable = getInjectable({
id: "close-window",
instantiate: () => () => requestWindowAction(WindowAction.CLOSE),
causesSideEffects: true,
});
export default closeWindowInjectable;

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { WindowAction } from "../../../../common/ipc/window";
import { requestWindowAction } from "../../../ipc";
const goBackInjectable = getInjectable({
id: "go-back",
instantiate: () => () => requestWindowAction(WindowAction.GO_BACK),
causesSideEffects: true,
});
export default goBackInjectable;

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { WindowAction } from "../../../../common/ipc/window";
import { requestWindowAction } from "../../../ipc";
const goForwardInjectable = getInjectable({
id: "go-forward",
instantiate: () => () => requestWindowAction(WindowAction.GO_FORWARD),
causesSideEffects: true,
});
export default goForwardInjectable;

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { WindowAction } from "../../../../common/ipc/window";
import { requestWindowAction } from "../../../ipc";
const maximizeWindowInjectable = getInjectable({
id: "maximize-window",
instantiate: () => () => requestWindowAction(WindowAction.MINIMIZE),
causesSideEffects: true,
});
export default maximizeWindowInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import topBarStateInjectable from "./state.injectable";
const topBarNextEnabledInjectable = getInjectable({
id: "top-bar-next-enabled",
instantiate: (di) => {
const state = di.inject(topBarStateInjectable);
return computed(() => state.nextEnabled);
},
});
export default topBarNextEnabledInjectable;

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { emitOpenAppMenuAsContextMenu } from "../../../ipc";
const openAppContextMenuInjectable = getInjectable({
id: "open-app-context-menu",
instantiate: () => emitOpenAppMenuAsContextMenu,
causesSideEffects: true,
});
export default openAppContextMenuInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import topBarStateInjectable from "./state.injectable";
const topBarPrevEnabledInjectable = getInjectable({
id: "top-bar-prev-enabled",
instantiate: (di) => {
const state = di.inject(topBarStateInjectable);
return computed(() => state.prevEnabled);
},
});
export default topBarPrevEnabledInjectable;

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/before-frame-starts-injection-token";
import ipcRendererInjectable from "../../../utils/channel/ipc-renderer.injectable";
import topBarStateInjectable from "./state.injectable";
// TODO: replace with a SyncBox
const startTopbarStateSyncInjectable = getInjectable({
id: "start-topbar-state-sync",
instantiate: (di) => {
const state = di.inject(topBarStateInjectable);
const ipcRenderer = di.inject(ipcRendererInjectable);
return {
run: () => {
ipcRenderer.on("history:can-go-back", action((event, canGoBack: boolean) => {
state.prevEnabled = canGoBack;
}));
ipcRenderer.on("history:can-go-forward", action((event, canGoForward: boolean) => {
state.nextEnabled = canGoForward;
}));
},
};
},
injectionToken: beforeFrameStartsInjectionToken,
});
export default startTopbarStateSyncInjectable;

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
const topBarStateInjectable = getInjectable({
id: "top-bar-state",
instantiate: () => observable.object({
prevEnabled: false,
nextEnabled: false,
}),
});
export default topBarStateInjectable;

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { WindowAction } from "../../../../common/ipc/window";
import { requestWindowAction } from "../../../ipc";
const toggleMaximizeWindowInjectable = getInjectable({
id: "toggle-maximize-window",
instantiate: () => () => requestWindowAction(WindowAction.TOGGLE_MAXIMIZE),
causesSideEffects: true,
});
export default toggleMaximizeWindowInjectable;

View File

@ -12,163 +12,125 @@ import type { DiContainer } from "@ogre-tools/injectable";
import type { DiRender } from "../../test-utils/renderFor"; import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor";
import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable"; import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable";
import { computed } from "mobx"; import { computed, observable } from "mobx";
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import mockFs from "mock-fs";
import isLinuxInjectable from "../../../../common/vars/is-linux.injectable"; import isLinuxInjectable from "../../../../common/vars/is-linux.injectable";
import isWindowsInjectable from "../../../../common/vars/is-windows.injectable"; import isWindowsInjectable from "../../../../common/vars/is-windows.injectable";
import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
jest.mock("../../../../common/vars", () => { import closeWindowInjectable from "./close-window.injectable";
const { SemVer } = require("semver"); import goBackInjectable from "./go-back.injectable";
import goForwardInjectable from "./go-forward.injectable";
return { import maximizeWindowInjectable from "./maximize-window.injectable";
...jest.requireActual<{}>("../../../../common/vars"), import openAppContextMenuInjectable from "./open-app-context-menu.injectable";
appSemVer: new SemVer("1.0.0"), import toggleMaximizeWindowInjectable from "./toggle-maximize-window.injectable";
}; import topBarStateInjectable from "./state.injectable";
});
const goBack = jest.fn();
const goForward = jest.fn();
jest.mock(
"electron",
() => ({
ipcRenderer: {
on: jest.fn(
(channel: string, listener: (event: any, ...args: any[]) => void) => {
if (channel === "history:can-go-back") {
listener({}, true);
}
if (channel === "history:can-go-forward") {
listener({}, true);
}
},
),
invoke: jest.fn(
(channel: string, action: string) => {
console.log("channel", channel, action);
if (channel !== "window:window-action") return;
switch(action) {
case "back": {
goBack();
break;
}
case "forward": {
goForward();
break;
}
}
},
),
},
}),
);
jest.mock("../../+catalog", () => ({
previousActiveTab: jest.fn(),
}));
describe("<TopBar/>", () => { describe("<TopBar/>", () => {
let di: DiContainer; let di: DiContainer;
let render: DiRender; let render: DiRender;
let goBack: jest.MockedFunction<() => void>;
let goForward: jest.MockedFunction<() => void>;
beforeEach(() => { beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true }); di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs(); di.override(rendererExtensionsInjectable, () => computed(() => []));
di.override(openAppContextMenuInjectable, () => jest.fn());
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(goBackInjectable, () => goBack = jest.fn());
di.override(goForwardInjectable, () => goForward = jest.fn());
di.override(closeWindowInjectable, () => jest.fn());
di.override(maximizeWindowInjectable, () => jest.fn());
di.override(toggleMaximizeWindowInjectable, () => jest.fn());
render = renderFor(di); render = renderFor(di);
}); });
afterEach(() => { describe("with both previous and next history enabled", () => {
mockFs.restore(); beforeEach(() => {
}); di.override(topBarStateInjectable, () => observable.object({
prevEnabled: true,
nextEnabled: true,
}));
});
it("renders w/o errors", () => {
const { container } = render(<TopBar/>);
it("renders w/o errors", () => { expect(container).toBeInstanceOf(HTMLElement);
const { container } = render(<TopBar/>); });
expect(container).toBeInstanceOf(HTMLElement); it("renders home button", async () => {
}); const { findByTestId } = render(<TopBar/>);
it("renders home button", async () => { expect(await findByTestId("home-button")).toBeInTheDocument();
const { findByTestId } = render(<TopBar/>); });
expect(await findByTestId("home-button")).toBeInTheDocument(); it("renders history arrows", async () => {
}); const { findByTestId } = render(<TopBar/>);
it("renders history arrows", async () => { expect(await findByTestId("history-back")).toBeInTheDocument();
const { findByTestId } = render(<TopBar/>); expect(await findByTestId("history-forward")).toBeInTheDocument();
});
expect(await findByTestId("history-back")).toBeInTheDocument(); it("enables arrow by ipc event", async () => {
expect(await findByTestId("history-forward")).toBeInTheDocument(); const { findByTestId } = render(<TopBar/>);
});
it("enables arrow by ipc event", async () => { expect(await findByTestId("history-back")).not.toHaveClass("disabled");
const { findByTestId } = render(<TopBar/>); expect(await findByTestId("history-forward")).not.toHaveClass("disabled");
});
expect(await findByTestId("history-back")).not.toHaveClass("disabled"); it("triggers browser history back and forward", async () => {
expect(await findByTestId("history-forward")).not.toHaveClass("disabled"); const { findByTestId } = render(<TopBar/>);
});
it("triggers browser history back and forward", async () => { const prevButton = await findByTestId("history-back");
const { findByTestId } = render(<TopBar/>); const nextButton = await findByTestId("history-forward");
const prevButton = await findByTestId("history-back"); fireEvent.click(prevButton);
const nextButton = await findByTestId("history-forward");
fireEvent.click(prevButton); expect(goBack).toBeCalled();
expect(goBack).toBeCalled(); fireEvent.click(nextButton);
fireEvent.click(nextButton); expect(goForward).toBeCalled();
});
expect(goForward).toBeCalled(); it("renders items", async () => {
}); const testId = "testId";
const text = "an item";
it("renders items", async () => { di.override(topBarItemsInjectable, () => computed(() => [
const testId = "testId"; {
const text = "an item"; components: {
Item: () => <span data-testid={testId}>{text}</span>,
di.override(topBarItemsInjectable, () => computed(() => [ },
{
components: {
Item: () => <span data-testid={testId}>{text}</span>,
}, },
}, ]));
]));
const { findByTestId } = render(<TopBar/>); const { findByTestId } = render(<TopBar/>);
expect(await findByTestId(testId)).toHaveTextContent(text); expect(await findByTestId(testId)).toHaveTextContent(text);
}); });
it("doesn't show windows title buttons on macos", () => { it("doesn't show windows title buttons on macos", () => {
di.override(isLinuxInjectable, () => false); di.override(isLinuxInjectable, () => false);
di.override(isWindowsInjectable, () => false); di.override(isWindowsInjectable, () => false);
const { queryByTestId } = render(<TopBar/>); const { queryByTestId } = render(<TopBar/>);
expect(queryByTestId("window-menu")).not.toBeInTheDocument(); expect(queryByTestId("window-menu")).not.toBeInTheDocument();
expect(queryByTestId("window-minimize")).not.toBeInTheDocument(); expect(queryByTestId("window-minimize")).not.toBeInTheDocument();
expect(queryByTestId("window-maximize")).not.toBeInTheDocument(); expect(queryByTestId("window-maximize")).not.toBeInTheDocument();
expect(queryByTestId("window-close")).not.toBeInTheDocument(); expect(queryByTestId("window-close")).not.toBeInTheDocument();
}); });
it("does show windows title buttons on linux", () => { it("does show windows title buttons on linux", () => {
di.override(isLinuxInjectable, () => true); di.override(isLinuxInjectable, () => true);
di.override(isWindowsInjectable, () => false); di.override(isWindowsInjectable, () => false);
const { queryByTestId } = render(<TopBar/>); const { queryByTestId } = render(<TopBar/>);
expect(queryByTestId("window-menu")).toBeInTheDocument(); expect(queryByTestId("window-menu")).toBeInTheDocument();
expect(queryByTestId("window-minimize")).toBeInTheDocument(); expect(queryByTestId("window-minimize")).toBeInTheDocument();
expect(queryByTestId("window-maximize")).toBeInTheDocument(); expect(queryByTestId("window-maximize")).toBeInTheDocument();
expect(queryByTestId("window-close")).toBeInTheDocument(); expect(queryByTestId("window-close")).toBeInTheDocument();
});
}); });
}); });

View File

@ -8,15 +8,11 @@ import React, { useEffect, useRef } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import { Icon } from "../../icon"; import { Icon } from "../../icon";
import { observable } from "mobx";
import { ipcRendererOn } from "../../../../common/ipc";
import { watchHistoryState } from "../../../remote-helpers/history-updater"; import { watchHistoryState } from "../../../remote-helpers/history-updater";
import { cssNames, noop } from "../../../utils"; import { cssNames, noop } from "../../../utils";
import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable"; import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import type { TopBarRegistration } from "./top-bar-registration"; import type { TopBarRegistration } from "./top-bar-registration";
import { emitOpenAppMenuAsContextMenu, requestWindowAction } from "../../../ipc";
import { WindowAction } from "../../../../common/ipc/window";
import isLinuxInjectable from "../../../../common/vars/is-linux.injectable"; import isLinuxInjectable from "../../../../common/vars/is-linux.injectable";
import isWindowsInjectable from "../../../../common/vars/is-windows.injectable"; import isWindowsInjectable from "../../../../common/vars/is-windows.injectable";
import type { NavigateToCatalog } from "../../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import type { NavigateToCatalog } from "../../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
@ -24,6 +20,14 @@ import navigateToCatalogInjectable from "../../../../common/front-end-routing/ro
import catalogRouteInjectable from "../../../../common/front-end-routing/routes/catalog/catalog-route.injectable"; import catalogRouteInjectable from "../../../../common/front-end-routing/routes/catalog/catalog-route.injectable";
import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; import routeIsActiveInjectable from "../../../routes/route-is-active.injectable";
import { UpdateButton } from "../../update-button"; import { UpdateButton } from "../../update-button";
import topBarPrevEnabledInjectable from "./prev-enabled.injectable";
import topBarNextEnabledInjectable from "./next-enabled.injectable";
import openAppContextMenuInjectable from "./open-app-context-menu.injectable";
import goBackInjectable from "./go-back.injectable";
import goForwardInjectable from "./go-forward.injectable";
import closeWindowInjectable from "./close-window.injectable";
import maximizeWindowInjectable from "./maximize-window.injectable";
import toggleMaximizeWindowInjectable from "./toggle-maximize-window.injectable";
interface Dependencies { interface Dependencies {
navigateToCatalog: NavigateToCatalog; navigateToCatalog: NavigateToCatalog;
@ -31,62 +35,43 @@ interface Dependencies {
items: IComputedValue<TopBarRegistration[]>; items: IComputedValue<TopBarRegistration[]>;
isWindows: boolean; isWindows: boolean;
isLinux: boolean; isLinux: boolean;
prevEnabled: IComputedValue<Boolean>;
nextEnabled: IComputedValue<Boolean>;
openAppContextMenu: () => void;
goBack: () => void;
goForward: () => void;
minimizeWindow: () => void;
toggleMaximizeWindow: () => void;
closeWindow: () => void;
} }
const prevEnabled = observable.box(false);
const nextEnabled = observable.box(false);
ipcRendererOn("history:can-go-back", (event, state: boolean) => {
prevEnabled.set(state);
});
ipcRendererOn("history:can-go-forward", (event, state: boolean) => {
nextEnabled.set(state);
});
const NonInjectedTopBar = observer(({ const NonInjectedTopBar = observer(({
items, items,
navigateToCatalog, navigateToCatalog,
catalogRouteIsActive, catalogRouteIsActive,
isWindows, isWindows,
isLinux, isLinux,
prevEnabled,
nextEnabled,
openAppContextMenu,
goBack,
goForward,
closeWindow,
minimizeWindow,
toggleMaximizeWindow,
}: Dependencies) => { }: Dependencies) => {
const elem = useRef<HTMLDivElement | null>(null); const elem = useRef<HTMLDivElement | null>(null);
const openAppContextMenu = () => {
emitOpenAppMenuAsContextMenu();
};
const goHome = () => { const goHome = () => {
navigateToCatalog(); navigateToCatalog();
}; };
const goBack = () => {
requestWindowAction(WindowAction.GO_BACK);
};
const goForward = () => {
requestWindowAction(WindowAction.GO_FORWARD);
};
const windowSizeToggle = (evt: React.MouseEvent) => { const windowSizeToggle = (evt: React.MouseEvent) => {
if (elem.current === evt.target) { if (elem.current === evt.target) {
toggleMaximize(); toggleMaximizeWindow();
} }
}; };
const minimizeWindow = () => {
requestWindowAction(WindowAction.MINIMIZE);
};
const toggleMaximize = () => {
requestWindowAction(WindowAction.TOGGLE_MAXIMIZE);
};
const closeWindow = () => {
requestWindowAction(WindowAction.CLOSE);
};
useEffect(() => watchHistoryState(), []); useEffect(() => watchHistoryState(), []);
return ( return (
@ -147,14 +132,14 @@ const NonInjectedTopBar = observer(({
width="10" width="10"
height="1" height="1"
x="1" x="1"
y="9" y="9"
/> />
</svg> </svg>
</div> </div>
<div <div
className={styles.maximize} className={styles.maximize}
data-testid="window-maximize" data-testid="window-maximize"
onClick={toggleMaximize} onClick={toggleMaximizeWindow}
> >
<svg shapeRendering="crispEdges" viewBox="0 0 12 12"> <svg shapeRendering="crispEdges" viewBox="0 0 12 12">
<rect <rect
@ -163,7 +148,7 @@ const NonInjectedTopBar = observer(({
x="1.5" x="1.5"
y="1.5" y="1.5"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
/> />
</svg> </svg>
</div> </div>
@ -193,23 +178,23 @@ const renderRegisteredItems = (items: TopBarRegistration[]) => (
}) })
); );
export const TopBar = withInjectables<Dependencies>( export const TopBar = withInjectables<Dependencies>(NonInjectedTopBar, {
NonInjectedTopBar, getProps: (di) => ({
{ navigateToCatalog: di.inject(navigateToCatalogInjectable),
getProps: (di) => { items: di.inject(topBarItemsInjectable),
const catalogRoute = di.inject(catalogRouteInjectable); isLinux: di.inject(isLinuxInjectable),
isWindows: di.inject(isWindowsInjectable),
return { prevEnabled: di.inject(topBarPrevEnabledInjectable),
navigateToCatalog: di.inject(navigateToCatalogInjectable), nextEnabled: di.inject(topBarNextEnabledInjectable),
items: di.inject(topBarItemsInjectable), catalogRouteIsActive: di.inject(
isLinux: di.inject(isLinuxInjectable), routeIsActiveInjectable,
isWindows: di.inject(isWindowsInjectable), di.inject(catalogRouteInjectable),
),
catalogRouteIsActive: di.inject( openAppContextMenu: di.inject(openAppContextMenuInjectable),
routeIsActiveInjectable, goBack: di.inject(goBackInjectable),
catalogRoute, goForward: di.inject(goForwardInjectable),
), closeWindow: di.inject(closeWindowInjectable),
}; minimizeWindow: di.inject(maximizeWindowInjectable),
}, toggleMaximizeWindow: di.inject(toggleMaximizeWindowInjectable),
}, }),
); });