diff --git a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts index c58152716f..8961ea8424 100644 --- a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts +++ b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts @@ -33,7 +33,7 @@ export const asLegacyGlobalObjectForExtensionApiWithModifications = < ...instantiationParameter, ); - const propertyValue = instance[propertyName] ?? otherFields[propertyName as any]; + const propertyValue = instance[propertyName] ?? otherFields[propertyName as any](); if (typeof propertyValue === "function") { return function (...args: any[]) { diff --git a/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx b/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx index 762e915a63..28ed8a8563 100644 --- a/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx +++ b/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx @@ -37,10 +37,6 @@ jest.mock("electron", () => ({ }, })); -const getComponent = (model: LogTabViewModel) => ( - -); - function mockLogTabViewModel(tabId: TabId, deps: Partial): LogTabViewModel { return new LogTabViewModel(tabId, { getLogs: jest.fn(), @@ -54,6 +50,7 @@ function mockLogTabViewModel(tabId: TabId, deps: Partial", () => { it("renders w/o errors", () => { const model = getOnePodViewModel("foobar"); - const { container } = render(getComponent(model)); + const { container } = render(); expect(container).toBeInstanceOf(HTMLElement); }); it("renders proper namespace", async () => { const model = getOnePodViewModel("foobar"); - const { findByTestId } = render(getComponent(model)); + const { findByTestId } = render(); const ns = await findByTestId("namespace-badge"); expect(ns).toHaveTextContent("default"); @@ -159,7 +156,7 @@ describe("", () => { it("renders proper selected items within dropdowns", async () => { const model = getOnePodViewModel("foobar"); - const { findByText } = render(getComponent(model)); + const { findByText } = render(); expect(await findByText("dockerExporter")).toBeInTheDocument(); expect(await findByText("docker-exporter")).toBeInTheDocument(); @@ -167,7 +164,7 @@ describe("", () => { it("renders sibling pods in dropdown", async () => { const model = getFewPodsTabData("foobar"); - const { container, findByText } = render(getComponent(model)); + const { container, findByText } = render(); selectEvent.openMenu(container.querySelector(".pod-selector")); expect(await findByText("deploymentPod2", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument(); @@ -176,7 +173,7 @@ describe("", () => { it("renders sibling containers in dropdown", async () => { const model = getFewPodsTabData("foobar"); - const { findByText, container } = render(getComponent(model)); + const { findByText, container } = render(); selectEvent.openMenu(container.querySelector(".container-selector")); expect(await findByText("node-exporter-1")).toBeInTheDocument(); @@ -186,7 +183,7 @@ describe("", () => { it("renders pod owner as dropdown title", async () => { const model = getFewPodsTabData("foobar"); - const { findByText, container } = render(getComponent(model)); + const { findByText, container } = render(); selectEvent.openMenu(container.querySelector(".pod-selector")); expect(await findByText("super-deployment")).toBeInTheDocument(); @@ -195,7 +192,7 @@ describe("", () => { it("updates tab name if selected pod changes", async () => { const updateTabName = jest.fn(); const model = getFewPodsTabData("foobar", { updateTabName }); - const { findByText, container } = render(getComponent(model)); + const { findByText, container } = render(); selectEvent.openMenu(container.querySelector(".pod-selector")); diff --git a/src/renderer/components/dock/logs/__test__/log-search.test.tsx b/src/renderer/components/dock/logs/__test__/log-search.test.tsx index a72859300e..8aa75799bf 100644 --- a/src/renderer/components/dock/logs/__test__/log-search.test.tsx +++ b/src/renderer/components/dock/logs/__test__/log-search.test.tsx @@ -3,4 +3,101 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -export {}; +import React from "react"; +import { screen } from "@testing-library/react"; +import { Pod } from "../../../../../common/k8s-api/endpoints"; +import { dockerPod } from "./pod.mock"; +import { getDiForUnitTesting } from "../../../../getDiForUnitTesting"; +import type { DiRender } from "../../../test-utils/renderFor"; +import { renderFor } from "../../../test-utils/renderFor"; +import { LogTabViewModel, LogTabViewModelDependencies } from "../logs-view-model"; +import type { TabId } from "../../dock/store"; +import { LogSearch } from "../search"; +import userEvent from "@testing-library/user-event"; +import { SearchStore } from "../../../../search-store/search-store"; + +function mockLogTabViewModel(tabId: TabId, deps: Partial): LogTabViewModel { + return new LogTabViewModel(tabId, { + getLogs: jest.fn(), + getLogsWithoutTimestamps: jest.fn(), + getTimestampSplitLogs: jest.fn(), + getLogTabData: jest.fn(), + setLogTabData: jest.fn(), + loadLogs: jest.fn(), + reloadLogs: jest.fn(), + updateTabName: jest.fn(), + stopLoadingLogs: jest.fn(), + getPodById: jest.fn(), + getPodsByOwnerId: jest.fn(), + createSearchStore: () => new SearchStore(), + ...deps, + }); +} + +const getOnePodViewModel = (tabId: TabId, deps: Partial = {}): LogTabViewModel => { + const selectedPod = new Pod(dockerPod); + + return mockLogTabViewModel(tabId, { + getLogTabData: () => ({ + selectedPodId: selectedPod.getId(), + selectedContainer: selectedPod.getContainers()[0].name, + namespace: selectedPod.getNs(), + showPrevious: false, + showTimestamps: false, + }), + getPodById: (id) => { + if (id === selectedPod.getId()) { + return selectedPod; + } + + return undefined; + }, + ...deps, + }); +}; + +describe("LogSearch tests", () => { + let render: DiRender; + + beforeEach(async () => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + render = renderFor(di); + + await di.runSetups(); + }); + + it("renders w/o errors", () => { + const model = getOnePodViewModel("foobar"); + const { container } = render( + , + ); + + expect(container).toBeInstanceOf(HTMLElement); + }); + + it("should scroll to new active overlay when clicking the previous button", async () => { + const scrollToOverlay = jest.fn(); + const model = getOnePodViewModel("foobar", { + getLogsWithoutTimestamps: () => [ + "hello", + "world", + ], + }); + + render( + , + ); + + userEvent.click(await screen.findByPlaceholderText("Search...")); + userEvent.keyboard("o"); + userEvent.click(await screen.findByText("keyboard_arrow_up")); + expect(scrollToOverlay).toBeCalled(); + }); +}); diff --git a/src/renderer/components/dock/logs/logs-view-model.injectable.ts b/src/renderer/components/dock/logs/logs-view-model.injectable.ts index 93e2008678..71264ab129 100644 --- a/src/renderer/components/dock/logs/logs-view-model.injectable.ts +++ b/src/renderer/components/dock/logs/logs-view-model.injectable.ts @@ -15,6 +15,7 @@ import setLogTabDataInjectable from "./set-log-tab-data.injectable"; import updateTabNameInjectable from "./update-tab-name.injectable"; import stopLoadingLogsInjectable from "./stop-loading-logs.injectable"; import { podsStore } from "../../+workloads-pods/pods.store"; +import createSearchStoreInjectable from "../../../search-store/create-search-store.injectable"; export interface InstantiateArgs { tabId: TabId; @@ -33,6 +34,7 @@ const logsViewModelInjectable = getInjectable({ stopLoadingLogs: di.inject(stopLoadingLogsInjectable), getPodById: id => podsStore.getById(id), getPodsByOwnerId: id => podsStore.getPodsByOwnerId(id), + createSearchStore: di.inject(createSearchStoreInjectable), }), lifecycle: lifecycleEnum.transient, }); diff --git a/src/renderer/components/dock/logs/logs-view-model.ts b/src/renderer/components/dock/logs/logs-view-model.ts index 02185c7152..3af2ea8612 100644 --- a/src/renderer/components/dock/logs/logs-view-model.ts +++ b/src/renderer/components/dock/logs/logs-view-model.ts @@ -5,7 +5,7 @@ import type { LogTabData } from "./tab-store"; import { computed, IComputedValue } from "mobx"; import type { TabId } from "../dock/store"; -import { SearchStore } from "../../../search-store/search-store"; +import type { SearchStore } from "../../../search-store/search-store"; import type { Pod } from "../../../../common/k8s-api/endpoints"; export interface LogTabViewModelDependencies { @@ -20,6 +20,7 @@ export interface LogTabViewModelDependencies { stopLoadingLogs: (tabId: TabId) => void; getPodById: (id: string) => Pod | undefined; getPodsByOwnerId: (id: string) => Pod[]; + createSearchStore: () => SearchStore; } export class LogTabViewModel { @@ -51,7 +52,7 @@ export class LogTabViewModel { return this.dependencies.getPodById(data.selectedPodId); }); - readonly searchStore = new SearchStore(); + readonly searchStore = this.dependencies.createSearchStore(); updateLogTabData = (partialData: Partial) => { this.dependencies.setLogTabData(this.tabId, { ...this.logTabData.get(), ...partialData }); diff --git a/src/renderer/components/dock/terminal/send-command.injectable.ts b/src/renderer/components/dock/terminal/send-command.injectable.ts index d4f2d16e09..5428b27ac1 100644 --- a/src/renderer/components/dock/terminal/send-command.injectable.ts +++ b/src/renderer/components/dock/terminal/send-command.injectable.ts @@ -41,27 +41,25 @@ async function sendCommand({ selectTab, createTerminalTab, getTerminalApi }: Dep if (tabId) { selectTab(tabId); } else { - const tab = createTerminalTab(); - - tabId = tab.id; - await when(() => Boolean(getTerminalApi(tab.id))); - - const shellIsReady = when(() => getTerminalApi(tab.id).isReady); - const notifyVeryLong = setTimeout(() => { - shellIsReady.cancel(); - Notifications.info( - "If terminal shell is not ready please check your shell init files, if applicable.", - { - timeout: 4_000, - }, - ); - }, 10_000); - - await shellIsReady.catch(noop); - clearTimeout(notifyVeryLong); + tabId = createTerminalTab().id; } + await when(() => Boolean(getTerminalApi(tabId))); + const terminalApi = getTerminalApi(tabId); + const shellIsReady = when(() =>terminalApi.isReady); + const notifyVeryLong = setTimeout(() => { + shellIsReady.cancel(); + Notifications.info( + "If terminal shell is not ready please check your shell init files, if applicable.", + { + timeout: 4_000, + }, + ); + }, 10_000); + + await shellIsReady.catch(noop); + clearTimeout(notifyVeryLong); if (terminalApi) { if (options.enter) { diff --git a/src/renderer/components/input/input.tsx b/src/renderer/components/input/input.tsx index 36461fd684..9e961201b4 100644 --- a/src/renderer/components/input/input.tsx +++ b/src/renderer/components/input/input.tsx @@ -353,6 +353,7 @@ export class Input extends React.Component { dirty: _dirty, // excluded from passing to input-element defaultValue, trim, + blurOnEnter, ...inputProps } = this.props; const { focused, dirty, valid, validating, errors } = this.state; diff --git a/src/renderer/search-store/create-search-store.injectable.ts b/src/renderer/search-store/create-search-store.injectable.ts new file mode 100644 index 0000000000..088a837f8c --- /dev/null +++ b/src/renderer/search-store/create-search-store.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { SearchStore } from "./search-store"; + +const createSearchStoreInjectable = getInjectable({ + instantiate: () => () => new SearchStore(), + lifecycle: lifecycleEnum.singleton, +}); + +export default createSearchStoreInjectable;