From 54cf5c55a8b544f9fc2711ffc9cdd52f479aa9b0 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 6 Mar 2024 12:24:44 +0000 Subject: [PATCH 1/3] Add tests of MethodModelingViewProvider viewLoaded --- .../src/model-editor/modeling-store.ts | 4 +- .../model-editor/modelingEventsMock.ts | 3 + .../model-editor/modelingStoreMock.ts | 5 +- .../method-modeling-view-provider.test.ts | 212 ++++++++++++++++++ 4 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts diff --git a/extensions/ql-vscode/src/model-editor/modeling-store.ts b/extensions/ql-vscode/src/model-editor/modeling-store.ts index 643b0b2a018..3622759373b 100644 --- a/extensions/ql-vscode/src/model-editor/modeling-store.ts +++ b/extensions/ql-vscode/src/model-editor/modeling-store.ts @@ -22,7 +22,7 @@ interface InternalDbModelingState { modelEvaluationRun: ModelEvaluationRun | undefined; } -interface DbModelingState { +export interface DbModelingState { readonly databaseItem: DatabaseItem; readonly methods: readonly Method[]; readonly hideModeledMethods: boolean; @@ -36,7 +36,7 @@ interface DbModelingState { readonly modelEvaluationRun: ModelEvaluationRun | undefined; } -interface SelectedMethodDetails { +export interface SelectedMethodDetails { readonly databaseItem: DatabaseItem; readonly method: Method; readonly usage: Usage | undefined; diff --git a/extensions/ql-vscode/test/__mocks__/model-editor/modelingEventsMock.ts b/extensions/ql-vscode/test/__mocks__/model-editor/modelingEventsMock.ts index e307cd1efb3..04db82f8e0b 100644 --- a/extensions/ql-vscode/test/__mocks__/model-editor/modelingEventsMock.ts +++ b/extensions/ql-vscode/test/__mocks__/model-editor/modelingEventsMock.ts @@ -3,6 +3,7 @@ import type { ModelingEvents } from "../../../src/model-editor/modeling-events"; export function createMockModelingEvents({ onActiveDbChanged = jest.fn(), + onDbOpened = jest.fn(), onDbClosed = jest.fn(), onSelectedMethodChanged = jest.fn(), onMethodsChanged = jest.fn(), @@ -16,6 +17,7 @@ export function createMockModelingEvents({ onModelEvaluationRunChanged = jest.fn(), }: { onActiveDbChanged?: ModelingEvents["onActiveDbChanged"]; + onDbOpened?: ModelingEvents["onDbOpened"]; onDbClosed?: ModelingEvents["onDbClosed"]; onSelectedMethodChanged?: ModelingEvents["onSelectedMethodChanged"]; onMethodsChanged?: ModelingEvents["onMethodsChanged"]; @@ -30,6 +32,7 @@ export function createMockModelingEvents({ } = {}): ModelingEvents { return mockedObject({ onActiveDbChanged, + onDbOpened, onDbClosed, onSelectedMethodChanged, onMethodsChanged, diff --git a/extensions/ql-vscode/test/__mocks__/model-editor/modelingStoreMock.ts b/extensions/ql-vscode/test/__mocks__/model-editor/modelingStoreMock.ts index 7cbad68c62c..8bfbd28f43d 100644 --- a/extensions/ql-vscode/test/__mocks__/model-editor/modelingStoreMock.ts +++ b/extensions/ql-vscode/test/__mocks__/model-editor/modelingStoreMock.ts @@ -3,17 +3,20 @@ import type { ModelingStore } from "../../../src/model-editor/modeling-store"; export function createMockModelingStore({ initializeStateForDb = jest.fn(), - getStateForActiveDb = jest.fn(), + getStateForActiveDb = jest.fn().mockReturnValue(undefined), + getSelectedMethodDetails = jest.fn().mockReturnValue(undefined), getModelEvaluationRun = jest.fn(), updateModelEvaluationRun = jest.fn(), }: { initializeStateForDb?: ModelingStore["initializeStateForDb"]; getStateForActiveDb?: ModelingStore["getStateForActiveDb"]; + getSelectedMethodDetails?: ModelingStore["getSelectedMethodDetails"]; getModelEvaluationRun?: ModelingStore["getModelEvaluationRun"]; updateModelEvaluationRun?: ModelingStore["updateModelEvaluationRun"]; } = {}): ModelingStore { return mockedObject({ initializeStateForDb, + getSelectedMethodDetails, getStateForActiveDb, getModelEvaluationRun, updateModelEvaluationRun, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts new file mode 100644 index 00000000000..051c626792d --- /dev/null +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts @@ -0,0 +1,212 @@ +import type { + Uri, + Webview, + WebviewView, + WebviewViewResolveContext, +} from "vscode"; +import { CancellationTokenSource, EventEmitter } from "vscode"; +import type { ModelConfigListener } from "../../../../../src/config"; +import { MethodModelingViewProvider } from "../../../../../src/model-editor/method-modeling/method-modeling-view-provider"; +import { createMockApp } from "../../../../__mocks__/appMock"; +import { createMockModelingEvents } from "../../../../__mocks__/model-editor/modelingEventsMock"; +import { createMockModelingStore } from "../../../../__mocks__/model-editor/modelingStoreMock"; +import { mockedObject } from "../../../../mocked-object"; +import type { FromMethodModelingMessage } from "../../../../../src/common/interface-types"; +import { DisposableObject } from "../../../../../src/common/disposable-object"; +import type { ModelingEvents } from "../../../../../src/model-editor/modeling-events"; +import type { + DbModelingState, + SelectedMethodDetails, +} from "../../../../../src/model-editor/modeling-store"; +import { mockDatabaseItem } from "../../../utils/mocking.helpers"; +import { + createMethod, + createUsage, +} from "../../../../factories/model-editor/method-factories"; + +describe("method modeling view provider", () => { + // Modeling store + let getStateForActiveDb: jest.Mock; + let getSelectedMethodDetails: jest.Mock< + SelectedMethodDetails | undefined, + [] + >; + + // Modeling events + let selectedMethodChangedEventEmitter: ModelingEvents["onSelectedMethodChangedEventEmitter"]; + let dbOpenedEventEmitter: ModelingEvents["onDbOpenedEventEmitter"]; + + // View provider + let viewProvider: MethodModelingViewProvider; + let onDidReceiveMessage: (msg: FromMethodModelingMessage) => Promise; + let postMessage: (message: unknown) => Promise; + + beforeEach(async () => { + const app = createMockApp({}); + + getStateForActiveDb = jest.fn().mockReturnValue(undefined); + getSelectedMethodDetails = jest.fn().mockReturnValue(undefined); + const modelingStore = createMockModelingStore({ + getStateForActiveDb, + getSelectedMethodDetails, + }); + + selectedMethodChangedEventEmitter = new EventEmitter(); + dbOpenedEventEmitter = new EventEmitter(); + const modelingEvents = createMockModelingEvents({ + onSelectedMethodChanged: selectedMethodChangedEventEmitter.event, + onDbOpened: dbOpenedEventEmitter.event, + }); + + const modelConfigListener = mockedObject({ + showTypeModels: true, + onDidChangeConfiguration: jest.fn(), + }); + + viewProvider = new MethodModelingViewProvider( + app, + modelingStore, + modelingEvents, + modelConfigListener, + ); + + postMessage = jest.fn().mockResolvedValue(true); + const webview: Webview = { + options: {}, + html: "", + onDidReceiveMessage: (listener) => { + onDidReceiveMessage = listener; + return new DisposableObject(); + }, + postMessage, + asWebviewUri: (uri: Uri) => uri, + cspSource: "", + }; + + const webviewView = mockedObject({ + webview, + onDidDispose: jest.fn(), + }); + + viewProvider.resolveWebviewView( + webviewView, + mockedObject({}), + new CancellationTokenSource().token, + ); + + expect(onDidReceiveMessage).toBeDefined(); + }); + + it("should load webview when no active DB", async () => { + await onDidReceiveMessage({ + t: "viewLoaded", + viewName: MethodModelingViewProvider.viewType, + }); + + expect(postMessage).toHaveBeenCalledTimes(1); + expect(postMessage).toHaveBeenCalledWith({ + t: "setMethodModelingPanelViewState", + viewState: { + language: undefined, + modelConfig: { + showTypeModels: true, + }, + }, + }); + }); + + it("should load webview when active DB but no selected method", async () => { + const dbModelingState = mockedObject({ + databaseItem: mockDatabaseItem({ + language: "java", + }), + }); + getStateForActiveDb.mockReturnValue(dbModelingState); + + await onDidReceiveMessage({ + t: "viewLoaded", + viewName: MethodModelingViewProvider.viewType, + }); + + expect(postMessage).toHaveBeenCalledTimes(3); + expect(postMessage).toHaveBeenNthCalledWith(1, { + t: "setMethodModelingPanelViewState", + viewState: { + language: undefined, + modelConfig: { + showTypeModels: true, + }, + }, + }); + expect(postMessage).toHaveBeenNthCalledWith(2, { + t: "setInModelingMode", + inModelingMode: true, + }); + expect(postMessage).toHaveBeenNthCalledWith(3, { + t: "setMethodModelingPanelViewState", + viewState: { + language: "java", + modelConfig: { + showTypeModels: true, + }, + }, + }); + }); + + it("should load webview when active DB and a selected method", async () => { + const dbModelingState = mockedObject({ + databaseItem: mockDatabaseItem({ + language: "java", + }), + }); + getStateForActiveDb.mockReturnValue(dbModelingState); + + const selectedMethodDetails: SelectedMethodDetails = { + databaseItem: dbModelingState.databaseItem, + method: createMethod(), + usage: createUsage(), + modeledMethods: [], + isModified: false, + isInProgress: false, + processedByAutoModel: false, + }; + getSelectedMethodDetails.mockReturnValue(selectedMethodDetails); + + await onDidReceiveMessage({ + t: "viewLoaded", + viewName: MethodModelingViewProvider.viewType, + }); + + expect(postMessage).toHaveBeenCalledTimes(4); + expect(postMessage).toHaveBeenNthCalledWith(1, { + t: "setMethodModelingPanelViewState", + viewState: { + language: undefined, + modelConfig: { + showTypeModels: true, + }, + }, + }); + expect(postMessage).toHaveBeenNthCalledWith(2, { + t: "setInModelingMode", + inModelingMode: true, + }); + expect(postMessage).toHaveBeenNthCalledWith(3, { + t: "setMethodModelingPanelViewState", + viewState: { + language: "java", + modelConfig: { + showTypeModels: true, + }, + }, + }); + expect(postMessage).toHaveBeenNthCalledWith(4, { + t: "setSelectedMethod", + method: selectedMethodDetails.method, + modeledMethods: selectedMethodDetails.modeledMethods, + isModified: selectedMethodDetails.isModified, + isInProgress: selectedMethodDetails.isInProgress, + processedByAutoModel: selectedMethodDetails.processedByAutoModel, + }); + }); +}); From dbd5d0f406336f420b03d210adaba271108d4bc2 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 11 Mar 2024 11:02:37 +0000 Subject: [PATCH 2/3] Remove unused arguments from resolveWebviewView --- .../vscode/abstract-webview-view-provider.ts | 13 ++----------- .../method-modeling-view-provider.test.ts | 15 +++------------ 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/extensions/ql-vscode/src/common/vscode/abstract-webview-view-provider.ts b/extensions/ql-vscode/src/common/vscode/abstract-webview-view-provider.ts index d82ede247b2..37181e5c549 100644 --- a/extensions/ql-vscode/src/common/vscode/abstract-webview-view-provider.ts +++ b/extensions/ql-vscode/src/common/vscode/abstract-webview-view-provider.ts @@ -1,9 +1,4 @@ -import type { - CancellationToken, - WebviewView, - WebviewViewProvider, - WebviewViewResolveContext, -} from "vscode"; +import type { WebviewView, WebviewViewProvider } from "vscode"; import { Uri } from "vscode"; import type { WebviewKind, WebviewMessage } from "./webview-html"; import { getHtmlForWebview } from "./webview-html"; @@ -28,11 +23,7 @@ export abstract class AbstractWebviewViewProvider< * This is called when a view first becomes visible. This may happen when the view is * first loaded or when the user hides and then shows a view again. */ - public resolveWebviewView( - webviewView: WebviewView, - _context: WebviewViewResolveContext, - _token: CancellationToken, - ) { + public resolveWebviewView(webviewView: WebviewView) { webviewView.webview.options = { enableScripts: true, localResourceRoots: [Uri.file(this.app.extensionPath)], diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts index 051c626792d..aae9175a49b 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts @@ -1,10 +1,5 @@ -import type { - Uri, - Webview, - WebviewView, - WebviewViewResolveContext, -} from "vscode"; -import { CancellationTokenSource, EventEmitter } from "vscode"; +import type { Uri, Webview, WebviewView } from "vscode"; +import { EventEmitter } from "vscode"; import type { ModelConfigListener } from "../../../../../src/config"; import { MethodModelingViewProvider } from "../../../../../src/model-editor/method-modeling/method-modeling-view-provider"; import { createMockApp } from "../../../../__mocks__/appMock"; @@ -88,11 +83,7 @@ describe("method modeling view provider", () => { onDidDispose: jest.fn(), }); - viewProvider.resolveWebviewView( - webviewView, - mockedObject({}), - new CancellationTokenSource().token, - ); + viewProvider.resolveWebviewView(webviewView); expect(onDidReceiveMessage).toBeDefined(); }); From 18f9b5fe15b79eed659dea919e42a5de412af2f7 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 11 Mar 2024 11:04:58 +0000 Subject: [PATCH 3/3] Use jest.MockedFunction --- .../method-modeling-view-provider.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts index aae9175a49b..6e2c555f8bf 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/method-modeling/method-modeling-view-provider.test.ts @@ -11,6 +11,7 @@ import { DisposableObject } from "../../../../../src/common/disposable-object"; import type { ModelingEvents } from "../../../../../src/model-editor/modeling-events"; import type { DbModelingState, + ModelingStore, SelectedMethodDetails, } from "../../../../../src/model-editor/modeling-store"; import { mockDatabaseItem } from "../../../utils/mocking.helpers"; @@ -21,10 +22,11 @@ import { describe("method modeling view provider", () => { // Modeling store - let getStateForActiveDb: jest.Mock; - let getSelectedMethodDetails: jest.Mock< - SelectedMethodDetails | undefined, - [] + let getStateForActiveDb: jest.MockedFunction< + ModelingStore["getStateForActiveDb"] + >; + let getSelectedMethodDetails: jest.MockedFunction< + ModelingStore["getSelectedMethodDetails"] >; // Modeling events