diff --git a/projects/yeoman-ui/packages/backend/package.json b/projects/yeoman-ui/packages/backend/package.json index 14e29a0f0..b68f7c6b9 100644 --- a/projects/yeoman-ui/packages/backend/package.json +++ b/projects/yeoman-ui/packages/backend/package.json @@ -1,7 +1,7 @@ { "name": "yeoman-ui", "displayName": "Application Wizard", - "version": "1.27.1", + "version": "1.27.2", "private": true, "description": "Provides rich user experience for Yeoman generators.", "categories": [ diff --git a/projects/yeoman-ui/packages/backend/src/messages.ts b/projects/yeoman-ui/packages/backend/src/messages.ts index 2975ba05a..cf90a5576 100644 --- a/projects/yeoman-ui/packages/backend/src/messages.ts +++ b/projects/yeoman-ui/packages/backend/src/messages.ts @@ -30,10 +30,6 @@ export default { `The '${artifactName}' project has been generated.`, show_progress_button: "Open Output View", show_progress_message: "Generating...", - progress_preparing: "Preparing...", - progress_writing_files: "Creating project files...", - progress_installing: "Installing dependencies...", - progress_finalising: "Finalising...", add_to_workspace: "Open the project in a multi-root workspace", open_in_a_new_workspace: "Open the project in a stand-alone", create_and_close: "Create the project and close it for future use", diff --git a/projects/yeoman-ui/packages/backend/src/vscode-youi-events.ts b/projects/yeoman-ui/packages/backend/src/vscode-youi-events.ts index c9c569763..9a0c4bff9 100644 --- a/projects/yeoman-ui/packages/backend/src/vscode-youi-events.ts +++ b/projects/yeoman-ui/packages/backend/src/vscode-youi-events.ts @@ -25,7 +25,6 @@ import { getFileSchemeWorkspaceFolders } from "./utils/workspaceFolders.js"; const { isEmpty, isNil, set } = lodash; -// App Wizard wrapper that delegates to VSCodeYouiEvents class YoUiAppWizard extends AppWizard { constructor(private readonly events: VSCodeYouiEvents) { super(); @@ -61,13 +60,7 @@ export class VSCodeYouiEvents implements YouiEvents { private readonly rpc: IRpc; private webviewPanel: WebviewPanel; private readonly messages: any; - private resolveFunc: (() => void) | undefined; - private progressReporter: { - report(value: { message?: string; increment?: number }): void; - } | null = null; - private currentProjectName: string | undefined; - private phaseStartTime: number = 0; - private currentPhase: "writing" | "install" | "end" | null = null; + private resolveFunc: any; public output: GeneratorOutput; private readonly logger: IChildLogger; private readonly appWizard: AppWizard; @@ -95,103 +88,27 @@ export class VSCodeYouiEvents implements YouiEvents { void this.rpc.invoke("setBanner", [bannerProps]); } - public async doGeneratorDone( + public doGeneratorDone( success: boolean, message: string, selectedWorkspace: string, type: string, targetFolderPath?: string - ): Promise { - // Show "Finalising..." before closing - if (this.progressReporter) { - this.progressReporter.report({ - message: this.messages.progress_finalising, - }); - // Add a brief delay so "Finalising..." is visible to users - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - - this.resolveInstallingProgress(); + ): void { set(this.webviewPanel, Constants.GENERATOR_COMPLETED, success); this.doClose(); - return this.showDoneMessage( + void this.showDoneMessage( success, message, selectedWorkspace, type, - targetFolderPath, - true // Skip resolving progress since we already did it + targetFolderPath ); } - public doGeneratorProgress( - projectName: string | undefined, - phase: "writing" | "install" | "end", - showProgress: boolean = false - ): void { - // Check VS Code setting (default: true) - // Note: This setting is not declared in yeoman-ui's package.json - consumers - // (e.g., application-modeler, app-generator) declare it in their package.json - // if they want users to control it. The true default is safe - actual gating - // happens via the per-generator showProgress option passed by consumers. - const config = vscode.workspace.getConfiguration(); - const settingEnabled = config.get( - "ApplicationWizard.showGeneratorProgress", - true - ); - - // Only show if both the setting is enabled AND the generator opts in - if (!settingEnabled || !showProgress) { - return; // Don't show progress notification if disabled or not opted in - } - - // Map phases to localized messages - const phaseMessages = { - writing: this.messages.progress_writing_files, - install: this.messages.progress_installing, - end: this.messages.progress_finalising, - }; - - // Minimum duration for each phase (milliseconds) - const MIN_DURATIONS = { - writing: 2000, // 2 seconds - install: 0, // No minimum - let npm install take as long as it needs - end: 1000, // 1 second - }; - - const message = phaseMessages[phase]; - - // If this is the first phase (writing) AND no progress notification exists yet - if (phase === "writing" && !this.progressReporter) { - // Close the webview panel (showing the question form) before showing progress - this.doClose(); - this.currentPhase = phase; - this.phaseStartTime = Date.now(); - this.showInstallMessage(projectName, message); - } else if (this.progressReporter) { - // Calculate time elapsed in current phase - const elapsed = Date.now() - this.phaseStartTime; - const minDuration = this.currentPhase - ? MIN_DURATIONS[this.currentPhase] - : 0; - const remainingTime = Math.max(0, minDuration - elapsed); - - if (remainingTime > 0) { - // Wait for minimum duration before showing next phase - setTimeout(() => { - if (this.progressReporter) { - this.progressReporter.report({ message }); - this.currentPhase = phase; - this.phaseStartTime = Date.now(); - } - }, remainingTime); - } else { - // Minimum duration already elapsed, update immediately - this.progressReporter.report({ message }); - this.currentPhase = phase; - this.phaseStartTime = Date.now(); - } - } + public doGeneratorInstall(): void { + this.doClose(); + this.showInstallMessage(); } public getAppWizard(): AppWizard { @@ -265,36 +182,16 @@ export class VSCodeYouiEvents implements YouiEvents { } } - private showInstallMessage( - projectName?: string, - initialMessage: string = this.messages.progress_preparing - ): void { - // Store project name for later use in success message - this.currentProjectName = projectName; - - // Use "Generating {projectName}" as the title - const title = projectName - ? `Generating ${projectName}` - : "Application Generator"; - + private showInstallMessage(): void { void vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, - title: title, - cancellable: false, + title: "Installing dependencies...", }, - async (progress) => { - // Store the progress reporter so we can update it - this.progressReporter = progress; - progress.report({ message: initialMessage }); - - // Keep the notification open until generation completes - await new Promise((resolve) => { + async () => { + await new Promise((resolve) => { this.resolveFunc = resolve; }); - - // Clean up the progress reporter - this.progressReporter = null; } ); } @@ -310,12 +207,9 @@ export class VSCodeYouiEvents implements YouiEvents { errorMmessage: string, selectedWorkspace: string, type: string, - targetFolderPath?: string, - skipResolve: boolean = false + targetFolderPath?: string ): Thenable { - if (!skipResolve) { - this.resolveInstallingProgress(); - } + this.resolveInstallingProgress(); if (success) { if (!isNil(targetFolderPath)) { @@ -425,33 +319,17 @@ export class VSCodeYouiEvents implements YouiEvents { selectedWorkspace: string, type: string ): string { - // Default message with project name if available - let successInfoMessage: string = this.currentProjectName - ? `Project ${this.currentProjectName} has been generated.` - : this.messages.artifact_generated_files; - + let successInfoMessage: string = this.messages.artifact_generated_files; if (type === "project") { - // For project type, use project name and add workspace-specific detail - if (this.currentProjectName) { - if (selectedWorkspace === this.messages.open_in_a_new_workspace) { - successInfoMessage = `Project ${this.currentProjectName} has been generated. The project will be opened in a new workspace.`; - } else if (selectedWorkspace === this.messages.add_to_workspace) { - successInfoMessage = `Project ${this.currentProjectName} has been generated. The project has been added to workspace.`; - } else { - successInfoMessage = `Project ${this.currentProjectName} has been generated.`; - } + if (selectedWorkspace === this.messages.open_in_a_new_workspace) { + successInfoMessage = + this.messages.artifact_generated_project_open_in_a_new_workspace; + } else if (selectedWorkspace === this.messages.add_to_workspace) { + successInfoMessage = + this.messages.artifact_generated_project_add_to_workspace; } else { - // Fallback to original messages if no project name - if (selectedWorkspace === this.messages.open_in_a_new_workspace) { - successInfoMessage = - this.messages.artifact_generated_project_open_in_a_new_workspace; - } else if (selectedWorkspace === this.messages.add_to_workspace) { - successInfoMessage = - this.messages.artifact_generated_project_add_to_workspace; - } else { - successInfoMessage = - this.messages.artifact_generated_project_saved_for_future; - } + successInfoMessage = + this.messages.artifact_generated_project_saved_for_future; } } else if (type === "module") { successInfoMessage = this.messages.artifact_generated_module; diff --git a/projects/yeoman-ui/packages/backend/src/webSocketServer/server-youi-events.ts b/projects/yeoman-ui/packages/backend/src/webSocketServer/server-youi-events.ts index 5d2861634..a8416cfe5 100644 --- a/projects/yeoman-ui/packages/backend/src/webSocketServer/server-youi-events.ts +++ b/projects/yeoman-ui/packages/backend/src/webSocketServer/server-youi-events.ts @@ -30,27 +30,18 @@ export class ServerYouiEvents implements YouiEvents { selectedWorkspace: string, type: string, targetPath = "" - ): Promise { - return this.rpc.invoke("generatorDone", [ + ): void { + void this.rpc.invoke("generatorDone", [ suceeded, message, selectedWorkspace, type, targetPath, - ]) as Promise; + ]); } - public doGeneratorProgress( - projectName: string | undefined, - phase: "writing" | "install" | "end", - showProgress: boolean = false - ): void { - // Only invoke if generator opts in (WebSocket doesn't have VS Code settings) - if (!showProgress) { - return; - } - // WebSocket implementation - invoke RPC method with progress info - void this.rpc.invoke("generatorProgress", [projectName, phase]); + public doGeneratorInstall(): void { + void this.rpc.invoke("generatorInstall"); } public showProgress(): void { diff --git a/projects/yeoman-ui/packages/backend/src/yeomanui.ts b/projects/yeoman-ui/packages/backend/src/yeomanui.ts index f092437a6..f7b82f595 100644 --- a/projects/yeoman-ui/packages/backend/src/yeomanui.ts +++ b/projects/yeoman-ui/packages/backend/src/yeomanui.ts @@ -545,7 +545,7 @@ export class YeomanUI { ); AnalyticsWrapper.updateGeneratorEnded(generatorName); // when targetFolderPath is undefined and no files are generated, send type = '' to get the empty toast message - void this.youiEvents.doGeneratorDone( + this.youiEvents.doGeneratorDone( true, message, selectedWorkspace, @@ -567,7 +567,7 @@ export class YeomanUI { const messagePrefix = `${generatorName} generator failed`; const errorMsg = error?.message || error; this.logError(error, messagePrefix); - void this.youiEvents.doGeneratorDone( + this.youiEvents.doGeneratorDone( false, `${messagePrefix} - ${errorMsg}`, "", @@ -579,49 +579,8 @@ export class YeomanUI { } private onGenInstall(gen: any) { - // Extract project name - const getProjectName = () => { - return ( - _.get(gen, "state.project.name") || - _.get(gen, "options.projectName") || - _.get(gen, "answers.projectName") || - _.get(gen, "answers.app.name") || - _.get(gen, "props.projectName") || - _.get(gen, "props.app.name") - ); - }; - - // Check if generator opts in to progress notifications - const showProgress = _.get(gen, "options.showGeneratorProgress", false); - - // Listen to writing phase - gen.on("method:writing", () => { - const projectName = getProjectName(); - void this.youiEvents.doGeneratorProgress( - projectName, - "writing", - showProgress - ); - }); - - // Listen to install phase gen.on("method:install", () => { - const projectName = getProjectName(); - void this.youiEvents.doGeneratorProgress( - projectName, - "install", - showProgress - ); - }); - - // Listen to end phase - gen.on("method:end", () => { - const projectName = getProjectName(); - void this.youiEvents.doGeneratorProgress( - projectName, - "end", - showProgress - ); + this.youiEvents.doGeneratorInstall(); }); } diff --git a/projects/yeoman-ui/packages/backend/src/youi-adapter.ts b/projects/yeoman-ui/packages/backend/src/youi-adapter.ts index 4f8faa701..5c73f2f92 100644 --- a/projects/yeoman-ui/packages/backend/src/youi-adapter.ts +++ b/projects/yeoman-ui/packages/backend/src/youi-adapter.ts @@ -81,7 +81,7 @@ export class YouiAdapter { try { return await cb(result); // eslint-disable-line @typescript-eslint/await-thenable } catch (err) { - void this.youiEvents.doGeneratorDone( + this.youiEvents.doGeneratorDone( false, get(err, "message", "Template Wizard detected an error"), "", diff --git a/projects/yeoman-ui/packages/backend/src/youi-events.ts b/projects/yeoman-ui/packages/backend/src/youi-events.ts index 419424150..fa134c236 100644 --- a/projects/yeoman-ui/packages/backend/src/youi-events.ts +++ b/projects/yeoman-ui/packages/backend/src/youi-events.ts @@ -7,12 +7,8 @@ export interface YouiEvents { selectedWorkspace: string, type: string, targetFolderPath?: string - ): Thenable; - doGeneratorProgress( - projectName: string | undefined, - phase: "writing" | "install" | "end", - showProgress?: boolean ): void; + doGeneratorInstall(): void; showProgress(message?: string): void; getAppWizard(): AppWizard; executeCommand(id: string, ...args: any[]): Thenable; diff --git a/projects/yeoman-ui/packages/backend/test/vscode-youi-events.spec.ts b/projects/yeoman-ui/packages/backend/test/vscode-youi-events.spec.ts index 46233573e..804cbdc35 100644 --- a/projects/yeoman-ui/packages/backend/test/vscode-youi-events.spec.ts +++ b/projects/yeoman-ui/packages/backend/test/vscode-youi-events.spec.ts @@ -1,8 +1,8 @@ import { vscode } from "./mockUtil.js"; import { expect } from "chai"; import { createSandbox, SinonSandbox, SinonMock } from "sinon"; -import lodash from "lodash"; -import { +import _ from "lodash"; +import type { IMethod, IPromiseCallbacks, IRpc, @@ -14,10 +14,7 @@ import { Constants } from "../src/utils/constants.js"; import * as loggerWrapper from "../src/logger/logger-wrapper.js"; import { VSCodeYouiEvents } from "../src/vscode-youi-events.js"; import { WorkspaceFile } from "../src/utils/workspaceFile.js"; -import { createRequire } from "node:module"; - -const require = createRequire(import.meta.url); -const fs = require("fs"); +import * as fs from "fs"; describe("vscode-youi-events unit test", () => { let events: VSCodeYouiEvents; @@ -31,6 +28,7 @@ describe("vscode-youi-events unit test", () => { let loggerMock: SinonMock; let uriMock: SinonMock; let fsMock: SinonMock; + let wsFileMockUri: any; const testLogger = { debug: () => true, @@ -83,6 +81,7 @@ describe("vscode-youi-events unit test", () => { const generatorOutput = new GeneratorOutput(); before(() => { + sandbox = createSandbox(); loggerWrapper.internalApi.setLogger(testLogger); }); @@ -91,7 +90,6 @@ describe("vscode-youi-events unit test", () => { }); beforeEach(() => { - sandbox = createSandbox(); const webViewPanel: any = { dispose: () => true }; events = new VSCodeYouiEvents( rpc, @@ -108,6 +106,9 @@ describe("vscode-youi-events unit test", () => { rpcMock = sandbox.mock(rpc); uriMock = sandbox.mock(vscode.Uri); fsMock = sandbox.mock(fs); + wsFileMockUri = vscode.Uri.file("/tmp/workspace.code-workspace"); + sandbox.stub(WorkspaceFile, "createWsWithPath").returns(wsFileMockUri); + sandbox.stub(WorkspaceFile, "createWsWithUri").returns(wsFileMockUri); }); afterEach(() => { @@ -121,6 +122,8 @@ describe("vscode-youi-events unit test", () => { uriMock.verify(); fsMock.verify(); sandbox.restore(); + sandbox = createSandbox(); + loggerWrapper.internalApi.setLogger(testLogger); }); describe("getAppWizard", () => { @@ -246,29 +249,6 @@ describe("vscode-youi-events unit test", () => { ]); appWizard.showInformation(message, MessageType.prompt); }); - - it("setHeaderTitle via AppWizard", () => { - const appWizard = events.getAppWizard(); - rpcMock - .expects("invoke") - .withExactArgs("setHeaderTitle", ["Test Title", "Test Info"]); - appWizard.setHeaderTitle("Test Title", "Test Info"); - }); - - it("setBanner via AppWizard", () => { - const appWizard = events.getAppWizard(); - const bannerProps: IBannerProps = { - text: "Test Banner", - ariaLabel: "Test Label", - action: { - text: "Learn More", - url: "https://example.com", - }, - triggerActionFrom: "link", - }; - rpcMock.expects("invoke").withExactArgs("setBanner", [bannerProps]); - appWizard.setBanner(bannerProps); - }); }); it("executeCommand", () => { @@ -281,285 +261,16 @@ describe("vscode-youi-events unit test", () => { return events.executeCommand(commandId, commandArgs); }); - describe("doGeneratorProgress", () => { - it("writing phase - initializes notification with project name", () => { - const projectName = "testProject"; - lodash.set(vscode, "ProgressLocation.Notification", 15); - eventsMock.expects("doClose"); - // Stub getConfiguration to enable progress notification - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(true), - } as any); - windowMock - .expects("withProgress") - .withArgs({ - location: 15, - title: "Generating testProject", - cancellable: false, - }) - .resolves(); - events.doGeneratorProgress(projectName, "writing", true); - }); - - it("writing phase - uses default title when no project name", () => { - lodash.set(vscode, "ProgressLocation.Notification", 15); - eventsMock.expects("doClose"); - // Stub getConfiguration to enable progress notification - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(true), - } as any); - windowMock - .expects("withProgress") - .withArgs({ - location: 15, - title: "Application Generator", - cancellable: false, - }) - .resolves(); - events.doGeneratorProgress(undefined, "writing", true); - }); - - it("install phase - updates progress message", () => { - // Stub getConfiguration to enable progress notification - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(true), - } as any); - const mockProgressReporter = { - report: sandbox.stub(), - }; - events["progressReporter"] = mockProgressReporter; - - events.doGeneratorProgress("testProject", "install", true); - - // Should be called with the install message after delay - expect(mockProgressReporter.report.called).to.be.true; - expect(mockProgressReporter.report.firstCall.args[0]).to.deep.equal({ - message: messages.default.progress_installing, - }); - - events["progressReporter"] = null; - }); - - it("end phase - updates progress message", () => { - // Stub getConfiguration to enable progress notification - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(true), - } as any); - const mockProgressReporter = { - report: sandbox.stub(), - }; - events["progressReporter"] = mockProgressReporter; - - events.doGeneratorProgress("testProject", "end", true); - - // Should be called with the end message - expect(mockProgressReporter.report.called).to.be.true; - expect(mockProgressReporter.report.firstCall.args[0]).to.deep.equal({ - message: messages.default.progress_finalising, - }); - - events["progressReporter"] = null; - }); - - it("install/end phases - does nothing when progressReporter is null", () => { - // Stub getConfiguration to enable progress notification - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(true), - } as any); - events["progressReporter"] = null; - - // Should not throw when progressReporter is null - events.doGeneratorProgress("testProject", "install", true); - events.doGeneratorProgress("testProject", "end", true); - }); - - it("does nothing when setting is disabled", () => { - // Stub getConfiguration to return false - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(false), - } as any); - - // Should not call doClose or showInstallMessage - eventsMock.expects("doClose").never(); - windowMock.expects("withProgress").never(); - - events.doGeneratorProgress("testProject", "writing", true); - events.doGeneratorProgress("testProject", "install", true); - events.doGeneratorProgress("testProject", "end", true); - }); - - it("does nothing when showProgress parameter is false", () => { - // Even if setting is enabled, showProgress=false should skip everything - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(true), - } as any); - - // Should not call doClose or showInstallMessage - eventsMock.expects("doClose").never(); - windowMock.expects("withProgress").never(); - - events.doGeneratorProgress("testProject", "writing", false); - events.doGeneratorProgress("testProject", "install", false); - events.doGeneratorProgress("testProject", "end", false); - }); - - it("writing phase with existing progressReporter - does not call doClose", () => { - // Stub getConfiguration to enable progress notification - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(true), - } as any); - - const mockProgressReporter = { - report: sandbox.stub(), - }; - events["progressReporter"] = mockProgressReporter; - - // Should not call doClose when progressReporter already exists - eventsMock.expects("doClose").never(); - - events.doGeneratorProgress("testProject", "writing", true); - - // Should update the progress reporter - expect(mockProgressReporter.report.called).to.be.true; - expect(mockProgressReporter.report.firstCall.args[0]).to.deep.equal({ - message: messages.default.progress_writing_files, - }); - - events["progressReporter"] = null; - }); - - it("phases fire in correct order without race condition", async () => { - // Stub getConfiguration to enable progress notification - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(true), - } as any); - - // Stub WorkspaceFile methods - sandbox - .stub(WorkspaceFile, "createWsWithPath") - .returns(vscode.Uri.file("mocked")); - sandbox - .stub(WorkspaceFile, "createWsWithUri") - .returns(vscode.Uri.file("mocked")); - - const reportCalls: string[] = []; - const mockProgressReporter = { - report: sandbox.stub().callsFake((args: any) => { - reportCalls.push(args.message); - }), - }; - - // Stub withProgress to track all report calls - windowMock - .expects("withProgress") - .callsFake((_options: any, callback: any) => { - callback(mockProgressReporter); - return Promise.resolve(); - }); - - // Fire writing phase (creates progress notification) - events.doGeneratorProgress("testProject", "writing", true); - - // Simulate rapid install and end phases (as yeoman emits them) - events["progressReporter"] = mockProgressReporter; - events["currentPhase"] = "writing"; - events["phaseStartTime"] = Date.now(); - - events.doGeneratorProgress("testProject", "install", true); - events.doGeneratorProgress("testProject", "end", true); - - // Wait for all setTimeout delays to complete with polling - // (2000ms for writing + 0ms for install + 1000ms for end) - const waitForMessages = async ( - expectedCount: number, - timeoutMs: number = 5000 - ) => { - const startTime = Date.now(); - while (reportCalls.length < expectedCount) { - if (Date.now() - startTime > timeoutMs) { - throw new Error( - `Timeout waiting for ${expectedCount} messages, got ${reportCalls.length}` - ); - } - await new Promise((resolve) => setTimeout(resolve, 100)); - } - }; - - await waitForMessages(3, 5000); - - // Verify messages appear in correct order - expect(reportCalls).to.have.lengthOf(3); - expect(reportCalls[0]).to.equal(messages.default.progress_writing_files); - expect(reportCalls[1]).to.equal(messages.default.progress_installing); - expect(reportCalls[2]).to.equal(messages.default.progress_finalising); - - events["progressReporter"] = null; - }); - - it("resolves progress notification properly when generation completes", async () => { - // Stub getConfiguration - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: sandbox - .stub() - .withArgs("ApplicationWizard.showGeneratorProgress", true) - .returns(true), - } as any); - - // Stub WorkspaceFile methods - sandbox - .stub(WorkspaceFile, "createWsWithPath") - .returns(vscode.Uri.file("mocked")); - - windowMock - .expects("withProgress") - .callsFake((_options: any, callback: any) => { - return callback({ - report: sandbox.stub(), - }); - }); - - // Start progress notification - events.doGeneratorProgress("testProject", "writing", true); - - // Verify resolveFunc was set - expect(events["resolveFunc"]).to.not.be.undefined; - - // Call resolveInstallingProgress (simulating doGeneratorDone) - events["resolveInstallingProgress"](); - - // Wait a tick for the promise to resolve - await new Promise((resolve) => setTimeout(resolve, 10)); - - // progressReporter should be cleaned up after resolution - expect(events["progressReporter"]).to.be.null; - }); + it("doGeneratorInstall", () => { + _.set(vscode, "ProgressLocation.Notification", 15); + windowMock + .expects("withProgress") + .withArgs({ + location: 15, + title: "Installing dependencies...", + }) + .resolves(); + events.doGeneratorInstall(); }); it("setAppWizardHeaderTitle", () => { @@ -778,10 +489,6 @@ describe("vscode-youi-events unit test", () => { .withArgs("vscode.openFolder") .resolves(); workspaceMock.expects("updateWorkspaceFolders").withArgs(0, null); - // Stub WorkspaceFile.createWsWithPath to prevent filesystem writes in CI - sandbox - .stub(WorkspaceFile, "createWsWithPath") - .returns(vscode.Uri.file("mocked")); uriMock.expects("file").once().returns({ fsPath: "testFsPath" }); return events.doGeneratorDone( true, @@ -808,12 +515,7 @@ describe("vscode-youi-events unit test", () => { .resolves(); workspaceMock.expects("updateWorkspaceFolders").withArgs(0, null); - // Stub WorkspaceFile.createWsWithUri to prevent filesystem writes in CI - sandbox - .stub(WorkspaceFile, "createWsWithUri") - .returns(vscode.Uri.file("mocked")); - - return events.doGeneratorDone( + events.doGeneratorDone( true, "success message", "Open the project in a multi-root workspace", @@ -837,12 +539,7 @@ describe("vscode-youi-events unit test", () => { .withArgs("vscode.openFolder") .resolves(); - // Stub WorkspaceFile.createWsWithUri to prevent filesystem writes in CI - sandbox - .stub(WorkspaceFile, "createWsWithUri") - .returns(vscode.Uri.file("mocked")); - - return events.doGeneratorDone( + events.doGeneratorDone( true, "success message", "Open the project in a stand-alone", @@ -862,12 +559,7 @@ describe("vscode-youi-events unit test", () => { ) .resolves(); - // Stub WorkspaceFile.createWsWithUri to prevent filesystem writes in CI - sandbox - .stub(WorkspaceFile, "createWsWithUri") - .returns(vscode.Uri.file("mocked")); - - return events.doGeneratorDone( + events.doGeneratorDone( true, "success message", "Create the project and close it for future use", @@ -949,142 +641,6 @@ describe("vscode-youi-events unit test", () => { "files" ); }); - - it("shows finalising message when progressReporter is active", async () => { - // Set up a mock progress reporter - const mockProgressReporter = { - report: sandbox.stub(), - }; - events["progressReporter"] = mockProgressReporter; - - eventsMock.expects("doClose"); - windowMock - .expects("showInformationMessage") - .withExactArgs(messages.default.artifact_generated_files) - .resolves(); - - await events.doGeneratorDone( - true, - "success message", - createAndClose, - "files", - null - ); - - // Verify progressReporter.report was called with finalising message - expect(mockProgressReporter.report.called).to.be.true; - expect(mockProgressReporter.report.firstCall.args[0]).to.deep.equal({ - message: messages.default.progress_finalising, - }); - - events["progressReporter"] = null; - }); - - describe("with project name in notification", () => { - beforeEach(() => { - // Set currentProjectName by calling doGeneratorInstall - events["currentProjectName"] = "myTestProject"; - }); - - afterEach(() => { - events["currentProjectName"] = undefined; - }); - - it("shows project name in success message for add to workspace", () => { - eventsMock.expects("doClose"); - sandbox.stub(vscode.workspace, "workspaceFolders").value([]); - sandbox.stub(vscode.workspace, "workspaceFile").value(undefined); - // Stub WorkspaceFile.createWsWithPath to prevent filesystem writes in CI - sandbox - .stub(WorkspaceFile, "createWsWithPath") - .returns(vscode.Uri.file("mocked")); - windowMock - .expects("showInformationMessage") - .withExactArgs( - "Project myTestProject has been generated. The project has been added to workspace." - ) - .resolves(); - commandsMock - .expects("executeCommand") - .withArgs("vscode.openFolder") - .resolves(); - workspaceMock.expects("updateWorkspaceFolders").withArgs(0, null); - return events.doGeneratorDone( - true, - "success message", - addToWorkspace, - "project", - "testDestinationRoot" - ); - }); - - it("shows project name in success message for open in new workspace", () => { - eventsMock.expects("doClose"); - sandbox.stub(vscode.workspace, "workspaceFolders").value([]); - windowMock - .expects("showInformationMessage") - .withExactArgs( - "Project myTestProject has been generated. The project will be opened in a new workspace." - ) - .resolves(); - commandsMock - .expects("executeCommand") - .withArgs("vscode.openFolder") - .resolves(); - return events.doGeneratorDone( - true, - "success message", - openNewWorkspace, - "project", - "testDestinationRoot" - ); - }); - - it("shows project name in success message for save for future use", () => { - eventsMock.expects("doClose"); - sandbox.stub(vscode.workspace, "workspaceFolders").value([]); - windowMock - .expects("showInformationMessage") - .withExactArgs("Project myTestProject has been generated.") - .resolves(); - return events.doGeneratorDone( - true, - "success message", - createAndClose, - "project", - "testDestinationRoot" - ); - }); - }); - - it("sets GENERATOR_COMPLETED flag before disposal", async () => { - // Setup: verify flag is set before doClose is called - let flagSetBeforeClose = false; - - eventsMock.expects("doClose").callsFake(() => { - // Check if flag was already set when doClose is called - const flagValue = (events["webviewPanel"] as any)?.[ - Constants.GENERATOR_COMPLETED - ]; - flagSetBeforeClose = flagValue === true; - }); - - windowMock - .expects("showInformationMessage") - .withExactArgs(messages.default.artifact_generated_files) - .resolves(); - - await events.doGeneratorDone( - true, - "success message", - createAndClose, - "files", - null - ); - - // Verify flag was set before doClose was called - expect(flagSetBeforeClose).to.be.true; - }); }); describe("getUniqueProjectName", () => { @@ -1122,21 +678,4 @@ describe("vscode-youi-events unit test", () => { expect(result).to.equal("UniqueProject"); }); }); - - describe("edge cases for coverage", () => { - it("showDoneMessage with skipResolve=false should call resolveInstallingProgress", async () => { - windowMock - .expects("showInformationMessage") - .withExactArgs(messages.default.artifact_generated_files) - .resolves(); - - // Call showDoneMessage directly with skipResolve=false (default) - await events["showDoneMessage"](true, "success", "", "files"); - }); - - it("getSuccessInfoMessage with empty type returns empty string", () => { - const result = events["getSuccessInfoMessage"]("", ""); - expect(result).to.equal(""); - }); - }); }); diff --git a/projects/yeoman-ui/packages/backend/test/yeomanui.spec.ts b/projects/yeoman-ui/packages/backend/test/yeomanui.spec.ts index b1d9b3e45..780ea3345 100644 --- a/projects/yeoman-ui/packages/backend/test/yeomanui.spec.ts +++ b/projects/yeoman-ui/packages/backend/test/yeomanui.spec.ts @@ -62,11 +62,11 @@ describe("yeomanui unit test", () => { } const appWizard: AppWizard = new TestAppWizard(); class TestEvents implements YouiEvents { - public doGeneratorDone(): Promise { - return Promise.resolve(); + public doGeneratorDone(): void { + return; } - public doGeneratorProgress(): Promise { - return Promise.resolve(); + public doGeneratorInstall(): void { + return; } public showProgress(): void { return; @@ -1364,15 +1364,10 @@ describe("yeomanui unit test", () => { GeneratorFilter.create(), flowPromise.state ); - const gen: any = { - on: () => "", - state: { project: { name: "testProject" } }, - }; + const gen: any = { on: () => "" }; const genMock = sandbox.mock(gen); - genMock.expects("on").withArgs("method:writing"); genMock.expects("on").withArgs("method:install"); - genMock.expects("on").withArgs("method:end"); yeomanUiInstance["onGenInstall"](gen); genMock.verify(); }); diff --git a/projects/yeoman-ui/packages/backend/test/youi-adapter.spec.ts b/projects/yeoman-ui/packages/backend/test/youi-adapter.spec.ts index bde92e286..01662d3f8 100644 --- a/projects/yeoman-ui/packages/backend/test/youi-adapter.spec.ts +++ b/projects/yeoman-ui/packages/backend/test/youi-adapter.spec.ts @@ -16,11 +16,11 @@ import { createFlowPromise } from "../src/utils/promise.js"; describe("YouiAdapter", () => { class TestEvents implements YouiEvents { - public doGeneratorDone(): Promise { - return Promise.resolve(); + public doGeneratorDone(): void { + return; } - public doGeneratorProgress(): Promise { - return Promise.resolve(); + public doGeneratorInstall(): void { + return; } public showProgress(): void { return; diff --git a/projects/yeoman-ui/packages/frontend/src/youi/App.vue b/projects/yeoman-ui/packages/frontend/src/youi/App.vue index 4605c38f5..075f29ea3 100644 --- a/projects/yeoman-ui/packages/frontend/src/youi/App.vue +++ b/projects/yeoman-ui/packages/frontend/src/youi/App.vue @@ -625,29 +625,6 @@ export default { this.doneStatus = true; this.isDone = true; }, - generatorProgress(projectName, phase) { - // Update UI to show progress notification - // Map phases to localized messages from backend - const phaseMessages = { - writing: _get(this.messages, "progress_writing_files", "Creating project files..."), - install: _get(this.messages, "progress_installing", "Installing dependencies..."), - end: _get(this.messages, "progress_finalising", "Finalising..."), - }; - - const message = phaseMessages[phase] || _get(this.messages, "show_progress_message", "Generating..."); - - // Update the prompt name to show progress - if (this.currentPrompt) { - this.currentPrompt.name = projectName - ? `Generating ${projectName}` - : "Generating"; - } - - // Show status message - this.doneMessage = message; - this.isDone = true; - this.doneStatus = true; - }, generatorDone(succeeded, message, targetPath) { this.currentPrompt.name = "Summary"; this.doneMessage = message; @@ -689,7 +666,7 @@ export default { const functions = [ "showPrompt", "setPromptList", - "generatorProgress", + "generatorInstall", "generatorDone", "log", "updateGeneratorsPrompt", diff --git a/projects/yeoman-ui/packages/frontend/test/App.spec.js b/projects/yeoman-ui/packages/frontend/test/App.spec.js index fa704a3b0..ae61aa82d 100644 --- a/projects/yeoman-ui/packages/frontend/test/App.spec.js +++ b/projects/yeoman-ui/packages/frontend/test/App.spec.js @@ -560,7 +560,7 @@ describe("App.vue", () => { wrapper.vm.showPrompt = jest.fn(); wrapper.vm.setPrompts = jest.fn(); - wrapper.vm.generatorProgress = jest.fn(); + wrapper.vm.generatorInstall = jest.fn(); wrapper.vm.generatorDone = jest.fn(); wrapper.vm.log = jest.fn(); @@ -574,9 +574,9 @@ describe("App.vue", () => { name: "showPrompt", }); expect(registerMethodSpy).toHaveBeenCalledWith({ - func: wrapper.vm.generatorProgress, + func: wrapper.vm.generatorInstall, thisArg: wrapper.vm, - name: "generatorProgress", + name: "generatorInstall", }); expect(registerMethodSpy).toHaveBeenCalledWith({ func: wrapper.vm.generatorDone, @@ -968,83 +968,6 @@ describe("App.vue", () => { }); }); - describe("generatorProgress - method", () => { - it("updates notification with project name and phase message for writing phase", () => { - wrapper = initComponent(App, {}, false, ["vscode-textfield"]); - - wrapper.vm.prompts = [{}, {}]; - wrapper.vm.promptIndex = 1; - wrapper.vm.currentPrompt.status = "pending"; - - wrapper.vm.generatorProgress("testProject", "writing"); - - expect(wrapper.vm.currentPrompt.name).toBe("Generating testProject"); - expect(wrapper.vm.doneMessage).toBe("Creating project files..."); - expect(wrapper.vm.isDone).toBeTruthy(); - expect(wrapper.vm.doneStatus).toBeTruthy(); - }); - - it("updates notification with project name and phase message for install phase", () => { - wrapper = initComponent(App, {}, false, ["vscode-textfield"]); - - wrapper.vm.prompts = [{}, {}]; - wrapper.vm.promptIndex = 1; - wrapper.vm.currentPrompt.status = "pending"; - - wrapper.vm.generatorProgress("myApp", "install"); - - expect(wrapper.vm.currentPrompt.name).toBe("Generating myApp"); - expect(wrapper.vm.doneMessage).toBe("Installing dependencies..."); - expect(wrapper.vm.isDone).toBeTruthy(); - expect(wrapper.vm.doneStatus).toBeTruthy(); - }); - - it("updates notification with project name and phase message for end phase", () => { - wrapper = initComponent(App, {}, false, ["vscode-textfield"]); - - wrapper.vm.prompts = [{}, {}]; - wrapper.vm.promptIndex = 1; - wrapper.vm.currentPrompt.status = "pending"; - - wrapper.vm.generatorProgress("finalProject", "end"); - - expect(wrapper.vm.currentPrompt.name).toBe("Generating finalProject"); - expect(wrapper.vm.doneMessage).toBe("Finalising..."); - expect(wrapper.vm.isDone).toBeTruthy(); - expect(wrapper.vm.doneStatus).toBeTruthy(); - }); - - it("uses default title when no project name provided", () => { - wrapper = initComponent(App, {}, false, ["vscode-textfield"]); - - wrapper.vm.prompts = [{}, {}]; - wrapper.vm.promptIndex = 1; - wrapper.vm.currentPrompt.status = "pending"; - - wrapper.vm.generatorProgress(undefined, "install"); - - expect(wrapper.vm.currentPrompt.name).toBe("Generating"); - expect(wrapper.vm.doneMessage).toBe("Installing dependencies..."); - expect(wrapper.vm.isDone).toBeTruthy(); - expect(wrapper.vm.doneStatus).toBeTruthy(); - }); - - it("uses generic message for unknown phase", () => { - wrapper = initComponent(App, {}, false, ["vscode-textfield"]); - - wrapper.vm.prompts = [{}, {}]; - wrapper.vm.promptIndex = 1; - wrapper.vm.currentPrompt.status = "pending"; - - wrapper.vm.generatorProgress("testProject", "unknown"); - - expect(wrapper.vm.currentPrompt.name).toBe("Generating testProject"); - expect(wrapper.vm.doneMessage).toBe("Generating..."); - expect(wrapper.vm.isDone).toBeTruthy(); - expect(wrapper.vm.doneStatus).toBeTruthy(); - }); - }); - describe("generatorDone - method", () => { test("status is pending", () => { wrapper = initComponent(App, { donePath: "testDonePath" }); diff --git a/projects/yeoman-ui/packages/generator-foodq/generators/app/index.js b/projects/yeoman-ui/packages/generator-foodq/generators/app/index.js index 361be3201..81a839a49 100644 --- a/projects/yeoman-ui/packages/generator-foodq/generators/app/index.js +++ b/projects/yeoman-ui/packages/generator-foodq/generators/app/index.js @@ -16,7 +16,6 @@ module.exports = class extends Generator { this.appWizard = types.AppWizard.create(opts); this.option("silent", { type: Boolean }); - this.option("showGeneratorProgress", { type: Boolean, default: true }); const silent = _.get(this.options, "silent", false); @@ -776,6 +775,7 @@ module.exports = class extends Generator { writing() { this.log("FoodQ is in writing stage."); + this.appWizard.showProgress("Generating the FoodQ project."); this.log("The following choices were chosen:"); !_.isNil(this.answers.hungry) &&