diff --git a/packages/vscode-extension/src/codeLensProvider.ts b/packages/vscode-extension/src/codeLensProvider.ts index 1beac20a92..6f11c24e42 100644 --- a/packages/vscode-extension/src/codeLensProvider.ts +++ b/packages/vscode-extension/src/codeLensProvider.ts @@ -21,7 +21,7 @@ import isUUID from "validator/lib/isUUID"; import * as vscode from "vscode"; import { environmentVariableRegex } from "./constants"; import { commandIsRunning } from "./globalVariables"; -import { getSystemInputs } from "./handlers"; +import { getSystemInputs } from "./utils/environmentUtils"; import { TelemetryTriggerFrom } from "./telemetry/extTelemetryEvents"; import { localize } from "./utils/localizeUtils"; import * as _ from "lodash"; diff --git a/packages/vscode-extension/src/commonlib/azureLogin.ts b/packages/vscode-extension/src/commonlib/azureLogin.ts index 61a0d25073..e741b7f558 100644 --- a/packages/vscode-extension/src/commonlib/azureLogin.ts +++ b/packages/vscode-extension/src/commonlib/azureLogin.ts @@ -29,7 +29,7 @@ import { AccountType, TelemetryErrorType, } from "../telemetry/extTelemetryEvents"; -import { VS_CODE_UI } from "../extension"; +import { VS_CODE_UI } from "../qm/vsc_ui"; import { AzureScopes, globalStateGet, globalStateUpdate } from "@microsoft/teamsfx-core"; import { getDefaultString, localize } from "../utils/localizeUtils"; import { diff --git a/packages/vscode-extension/src/commonlib/telemetry.ts b/packages/vscode-extension/src/commonlib/telemetry.ts index 53f61f0820..e425daebff 100644 --- a/packages/vscode-extension/src/commonlib/telemetry.ts +++ b/packages/vscode-extension/src/commonlib/telemetry.ts @@ -1,23 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import * as vscode from "vscode"; +import * as os from "os"; +import * as path from "path"; // eslint-disable-next-line import/default import Reporter from "@vscode/extension-telemetry"; import { TelemetryReporter, ConfigFolderName } from "@microsoft/teamsfx-api"; -import { - getAllFeatureFlags, - getPackageVersion, - isFeatureFlagEnabled, - FeatureFlags, - anonymizeFilePaths, -} from "../utils/commonUtils"; +import { anonymizeFilePaths } from "../utils/fileSystemUtils"; +import { isFeatureFlagEnabled, FeatureFlags, getAllFeatureFlags } from "../featureFlags"; +import { getPackageVersion } from "../utils/telemetryUtils"; import { TelemetryProperty } from "../telemetry/extTelemetryEvents"; -import { getProjectMetadata } from "@microsoft/teamsfx-core"; -import { Correlator } from "@microsoft/teamsfx-core"; +import { Correlator, getProjectMetadata } from "@microsoft/teamsfx-core"; import { configure, getLogger, Logger } from "log4js"; -import * as os from "os"; -import * as path from "path"; -import * as globalVariables from "../globalVariables"; +import { workspaceUri } from "../globalVariables"; const TelemetryTestLoggerFile = "telemetryTest.log"; @@ -176,7 +171,7 @@ export class VSCodeTelemetryReporter extends vscode.Disposable implements Teleme private checkAndOverwriteSharedProperty(properties: { [p: string]: string }) { if (!properties[TelemetryProperty.ProjectId]) { - const fixedProjectSettings = getProjectMetadata(globalVariables.workspaceUri?.fsPath); + const fixedProjectSettings = getProjectMetadata(workspaceUri?.fsPath); if (fixedProjectSettings?.projectId) { properties[TelemetryProperty.ProjectId] = fixedProjectSettings?.projectId; diff --git a/packages/vscode-extension/src/debug/commonUtils.ts b/packages/vscode-extension/src/debug/commonUtils.ts index 68c4298832..0b79dec0d2 100644 --- a/packages/vscode-extension/src/debug/commonUtils.ts +++ b/packages/vscode-extension/src/debug/commonUtils.ts @@ -13,16 +13,11 @@ import { import * as fs from "fs-extra"; import * as path from "path"; import * as uuid from "uuid"; -import * as vscode from "vscode"; import VsCodeLogInstance from "../commonlib/log"; - -import * as globalVariables from "../globalVariables"; -import { core, getSystemInputs } from "../handlers"; +import { workspaceUri } from "../globalVariables"; import { ExtTelemetry } from "../telemetry/extTelemetry"; import { allRunningDebugSessions } from "./teamsfxTaskHandler"; - import { ExtensionErrors, ExtensionSource } from "../error"; -import { VS_CODE_UI } from "../extension"; export async function getProjectRoot( folderPath: string, @@ -40,10 +35,10 @@ export async function getNpmInstallLogInfo(): Promise { export async function getTestToolLogInfo(): Promise { const localEnvManager = new LocalEnvManager(VsCodeLogInstance, ExtTelemetry.reporter); - if (!globalVariables.workspaceUri?.fsPath) { + if (!workspaceUri?.fsPath) { return undefined; } - return await localEnvManager.getTestToolLogInfo(globalVariables.workspaceUri?.fsPath); + return await localEnvManager.getTestToolLogInfo(workspaceUri?.fsPath); } export class LocalDebugSession { @@ -137,7 +132,7 @@ export async function getV3TeamsAppId(projectPath: string, env: string): Promise } export async function getTeamsAppKeyName(env?: string): Promise { - const templatePath = pathUtils.getYmlFilePath(globalVariables.workspaceUri!.fsPath, env); + const templatePath = pathUtils.getYmlFilePath(workspaceUri!.fsPath, env); const maybeProjectModel = await metadataUtil.parse(templatePath, env); if (maybeProjectModel.isErr()) { return undefined; @@ -153,18 +148,6 @@ export async function getTeamsAppKeyName(env?: string): Promise { - const inputs = getSystemInputs(); - inputs.stage = Stage.debug; - const result = await core.phantomMigrationV3(inputs); - if (result.isErr()) { - await vscode.debug.stopDebugging(); - throw result.error; - } - // reload window to terminate debugging - await VS_CODE_UI.reload(); -} - // Only work in ts/js project export function isTestToolEnabledProject(workspacePath: string): boolean { const testToolYmlPath = path.join(workspacePath, MetadataV3.testToolConfigFile); diff --git a/packages/vscode-extension/src/debug/launch.ts b/packages/vscode-extension/src/debug/launch.ts index 20277c6640..1ab75723c3 100644 --- a/packages/vscode-extension/src/debug/launch.ts +++ b/packages/vscode-extension/src/debug/launch.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { VS_CODE_UI } from "../extension"; +import { VS_CODE_UI } from "../qm/vsc_ui"; import * as constants from "./constants"; import VsCodeLogInstance from "../commonlib/log"; import { Hub } from "@microsoft/teamsfx-core"; diff --git a/packages/vscode-extension/src/debug/prerequisitesHandler.ts b/packages/vscode-extension/src/debug/prerequisitesHandler.ts index ffd044e780..838aa9e1b2 100644 --- a/packages/vscode-extension/src/debug/prerequisitesHandler.ts +++ b/packages/vscode-extension/src/debug/prerequisitesHandler.ts @@ -41,9 +41,9 @@ import { signedOut } from "../commonlib/common/constant"; import VsCodeLogInstance from "../commonlib/log"; import M365TokenInstance from "../commonlib/m365Login"; import { ExtensionErrors, ExtensionSource } from "../error"; -import { VS_CODE_UI } from "../extension"; -import * as globalVariables from "../globalVariables"; -import { checkCopilotCallback, openAccountHelpHandler, tools } from "../handlers"; +import { VS_CODE_UI } from "../qm/vsc_ui"; +import { tools, workspaceUri } from "../globalVariables"; +import { checkCopilotCallback, openAccountHelpHandler } from "../handlers"; import { ProgressHandler } from "../progressHandler"; import { ExtTelemetry } from "../telemetry/extTelemetry"; import { TelemetryEvent, TelemetryProperty } from "../telemetry/extTelemetryEvents"; @@ -621,7 +621,7 @@ async function checkNode( try { VsCodeLogInstance.outputChannel.appendLine(`${prefix} ${ProgressMessage[nodeDep]} ...`); const nodeStatus = await depsManager.ensureDependency(nodeDep, true, { - projectPath: globalVariables.workspaceUri?.fsPath, + projectPath: workspaceUri?.fsPath, }); return { checker: nodeStatus.name, @@ -790,7 +790,7 @@ async function checkFailure( } function getOrderedCheckersForGetStarted(): PrerequisiteOrderedChecker[] { - const workspacePath = globalVariables.workspaceUri?.fsPath; + const workspacePath = workspaceUri?.fsPath; return [ { info: { checker: workspacePath ? DepsType.ProjectNode : DepsType.LtsNode }, diff --git a/packages/vscode-extension/src/debug/taskTerminal/devTunnelTaskTerminal.ts b/packages/vscode-extension/src/debug/taskTerminal/devTunnelTaskTerminal.ts index 24014be909..864287b272 100644 --- a/packages/vscode-extension/src/debug/taskTerminal/devTunnelTaskTerminal.ts +++ b/packages/vscode-extension/src/debug/taskTerminal/devTunnelTaskTerminal.ts @@ -19,15 +19,15 @@ import { TaskDefaultValue, TunnelType } from "@microsoft/teamsfx-core"; import VsCodeLogInstance from "../../commonlib/log"; import { ExtensionErrors } from "../../error"; -import { VS_CODE_UI } from "../../extension"; -import { tools } from "../../handlers"; +import { VS_CODE_UI } from "../../qm/vsc_ui"; +import { tools } from "../../globalVariables"; import { ExtTelemetry } from "../../telemetry/extTelemetry"; import { TelemetryEvent, TelemetryProperty, TelemetrySuccess, } from "../../telemetry/extTelemetryEvents"; -import { FeatureFlags, isFeatureFlagEnabled } from "../../utils/commonUtils"; +import { FeatureFlags, isFeatureFlagEnabled } from "../../featureFlags"; import { devTunnelDisplayMessages } from "../constants"; import { maskValue } from "../localTelemetryReporter"; import { BaseTaskTerminal } from "./baseTaskTerminal"; diff --git a/packages/vscode-extension/src/debug/taskTerminal/launchDesktopClientTerminal.ts b/packages/vscode-extension/src/debug/taskTerminal/launchDesktopClientTerminal.ts index ec5d731503..888bfa3ef4 100644 --- a/packages/vscode-extension/src/debug/taskTerminal/launchDesktopClientTerminal.ts +++ b/packages/vscode-extension/src/debug/taskTerminal/launchDesktopClientTerminal.ts @@ -13,7 +13,8 @@ import { TelemetryEvent, TelemetryProperty } from "../../telemetry/extTelemetryE import { ExtensionErrors, ExtensionSource } from "../../error"; import { getDefaultString, localize } from "../../utils/localizeUtils"; import { openTerminalDisplayMessage, openTerminalMessage } from "../constants"; -import { core, getSystemInputs } from "../../handlers"; +import { getSystemInputs } from "../../utils/environmentUtils"; +import { core } from "../../globalVariables"; import * as path from "path"; interface LaunchDesktopClientArgs { diff --git a/packages/vscode-extension/src/debug/taskTerminal/launchTeamsClientTerminal.ts b/packages/vscode-extension/src/debug/taskTerminal/launchTeamsClientTerminal.ts index d68d07c633..971dabfd17 100644 --- a/packages/vscode-extension/src/debug/taskTerminal/launchTeamsClientTerminal.ts +++ b/packages/vscode-extension/src/debug/taskTerminal/launchTeamsClientTerminal.ts @@ -13,8 +13,8 @@ import * as util from "util"; import * as vscode from "vscode"; import VsCodeLogInstance from "../../commonlib/log"; import { ExtensionErrors, ExtensionSource } from "../../error"; -import * as globalVariables from "../../globalVariables"; -import { core, getSystemInputs } from "../../handlers"; +import { core, workspaceUri } from "../../globalVariables"; +import { getSystemInputs } from "../../utils/environmentUtils"; import { TelemetryEvent, TelemetryProperty } from "../../telemetry/extTelemetryEvents"; import { getDefaultString, localize } from "../../utils/localizeUtils"; import { getLocalDebugSession } from "../commonUtils"; @@ -89,7 +89,7 @@ export class LaunchTeamsClientTerminal extends BaseTaskTerminal { private openUrl(url: string): Promise> { return new Promise>((resolve) => { const options: cp.SpawnOptions = { - cwd: globalVariables.workspaceUri?.fsPath ?? "", + cwd: workspaceUri?.fsPath ?? "", shell: false, detached: false, }; diff --git a/packages/vscode-extension/src/debug/taskTerminal/lifecycleTaskTerminal.ts b/packages/vscode-extension/src/debug/taskTerminal/lifecycleTaskTerminal.ts index dc1a0a0c6c..1006010e2a 100644 --- a/packages/vscode-extension/src/debug/taskTerminal/lifecycleTaskTerminal.ts +++ b/packages/vscode-extension/src/debug/taskTerminal/lifecycleTaskTerminal.ts @@ -1,21 +1,20 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Xiaofu Huang */ import * as path from "path"; import * as vscode from "vscode"; import { err, FxError, ok, Result, Stage, Void } from "@microsoft/teamsfx-api"; -import { TaskDefaultValue } from "@microsoft/teamsfx-core"; -import { Correlator } from "@microsoft/teamsfx-core"; -import * as globalVariables from "../../globalVariables"; -import { getSystemInputs, runCommand } from "../../handlers"; +import { Correlator, TaskDefaultValue } from "@microsoft/teamsfx-core"; +import { workspaceUri } from "../../globalVariables"; +import { runCommand } from "../../handlers"; import { TelemetryEvent, TelemetryProperty } from "../../telemetry/extTelemetryEvents"; -import * as commonUtils from "../commonUtils"; +import { getLocalDebugSession } from "../commonUtils"; import { localTelemetryReporter, maskValue } from "../localTelemetryReporter"; import { BaseTaskTerminal } from "./baseTaskTerminal"; +import { getSystemInputs } from "../../utils/environmentUtils"; interface LifecycleArgs { template?: string; @@ -43,7 +42,7 @@ export class LifecycleTaskTerminal extends BaseTaskTerminal { [TelemetryProperty.DebugLifecycle]: this.stage, }; - return Correlator.runWithId(commonUtils.getLocalDebugSession().id, () => + return Correlator.runWithId(getLocalDebugSession().id, () => localTelemetryReporter.runWithTelemetryProperties( TelemetryEvent.DebugLifecycleTask, telemetryProperties, @@ -66,7 +65,7 @@ export class LifecycleTaskTerminal extends BaseTaskTerminal { inputs.isLocalDebug = true; if (this.args.template) { inputs.workflowFilePath = path.resolve( - globalVariables.workspaceUri?.fsPath ?? "", + workspaceUri?.fsPath ?? "", BaseTaskTerminal.resolveTeamsFxVariables(this.args.template) ); } diff --git a/packages/vscode-extension/src/debug/taskTerminal/migrateTaskTerminal.ts b/packages/vscode-extension/src/debug/taskTerminal/migrateTaskTerminal.ts index 1e1a2e3494..9c67943216 100644 --- a/packages/vscode-extension/src/debug/taskTerminal/migrateTaskTerminal.ts +++ b/packages/vscode-extension/src/debug/taskTerminal/migrateTaskTerminal.ts @@ -1,13 +1,11 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. import * as vscode from "vscode"; import { FxError, ok, Result, Void } from "@microsoft/teamsfx-api"; -import * as commonUtils from "../commonUtils"; import { BaseTaskTerminal } from "./baseTaskTerminal"; +import { triggerV3Migration } from "../../utils/migrationUtils"; export class MigrateTaskTerminal extends BaseTaskTerminal { constructor(taskDefinition: vscode.TaskDefinition) { @@ -15,7 +13,7 @@ export class MigrateTaskTerminal extends BaseTaskTerminal { } async do(): Promise> { - await commonUtils.triggerV3Migration(); + await triggerV3Migration(); return ok(Void); } } diff --git a/packages/vscode-extension/src/debug/taskTerminal/utils/devTunnelStateManager.ts b/packages/vscode-extension/src/debug/taskTerminal/utils/devTunnelStateManager.ts index 6d08218276..441d091a44 100644 --- a/packages/vscode-extension/src/debug/taskTerminal/utils/devTunnelStateManager.ts +++ b/packages/vscode-extension/src/debug/taskTerminal/utils/devTunnelStateManager.ts @@ -9,8 +9,8 @@ import { Mutex, withTimeout } from "async-mutex"; import * as fs from "fs-extra"; import * as path from "path"; import { isFeatureFlagEnabled } from "@microsoft/teamsfx-core"; -import * as globalVariables from "../../../globalVariables"; -import { FeatureFlags } from "../../../utils/commonUtils"; +import { context, workspaceUri } from "../../../globalVariables"; +import { FeatureFlags } from "../../../featureFlags"; interface IDevTunnelState { tunnelId?: string; @@ -91,12 +91,12 @@ interface IStateService { class VSCodeStateService implements IStateService { get(key: string): Promise { return new Promise((resolve) => { - resolve(globalVariables.context.workspaceState.get(key)); + resolve(context.workspaceState.get(key)); }); } async update(key: string, value: any): Promise { - await globalVariables.context.workspaceState.update(key, value); + await context.workspaceState.update(key, value); } } @@ -104,12 +104,10 @@ class FileStateService implements IStateService { private readonly stateFileName = "devtunnel.state.json"; async get(key: string): Promise { try { - if (!globalVariables.workspaceUri?.fsPath) { + if (!workspaceUri?.fsPath) { return undefined; } - const data = await fs.readJson( - path.resolve(globalVariables.workspaceUri.fsPath, this.stateFileName) - ); + const data = await fs.readJson(path.resolve(workspaceUri.fsPath, this.stateFileName)); return data?.[key] as T; } catch { @@ -119,10 +117,10 @@ class FileStateService implements IStateService { async update(key: string, value: any): Promise { try { - if (!globalVariables.workspaceUri?.fsPath) { + if (!workspaceUri?.fsPath) { return; } - const stateFilePath = path.resolve(globalVariables.workspaceUri.fsPath, this.stateFileName); + const stateFilePath = path.resolve(workspaceUri.fsPath, this.stateFileName); let data: { [key: string]: any } = {}; try { data = await fs.readJson(stateFilePath); diff --git a/packages/vscode-extension/src/debug/teamsfxDebugProvider.ts b/packages/vscode-extension/src/debug/teamsfxDebugProvider.ts index f85b2353c8..29872d1158 100644 --- a/packages/vscode-extension/src/debug/teamsfxDebugProvider.ts +++ b/packages/vscode-extension/src/debug/teamsfxDebugProvider.ts @@ -18,12 +18,15 @@ import { import VsCodeLogInstance from "../commonlib/log"; import M365TokenInstance from "../commonlib/m365Login"; import { ExtensionSource } from "../error"; -import { core, getSystemInputs, showError } from "../handlers"; +import { showError } from "../handlers"; +import { core } from "../globalVariables"; import { TelemetryEvent, TelemetryProperty } from "../telemetry/extTelemetryEvents"; -import * as commonUtils from "./commonUtils"; +import { getLocalDebugSessionId, endLocalDebugSession } from "./commonUtils"; import { accountHintPlaceholder, Host, sideloadingDisplayMessages } from "./constants"; import { localTelemetryReporter, sendDebugAllEvent } from "./localTelemetryReporter"; import { terminateAllRunningTeamsfxTasks } from "./teamsfxTaskHandler"; +import { triggerV3Migration } from "../utils/migrationUtils"; +import { getSystemInputs } from "../utils/environmentUtils"; export interface TeamsfxDebugConfiguration extends vscode.DebugConfiguration { teamsfxIsRemote?: boolean; @@ -40,7 +43,7 @@ export class TeamsfxDebugProvider implements vscode.DebugConfigurationProvider { token?: vscode.CancellationToken ): Promise { return await Correlator.runWithId( - commonUtils.getLocalDebugSessionId(), + getLocalDebugSessionId(), this._resolveDebugConfiguration, folder, debugConfiguration, @@ -69,7 +72,7 @@ export class TeamsfxDebugProvider implements vscode.DebugConfigurationProvider { // migrate to v3 if (!isValidProjectV3(folder.uri.fsPath)) { - await commonUtils.triggerV3Migration(); + await triggerV3Migration(); return debugConfiguration; } @@ -121,7 +124,7 @@ export class TeamsfxDebugProvider implements vscode.DebugConfigurationProvider { // Attach correlation-id to DebugConfiguration so concurrent debug sessions are correctly handled in this stage. // For backend and bot debug sessions, debugConfiguration.url is undefined so we need to set correlation id early. - debugConfiguration.teamsfxCorrelationId = commonUtils.getLocalDebugSessionId(); + debugConfiguration.teamsfxCorrelationId = getLocalDebugSessionId(); const result = await localTelemetryReporter.runWithTelemetryExceptionProperties( TelemetryEvent.DebugProviderResolveDebugConfiguration, @@ -197,7 +200,7 @@ export class TeamsfxDebugProvider implements vscode.DebugConfigurationProvider { if (telemetryIsRemote === false) { await sendDebugAllEvent(error); } - commonUtils.endLocalDebugSession(); + endLocalDebugSession(); } return debugConfiguration; } diff --git a/packages/vscode-extension/src/debug/teamsfxTaskHandler.ts b/packages/vscode-extension/src/debug/teamsfxTaskHandler.ts index e6f5f20040..4914cb803c 100644 --- a/packages/vscode-extension/src/debug/teamsfxTaskHandler.ts +++ b/packages/vscode-extension/src/debug/teamsfxTaskHandler.ts @@ -17,7 +17,7 @@ import { import VsCodeLogInstance from "../commonlib/log"; import { ExtensionErrors, ExtensionSource } from "../error"; -import { VS_CODE_UI } from "../extension"; +import { VS_CODE_UI } from "../qm/vsc_ui"; import * as globalVariables from "../globalVariables"; import { TelemetryEvent, diff --git a/packages/vscode-extension/src/extension.ts b/packages/vscode-extension/src/extension.ts index 1694db612f..594a552b2f 100644 --- a/packages/vscode-extension/src/extension.ts +++ b/packages/vscode-extension/src/extension.ts @@ -84,19 +84,15 @@ import { import * as handlers from "./handlers"; import { ManifestTemplateHoverProvider } from "./hoverProvider"; import * as officeDevHandlers from "./officeDevHandlers"; -import { VsCodeUI } from "./qm/vsc_ui"; +import { initVSCodeUI } from "./qm/vsc_ui"; import { ExtTelemetry } from "./telemetry/extTelemetry"; import { TelemetryEvent, TelemetryTriggerFrom } from "./telemetry/extTelemetryEvents"; import accountTreeViewProviderInstance from "./treeview/account/accountTreeViewProvider"; import officeDevTreeViewManager from "./treeview/officeDevTreeViewManager"; import TreeViewManagerInstance from "./treeview/treeViewManager"; import { UriHandler, setUriEventHandler } from "./uriHandler"; -import { - FeatureFlags, - delay, - hasAdaptiveCardInWorkspace, - isM365Project, -} from "./utils/commonUtils"; +import { delay, hasAdaptiveCardInWorkspace, isM365Project } from "./utils/commonUtils"; +import { FeatureFlags } from "./featureFlags"; import { loadLocalizedStrings } from "./utils/localizeUtils"; import { checkProjectTypeAndSendTelemetry } from "./utils/projectChecker"; import { ReleaseNote } from "./utils/releaseNote"; @@ -105,8 +101,6 @@ import { registerOfficeTaskAndDebugEvents } from "./debug/officeTaskHandler"; import { createProjectFromWalkthroughHandler } from "./handlers/walkthrough"; import { checkCopilotAccessHandler } from "./handlers/checkCopilotAccess"; -export let VS_CODE_UI: VsCodeUI; - export async function activate(context: vscode.ExtensionContext) { process.env[FeatureFlags.ChatParticipant] = ( IsChatParticipantEnabled && @@ -118,7 +112,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(new ExtTelemetry.Reporter(context)); - VS_CODE_UI = new VsCodeUI(context); + initVSCodeUI(context); initializeGlobalVariables(context); loadLocalizedStrings(); diff --git a/packages/vscode-extension/src/featureFlags.ts b/packages/vscode-extension/src/featureFlags.ts new file mode 100644 index 0000000000..1974d4aba9 --- /dev/null +++ b/packages/vscode-extension/src/featureFlags.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export class FeatureFlags { + static readonly InsiderPreview = "__TEAMSFX_INSIDER_PREVIEW"; + static readonly TelemetryTest = "TEAMSFX_TELEMETRY_TEST"; + static readonly DevTunnelTest = "TEAMSFX_DEV_TUNNEL_TEST"; + static readonly Preview = "TEAMSFX_PREVIEW"; + static readonly DevelopCopilotPlugin = "DEVELOP_COPILOT_PLUGIN"; + static readonly ChatParticipant = "TEAMSFX_CHAT_PARTICIPANT"; +} + +// Determine whether feature flag is enabled based on environment variable setting + +export function isFeatureFlagEnabled(featureFlagName: string, defaultValue = false): boolean { + const flag = process.env[featureFlagName]; + + if (flag === undefined) { + return defaultValue; // allows consumer to set a default value when environment variable not set + } else { + return flag === "1" || flag.toLowerCase() === "true"; // can enable feature flag by set environment variable value to "1" or "true" + } +} + +export function getAllFeatureFlags(): string[] | undefined { + const result = Object.values(FeatureFlags) + .filter((featureFlag: string) => { + return isFeatureFlagEnabled(featureFlag); + }) + .map((featureFlag) => { + return featureFlag; + }); + + return result; +} diff --git a/packages/vscode-extension/src/folder.ts b/packages/vscode-extension/src/folder.ts index a85f36b7a7..a89832c13b 100644 --- a/packages/vscode-extension/src/folder.ts +++ b/packages/vscode-extension/src/folder.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as path from "path"; export function getResourceFolder(): string { diff --git a/packages/vscode-extension/src/globalVariables.ts b/packages/vscode-extension/src/globalVariables.ts index 6c89838ef3..eca4084be7 100644 --- a/packages/vscode-extension/src/globalVariables.ts +++ b/packages/vscode-extension/src/globalVariables.ts @@ -6,10 +6,12 @@ import * as path from "path"; import * as vscode from "vscode"; import { UserState } from "./constants"; import { + FxCore, isValidProject, isValidOfficeAddInProject, isManifestOnlyOfficeAddinProject, } from "@microsoft/teamsfx-core"; +import { Tools } from "@microsoft/teamsfx-api"; /** * Common variables used throughout the extension. They must be initialized in the activate() method of extension.ts @@ -23,6 +25,8 @@ export let isSPFxProject = false; export let isExistingUser = "no"; export let defaultExtensionLogPath: string; export let commandIsRunning = false; +export let core: FxCore; +export let tools: Tools; if (vscode.workspace && vscode.workspace.workspaceFolders) { if (vscode.workspace.workspaceFolders.length > 0) { @@ -75,3 +79,10 @@ export function setCommandIsRunning(isRunning: boolean) { export function unsetIsTeamsFxProject() { isTeamsFxProject = false; } + +export function setTools(toolsInstance: Tools) { + tools = toolsInstance; +} +export function setCore(coreInstance: FxCore) { + core = coreInstance; +} diff --git a/packages/vscode-extension/src/handlers.ts b/packages/vscode-extension/src/handlers.ts index aac02f4b45..b6d80f07be 100644 --- a/packages/vscode-extension/src/handlers.ts +++ b/packages/vscode-extension/src/handlers.ts @@ -44,7 +44,6 @@ import { err, ok, } from "@microsoft/teamsfx-api"; -import * as commonTools from "@microsoft/teamsfx-core"; import { AppStudioClient, AppStudioScopes, @@ -78,7 +77,6 @@ import { pluginManifestUtils, } from "@microsoft/teamsfx-core"; import { ExtensionContext, QuickPickItem, Uri, commands, env, window, workspace } from "vscode"; - import commandController from "./commandController"; import AzureAccountManager from "./commonlib/azureLogin"; import { signedIn, signedOut } from "./commonlib/common/constant"; @@ -93,7 +91,6 @@ import { } from "./constants"; import { PanelType } from "./controls/PanelType"; import { WebviewPanel } from "./controls/webviewPanel"; -import * as commonUtils from "./debug/commonUtils"; import { vscodeLogger } from "./debug/depsChecker/vscodeLogger"; import { vscodeTelemetry } from "./debug/depsChecker/vscodeTelemetry"; import { openHubWebClient } from "./debug/launch"; @@ -102,8 +99,21 @@ import { selectAndDebug } from "./debug/runIconHandler"; import { ExtensionErrors, ExtensionSource } from "./error"; import * as exp from "./exp/index"; import { TreatmentVariableValue } from "./exp/treatmentVariables"; -import { VS_CODE_UI } from "./extension"; -import * as globalVariables from "./globalVariables"; +import { VS_CODE_UI } from "./qm/vsc_ui"; +import { + checkIsSPFx, + context, + core, + initializeGlobalVariables, + isOfficeAddInProject, + isSPFxProject, + isTeamsFxProject, + setCommandIsRunning, + setCore, + setTools, + tools, + workspaceUri, +} from "./globalVariables"; import { TeamsAppMigrationHandler } from "./migration/migrationHandler"; import { ExtTelemetry } from "./telemetry/extTelemetry"; import { @@ -124,7 +134,6 @@ import envTreeProviderInstance from "./treeview/environmentTreeViewProvider"; import { TreeViewCommand } from "./treeview/treeViewCommand"; import TreeViewManagerInstance from "./treeview/treeViewManager"; import { - anonymizeFilePaths, getAppName, getLocalDebugMessageTemplate, getResourceGroupNameFromEnv, @@ -134,6 +143,7 @@ import { isTriggerFromWalkThrough, openFolderInExplorer, } from "./utils/commonUtils"; +import { anonymizeFilePaths } from "./utils/fileSystemUtils"; import { getDefaultString, loadedLocale, localize } from "./utils/localizeUtils"; import { ExtensionSurvey } from "./utils/survey"; import { @@ -144,15 +154,15 @@ import { import { openOfficeDevFolder } from "./officeDevHandlers"; import { invokeTeamsAgent } from "./copilotChatHandlers"; import { updateProjectStatus } from "./utils/projectStatusUtils"; - -export let core: FxCore; -export let tools: Tools; +import { triggerV3Migration } from "./utils/migrationUtils"; +import { isTestToolEnabledProject } from "./debug/commonUtils"; +import { getSystemInputs } from "./utils/environmentUtils"; export function activate(): Result { const result: Result = ok(Void); - const validProject = isValidProject(globalVariables.workspaceUri?.fsPath); + const validProject = isValidProject(workspaceUri?.fsPath); if (validProject) { - const fixedProjectSettings = getProjectMetadata(globalVariables.workspaceUri?.fsPath); + const fixedProjectSettings = getProjectMetadata(workspaceUri?.fsPath); ExtTelemetry.addSharedProperty( TelemetryProperty.ProjectId, fixedProjectSettings?.projectId as string @@ -192,7 +202,7 @@ export function activate(): Result { m365NotificationCallback, false ); - tools = { + setTools({ logProvider: VsCodeLogInstance, tokenProvider: { azureAccountProvider: AzureAccountManager, @@ -201,17 +211,17 @@ export function activate(): Result { telemetryReporter: ExtTelemetry.reporter, ui: VS_CODE_UI, expServiceProvider: exp.getExpService(), - }; - core = new FxCore(tools); + }); + setCore(new FxCore(tools)); core.on(CoreCallbackEvent.lock, async (command: string) => { - globalVariables.setCommandIsRunning(true); + setCommandIsRunning(true); await commandController.lockedByOperation(command); }); core.on(CoreCallbackEvent.unlock, async (command: string) => { - globalVariables.setCommandIsRunning(false); + setCommandIsRunning(false); await commandController.unlockedByOperation(command); }); - const workspacePath = globalVariables.workspaceUri?.fsPath; + const workspacePath = workspaceUri?.fsPath; if (workspacePath) { addFileSystemWatcher(workspacePath); } @@ -293,7 +303,7 @@ async function refreshEnvTreeOnFileChanged(workspacePath: string, files: readonl } export function addFileSystemWatcher(workspacePath: string) { - if (isValidProject(globalVariables.workspaceUri?.fsPath)) { + if (isValidProject(workspaceUri?.fsPath)) { const packageLockFileWatcher = vscode.workspace.createFileSystemWatcher("**/package-lock.json"); packageLockFileWatcher.onDidCreate(async (event) => { @@ -318,7 +328,7 @@ export function addFileSystemWatcher(workspacePath: string) { } export function refreshSPFxTreeOnFileChanged() { - globalVariables.initializeGlobalVariables(globalVariables.context); + initializeGlobalVariables(context); TreeViewManagerInstance.updateTreeViewsOnSPFxChanged(); } @@ -350,16 +360,6 @@ async function refreshEnvTreeOnFileContentChanged(workspacePath: string, filePat } } -export function getSystemInputs(): Inputs { - const answers: Inputs = { - projectPath: globalVariables.workspaceUri?.fsPath, - platform: Platform.VSCode, - vscodeEnv: detectVsCodeEnv(), - locale: loadedLocale, - }; - return answers; -} - export async function createNewProjectHandler(...args: any[]): Promise> { ExtTelemetry.sendTelemetryEvent(TelemetryEvent.CreateProjectStart, getTriggerFromProperty(args)); let inputs: Inputs | undefined; @@ -431,7 +431,7 @@ export async function updateAutoOpenGlobalKey( await globalStateUpdate(GlobalKey.CreateWarnings, JSON.stringify(warnings)); } - if (globalVariables.checkIsSPFx(projectUri.fsPath)) { + if (checkIsSPFx(projectUri.fsPath)) { globalStateUpdate(GlobalKey.AutoInstallDependency, true); } } @@ -517,7 +517,7 @@ export async function validateManifestHandler(args?: any[]): Promise> { - const projectPath = globalVariables.workspaceUri?.fsPath; + const projectPath = workspaceUri?.fsPath; if (!isValidProject(projectPath)) { return err(new InvalidProjectError()); } @@ -575,7 +575,7 @@ export async function publishInDeveloperPortalHandler( TelemetryEvent.PublishInDeveloperPortalStart, getTriggerFromProperty(args) ); - const workspacePath = globalVariables.workspaceUri?.fsPath; + const workspacePath = workspaceUri?.fsPath; const zipDefaultFolder: string | undefined = path.join( workspacePath!, BuildFolderName, @@ -868,26 +868,6 @@ export async function downloadSample(inputs: Inputs): Promise { export async function validateAzureDependenciesHandler(): Promise { try { - await commonUtils.triggerV3Migration(); + await triggerV3Migration(); return undefined; } catch (error: any) { void showError(error as FxError); @@ -1029,7 +1009,7 @@ export async function validateAzureDependenciesHandler(): Promise { try { - await commonUtils.triggerV3Migration(); + await triggerV3Migration(); return undefined; } catch (error: any) { void showError(error as FxError); @@ -1042,7 +1022,7 @@ export async function validateLocalPrerequisitesHandler(): Promise { try { - await commonUtils.triggerV3Migration(); + await triggerV3Migration(); return undefined; } catch (error: any) { void showError(error as FxError); @@ -1074,7 +1054,7 @@ export async function validateGetStartedPrerequisitesHandler( */ export async function backendExtensionsInstallHandler(): Promise { try { - await commonUtils.triggerV3Migration(); + await triggerV3Migration(); return undefined; } catch (error: any) { void showError(error as FxError); @@ -1115,7 +1095,7 @@ export async function getDotnetPathHandler(): Promise { */ export async function preDebugCheckHandler(): Promise { try { - await commonUtils.triggerV3Migration(); + await triggerV3Migration(); return undefined; } catch (error: any) { void showError(error as FxError); @@ -1288,18 +1268,18 @@ export async function autoOpenProjectHandler(): Promise { await openWelcomeHandler([TelemetryTriggerFrom.Auto]); await globalStateUpdate(GlobalKey.OpenWalkThrough, false); - if (globalVariables.workspaceUri?.fsPath) { - await ShowScaffoldingWarningSummary(globalVariables.workspaceUri.fsPath, createWarnings); + if (workspaceUri?.fsPath) { + await ShowScaffoldingWarningSummary(workspaceUri.fsPath, createWarnings); await globalStateUpdate(GlobalKey.CreateWarnings, ""); } } - if (isOpenReadMe === globalVariables.workspaceUri?.fsPath) { + if (isOpenReadMe === workspaceUri?.fsPath) { await showLocalDebugMessage(); await openReadMeHandler(TelemetryTriggerFrom.Auto); - await updateProjectStatus(globalVariables.workspaceUri.fsPath, CommandKey.OpenReadMe, ok(null)); + await updateProjectStatus(workspaceUri.fsPath, CommandKey.OpenReadMe, ok(null)); await globalStateUpdate(GlobalKey.OpenReadMe, ""); - await ShowScaffoldingWarningSummary(globalVariables.workspaceUri.fsPath, createWarnings); + await ShowScaffoldingWarningSummary(workspaceUri.fsPath, createWarnings); await globalStateUpdate(GlobalKey.CreateWarnings, ""); } if (isOpenSampleReadMe) { @@ -1315,7 +1295,7 @@ export async function autoOpenProjectHandler(): Promise { export async function openReadMeHandler(...args: unknown[]) { ExtTelemetry.sendTelemetryEvent(TelemetryEvent.ClickOpenReadMe, getTriggerFromProperty(args)); - if (!globalVariables.isTeamsFxProject && !globalVariables.isOfficeAddInProject) { + if (!isTeamsFxProject && !isOfficeAddInProject) { const createProject = { title: localize("teamstoolkit.handlers.createProjectTitle"), run: async (): Promise => { @@ -1418,13 +1398,11 @@ export async function showLocalDebugMessage() { await globalStateUpdate(GlobalKey.ShowLocalDebugMessage, false); } - const hasLocalEnv = await fs.pathExists( - path.join(globalVariables.workspaceUri!.fsPath, "teamsapp.local.yml") - ); + const hasLocalEnv = await fs.pathExists(path.join(workspaceUri!.fsPath, "teamsapp.local.yml")); const appName = (await getAppName()) ?? localize("teamstoolkit.handlers.fallbackAppName"); const isWindows = process.platform === "win32"; - const folderLink = encodeURI(globalVariables.workspaceUri!.toString()); + const folderLink = encodeURI(workspaceUri!.toString()); const openFolderCommand = `command:fx-extension.openFolder?%5B%22${folderLink}%22%5D`; if (hasLocalEnv) { @@ -1438,7 +1416,7 @@ export async function showLocalDebugMessage() { const messageTemplate = await getLocalDebugMessageTemplate(isWindows); - let message = util.format(messageTemplate, appName, globalVariables.workspaceUri?.fsPath); + let message = util.format(messageTemplate, appName, workspaceUri?.fsPath); if (isWindows) { message = util.format(messageTemplate, appName, openFolderCommand); } @@ -1467,7 +1445,7 @@ export async function showLocalDebugMessage() { : util.format( localize("teamstoolkit.handlers.provisionDescription.fallback"), appName, - globalVariables.workspaceUri?.fsPath + workspaceUri?.fsPath ); void vscode.window.showInformationMessage(message, provision).then((selection) => { if (selection?.title === localize("teamstoolkit.handlers.provisionTitle")) { @@ -1810,7 +1788,7 @@ export async function openAzureAccountHandler() { } export function saveTextDocumentHandler(document: vscode.TextDocumentWillSaveEvent) { - if (!isValidProject(globalVariables.workspaceUri?.fsPath)) { + if (!isValidProject(workspaceUri?.fsPath)) { return; } @@ -1882,8 +1860,8 @@ export async function showError(e: UserError | SystemError) { }; const recommendTestTool = e.recommendedOperation === RecommendedOperations.DebugInTestTool && - globalVariables.workspaceUri?.fsPath && - commonUtils.isTestToolEnabledProject(globalVariables.workspaceUri.fsPath); + workspaceUri?.fsPath && + isTestToolEnabledProject(workspaceUri.fsPath); if (recommendTestTool) { const recommendTestToolMessage = openTestToolMessage(); @@ -2134,7 +2112,7 @@ export async function openPreviewAadFile(args: any[]): Promise { } ExtTelemetry.sendTelemetryEvent(telemetryStartName); - const workspacePath = globalVariables.workspaceUri?.fsPath; + const workspacePath = workspaceUri?.fsPath; if (!workspacePath) { const noOpenWorkspaceError = new UserError( ExtensionSource, @@ -2298,7 +2276,7 @@ export async function updatePreviewManifest(args: any[]): Promise { const result = await runCommand(Stage.deployTeams, inputs); if (!args || args.length === 0) { - const workspacePath = globalVariables.workspaceUri?.fsPath; + const workspacePath = workspaceUri?.fsPath; const inputs = getSystemInputs(); inputs.ignoreEnvInfo = true; const env = await core.getSelectedEnv(inputs); @@ -2341,7 +2319,7 @@ export function editAadManifestTemplate(args: any[]) { getTriggerFromProperty(args && args.length > 1 ? [args[1]] : undefined) ); if (args && args.length > 1) { - const workspacePath = globalVariables.workspaceUri?.fsPath; + const workspacePath = workspaceUri?.fsPath; const manifestPath = `${workspacePath as string}/${MetadataV3.aadManifestFileName}`; void workspace.openTextDocument(manifestPath).then((document) => { void window.showTextDocument(document); @@ -2368,7 +2346,6 @@ export async function signOutM365(isFromTreeView: boolean) { : TelemetryTriggerFrom.CommandPalette, [TelemetryProperty.AccountType]: AccountType.M365, }); - const vscodeEnv = detectVsCodeEnv(); let result = false; result = await M365TokenInstance.signout(); if (result) { @@ -2580,7 +2557,7 @@ export async function openLifecycleTreeview(args?: any[]) { TelemetryEvent.ClickOpenLifecycleTreeview, getTriggerFromProperty(args) ); - if (globalVariables.isTeamsFxProject) { + if (isTeamsFxProject) { await vscode.commands.executeCommand("teamsfx-lifecycle.focus"); } else { await vscode.commands.executeCommand("workbench.view.extension.teamsfx"); @@ -2600,7 +2577,7 @@ export async function selectTutorialsHandler( const config: SingleSelectConfig = { name: "tutorialName", title: localize("teamstoolkit.commandsTreeViewProvider.guideTitle"), - options: globalVariables.isSPFxProject + options: isSPFxProject ? [ { id: "cicdPipeline", @@ -2861,7 +2838,7 @@ export async function selectTutorialsHandler( ], returnObject: true, }; - if (TreatmentVariableValue.inProductDoc && !globalVariables.isSPFxProject) { + if (TreatmentVariableValue.inProductDoc && !isSPFxProject) { (config.options as StaticOptions).splice(0, 1, { id: "cardActionResponse", label: `${localize("teamstoolkit.guides.cardActionResponse.label")}`, diff --git a/packages/vscode-extension/src/handlers/walkthrough.ts b/packages/vscode-extension/src/handlers/walkthrough.ts index a401ebefbe..dccaf93e1c 100644 --- a/packages/vscode-extension/src/handlers/walkthrough.ts +++ b/packages/vscode-extension/src/handlers/walkthrough.ts @@ -1,13 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import * as handlerBase from "../handlers"; +import { runCommand } from "../handlers"; import * as commonUtils from "../utils/commonUtils"; import { ExtTelemetry } from "../telemetry/extTelemetry"; import { TelemetryEvent } from "../telemetry/extTelemetryEvents"; import { CreateProjectResult, FxError, Result, Stage } from "@microsoft/teamsfx-api"; +import { getSystemInputs } from "../utils/environmentUtils"; export async function createProjectFromWalkthroughHandler( args?: any[] @@ -18,13 +19,13 @@ export async function createProjectFromWalkthroughHandler( ); // parse questions model answers to inputs - const inputs = handlerBase.getSystemInputs(); + const inputs = getSystemInputs(); if (args && args.length >= 2 && args[1]) { Object.keys(args[1]).forEach((k) => { inputs[k] = args[1][k]; }); } - const result = await handlerBase.runCommand(Stage.create, inputs); + const result = await runCommand(Stage.create, inputs); return result; } diff --git a/packages/vscode-extension/src/hoverProvider.ts b/packages/vscode-extension/src/hoverProvider.ts index 0b5ba11257..a633052f64 100644 --- a/packages/vscode-extension/src/hoverProvider.ts +++ b/packages/vscode-extension/src/hoverProvider.ts @@ -2,10 +2,9 @@ // Licensed under the MIT license. import * as vscode from "vscode"; -import { environmentNameManager } from "@microsoft/teamsfx-core"; -import { envUtil } from "@microsoft/teamsfx-core"; +import { environmentNameManager, envUtil } from "@microsoft/teamsfx-core"; import { environmentVariableRegex } from "./constants"; -import { getSystemInputs } from "./handlers"; +import { getSystemInputs } from "./utils/environmentUtils"; import { DotenvParseOutput } from "dotenv"; export class ManifestTemplateHoverProvider implements vscode.HoverProvider { diff --git a/packages/vscode-extension/src/officeDevHandlers.ts b/packages/vscode-extension/src/officeDevHandlers.ts index 9e00657e11..ade6130e89 100644 --- a/packages/vscode-extension/src/officeDevHandlers.ts +++ b/packages/vscode-extension/src/officeDevHandlers.ts @@ -18,7 +18,7 @@ import * as vscode from "vscode"; import { Uri } from "vscode"; import { GlobalKey } from "./constants"; import { OfficeDevTerminal, TriggerCmdType } from "./debug/taskTerminal/officeDevTerminal"; -import { VS_CODE_UI } from "./extension"; +import { VS_CODE_UI } from "./qm/vsc_ui"; import * as globalVariables from "./globalVariables"; import { ShowScaffoldingWarningSummary, @@ -27,11 +27,15 @@ import { openSampleReadmeHandler, showLocalDebugMessage, } from "./handlers"; -import { TelemetryTriggerFrom, VSCodeWindowChoice } from "./telemetry/extTelemetryEvents"; +import { + TelemetryTriggerFrom, + VSCodeWindowChoice, + TelemetryEvent, + TelemetryProperty, +} from "./telemetry/extTelemetryEvents"; import { isTriggerFromWalkThrough, getTriggerFromProperty } from "./utils/commonUtils"; import { localize } from "./utils/localizeUtils"; import { ExtTelemetry } from "./telemetry/extTelemetry"; -import { TelemetryEvent, TelemetryProperty } from "./telemetry/extTelemetryEvents"; export async function openOfficePartnerCenterHandler( args?: any[] diff --git a/packages/vscode-extension/src/qm/vsc_ui.ts b/packages/vscode-extension/src/qm/vsc_ui.ts index 18c6c31fc2..a65375122a 100644 --- a/packages/vscode-extension/src/qm/vsc_ui.ts +++ b/packages/vscode-extension/src/qm/vsc_ui.ts @@ -13,22 +13,20 @@ import { } from "@microsoft/teamsfx-api"; import { assembleError, + isValidHttpUrl, loadingDefaultPlaceholder, loadingOptionsPlaceholder, } from "@microsoft/teamsfx-core"; -import { Localizer, VSCodeUI } from "@microsoft/vscode-ui"; +import { InternalUIError, Localizer, sleep, VSCodeUI } from "@microsoft/vscode-ui"; import * as packageJson from "../../package.json"; import { TerminalName } from "../constants"; import { ExtTelemetry } from "../telemetry/extTelemetry"; -import { sleep } from "../utils/commonUtils"; import { getDefaultString, localize } from "../utils/localizeUtils"; -import { InternalUIError } from "@microsoft/vscode-ui"; import { SelectFileOrInputResultType, TelemetryEvent, TelemetryProperty, } from "../telemetry/extTelemetryEvents"; -import { isValidHttpUrl } from "@microsoft/teamsfx-core"; export class TTKLocalizer implements Localizer { loadingOptionsPlaceholder(): string { @@ -76,6 +74,7 @@ export class TTKLocalizer implements Localizer { } export const ttkLocalizer = new TTKLocalizer(); +export let VS_CODE_UI: VsCodeUI; export class VsCodeUI extends VSCodeUI { context: ExtensionContext; @@ -138,3 +137,7 @@ export class VsCodeUI extends VSCodeUI { return res; } } + +export function initVSCodeUI(context: ExtensionContext) { + VS_CODE_UI = new VsCodeUI(context); +} diff --git a/packages/vscode-extension/src/telemetry/extTelemetry.ts b/packages/vscode-extension/src/telemetry/extTelemetry.ts index 24f5710edc..fb7d152300 100644 --- a/packages/vscode-extension/src/telemetry/extTelemetry.ts +++ b/packages/vscode-extension/src/telemetry/extTelemetry.ts @@ -3,12 +3,16 @@ import * as vscode from "vscode"; import { FxError, Stage } from "@microsoft/teamsfx-api"; -import { Correlator, telemetryUtils } from "@microsoft/teamsfx-core"; -import { globalStateGet, globalStateUpdate } from "@microsoft/teamsfx-core"; +import { + Correlator, + telemetryUtils, + globalStateGet, + globalStateUpdate, +} from "@microsoft/teamsfx-core"; import * as extensionPackage from "../../package.json"; import { VSCodeTelemetryReporter } from "../commonlib/telemetry"; import * as globalVariables from "../globalVariables"; -import { getProjectId } from "../utils/commonUtils"; +import { getProjectId } from "../utils/telemetryUtils"; import { TelemetryComponentType, TelemetryEvent, TelemetryProperty } from "./extTelemetryEvents"; const TelemetryCacheKey = "TelemetryEvents"; diff --git a/packages/vscode-extension/src/utils/commonUtils.ts b/packages/vscode-extension/src/utils/commonUtils.ts index 86fee26493..cb27f5161e 100644 --- a/packages/vscode-extension/src/utils/commonUtils.ts +++ b/packages/vscode-extension/src/utils/commonUtils.ts @@ -10,40 +10,11 @@ import { ConfigFolderName, SubscriptionInfo } from "@microsoft/teamsfx-api"; import { isValidProject } from "@microsoft/teamsfx-core"; import { glob } from "glob"; import { workspace } from "vscode"; -import * as extensionPackage from "../../package.json"; -import * as commonUtils from "../debug/commonUtils"; -import { getV3TeamsAppId } from "../debug/commonUtils"; -import * as globalVariables from "../globalVariables"; -import { core } from "../handlers"; +import { getProjectRoot, getV3TeamsAppId } from "../debug/commonUtils"; +import { workspaceUri, isTeamsFxProject, core } from "../globalVariables"; import { TelemetryProperty, TelemetryTriggerFrom } from "../telemetry/extTelemetryEvents"; import { localize } from "./localizeUtils"; -export function getPackageVersion(versionStr: string): string { - if (versionStr.includes("alpha")) { - return "alpha"; - } - - if (versionStr.includes("beta")) { - return "beta"; - } - - if (versionStr.includes("rc")) { - return "rc"; - } - - return "formal"; -} - -export function isFeatureFlag(): boolean { - return extensionPackage.featureFlag === "true"; -} - -export async function sleep(ms: number) { - await new Promise((resolve) => setTimeout(resolve, ms)); - - await new Promise((resolve) => setTimeout(resolve, 0)); -} - export function isWindows() { return os.type() === "Windows_NT"; } @@ -66,7 +37,7 @@ export async function getTeamsAppTelemetryInfoByEnv( env: string ): Promise { try { - const ws = globalVariables.workspaceUri!.fsPath; + const ws = workspaceUri!.fsPath; if (isValidProject(ws)) { const projectInfoRes = await core.getProjectInfo(ws, env); if (projectInfoRes.isOk()) { @@ -81,26 +52,12 @@ export async function getTeamsAppTelemetryInfoByEnv( return undefined; } -export async function getProjectId(): Promise { - if (!globalVariables.workspaceUri) { - return undefined; - } - try { - const ws = globalVariables.workspaceUri.fsPath; - const projInfoRes = await core.getProjectId(ws); - if (projInfoRes.isOk()) { - return projInfoRes.value; - } - } catch (e) {} - return undefined; -} - export async function getAppName(): Promise { - if (!globalVariables.workspaceUri) { + if (!workspaceUri) { return undefined; } try { - const ws = globalVariables.workspaceUri.fsPath; + const ws = workspaceUri.fsPath; const nameRes = await core.getTeamsAppName(ws); if (nameRes.isOk() && nameRes.value != "") { return nameRes.value; @@ -130,50 +87,6 @@ export async function isM365Project(workspacePath: string): Promise { } } -export function anonymizeFilePaths(stack?: string): string { - if (!stack) { - return ""; - } - const filePathRegex = /\s\(([a-zA-Z]:(\\|\/)([^\\\/\s:]+(\\|\/))+|\/([^\s:\/]+\/)+)/g; - const redactedErrorMessage = stack.replace(filePathRegex, " (/"); - return redactedErrorMessage; -} - -export class FeatureFlags { - static readonly InsiderPreview = "__TEAMSFX_INSIDER_PREVIEW"; - static readonly TelemetryTest = "TEAMSFX_TELEMETRY_TEST"; - static readonly DevTunnelTest = "TEAMSFX_DEV_TUNNEL_TEST"; - static readonly Preview = "TEAMSFX_PREVIEW"; - static readonly DevelopCopilotPlugin = "DEVELOP_COPILOT_PLUGIN"; - static readonly ChatParticipant = "TEAMSFX_CHAT_PARTICIPANT"; -} - -// Determine whether feature flag is enabled based on environment variable setting - -export function isFeatureFlagEnabled(featureFlagName: string, defaultValue = false): boolean { - const flag = process.env[featureFlagName]; - - if (flag === undefined) { - return defaultValue; // allows consumer to set a default value when environment variable not set - } else { - return flag === "1" || flag.toLowerCase() === "true"; // can enable feature flag by set environment variable value to "1" or "true" - } -} - -export function getAllFeatureFlags(): string[] | undefined { - const result = Object.values(FeatureFlags) - - .filter((featureFlag: string) => { - return isFeatureFlagEnabled(featureFlag); - }) - - .map((featureFlag) => { - return featureFlag; - }); - - return result; -} - export async function getSubscriptionInfoFromEnv( env: string ): Promise { @@ -255,23 +168,22 @@ export async function getResourceGroupNameFromEnv(env: string): Promise { // If TEAMS_APP_ID is set, it's highly possible that the project is provisioned. try { - const teamsAppId = await getV3TeamsAppId(globalVariables.workspaceUri!.fsPath, env); + const teamsAppId = await getV3TeamsAppId(workspaceUri!.fsPath, env); return teamsAppId !== ""; } catch (error) { return false; } } -async function getProvisionResultJson(env: string): Promise | undefined> { - if (globalVariables.workspaceUri) { - if (!globalVariables.isTeamsFxProject) { +export async function getProvisionResultJson( + env: string +): Promise | undefined> { + if (workspaceUri) { + if (!isTeamsFxProject) { return undefined; } - const configRoot = await commonUtils.getProjectRoot( - globalVariables.workspaceUri.fsPath, - `.${ConfigFolderName}` - ); + const configRoot = await getProjectRoot(workspaceUri.fsPath, `.${ConfigFolderName}`); const provisionOutputFile = path.join(configRoot!, path.join("states", `state.${env}.json`)); @@ -348,8 +260,8 @@ export async function hasAdaptiveCardInWorkspace(): Promise { // Skip large files which are unlikely to be adaptive cards to prevent performance impact. const fileSizeLimit = 1024 * 1024; - if (globalVariables.workspaceUri) { - const files = await glob(globalVariables.workspaceUri.path + "/**/*.json", { + if (workspaceUri) { + const files = await glob(workspaceUri.path + "/**/*.json", { ignore: ["**/node_modules/**", "./node_modules/**"], }); for (const file of files) { diff --git a/packages/vscode-extension/src/utils/environmentUtils.ts b/packages/vscode-extension/src/utils/environmentUtils.ts new file mode 100644 index 0000000000..17fe34d606 --- /dev/null +++ b/packages/vscode-extension/src/utils/environmentUtils.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import * as vscode from "vscode"; +import { Inputs, Platform, VsCodeEnv } from "@microsoft/teamsfx-api"; +import { workspaceUri } from "../globalVariables"; +import { loadedLocale } from "./localizeUtils"; + +export function detectVsCodeEnv(): VsCodeEnv { + // extensionKind returns ExtensionKind.UI when running locally, so use this to detect remote + const extension = vscode.extensions.getExtension("TeamsDevApp.ms-teams-vscode-extension"); + + if (extension?.extensionKind === vscode.ExtensionKind.Workspace) { + // running remotely + // Codespaces browser-based editor will return UIKind.Web for uiKind + if (vscode.env.uiKind === vscode.UIKind.Web) { + return VsCodeEnv.codespaceBrowser; + } else if (vscode.env.remoteName === "codespaces") { + return VsCodeEnv.codespaceVsCode; + } else { + return VsCodeEnv.remote; + } + } else { + // running locally + return VsCodeEnv.local; + } +} + +export function getSystemInputs(): Inputs { + const answers: Inputs = { + projectPath: workspaceUri?.fsPath, + platform: Platform.VSCode, + vscodeEnv: detectVsCodeEnv(), + locale: loadedLocale, + }; + return answers; +} diff --git a/packages/vscode-extension/src/utils/fileSystemUtils.ts b/packages/vscode-extension/src/utils/fileSystemUtils.ts new file mode 100644 index 0000000000..1b091ba11a --- /dev/null +++ b/packages/vscode-extension/src/utils/fileSystemUtils.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function anonymizeFilePaths(stack?: string): string { + if (!stack) { + return ""; + } + const filePathRegex = /\s\(([a-zA-Z]:(\\|\/)([^\\\/\s:]+(\\|\/))+|\/([^\s:\/]+\/)+)/g; + const redactedErrorMessage = stack.replace(filePathRegex, " (/"); + return redactedErrorMessage; +} diff --git a/packages/vscode-extension/src/utils/migrationUtils.ts b/packages/vscode-extension/src/utils/migrationUtils.ts new file mode 100644 index 0000000000..82e79b5555 --- /dev/null +++ b/packages/vscode-extension/src/utils/migrationUtils.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import * as vscode from "vscode"; +import { Stage } from "@microsoft/teamsfx-api"; +import { VS_CODE_UI } from "../qm/vsc_ui"; +import { core } from "../globalVariables"; +import { getSystemInputs } from "../utils/environmentUtils"; + +export async function triggerV3Migration(): Promise { + const inputs = getSystemInputs(); + inputs.stage = Stage.debug; + const result = await core.phantomMigrationV3(inputs); + if (result.isErr()) { + await vscode.debug.stopDebugging(); + throw result.error; + } + // reload window to terminate debugging + await VS_CODE_UI.reload(); +} diff --git a/packages/vscode-extension/src/utils/projectChecker.ts b/packages/vscode-extension/src/utils/projectChecker.ts index 240f9dfbfc..075710343f 100644 --- a/packages/vscode-extension/src/utils/projectChecker.ts +++ b/packages/vscode-extension/src/utils/projectChecker.ts @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import { telemetryUtils } from "@microsoft/teamsfx-core"; -import { workspaceUri } from "../globalVariables"; -import { core } from "../handlers"; +import { core, workspaceUri } from "../globalVariables"; import { ExtTelemetry } from "../telemetry/extTelemetry"; export async function checkProjectTypeAndSendTelemetry(): Promise { diff --git a/packages/vscode-extension/src/utils/telemetryUtils.ts b/packages/vscode-extension/src/utils/telemetryUtils.ts new file mode 100644 index 0000000000..d61bed260b --- /dev/null +++ b/packages/vscode-extension/src/utils/telemetryUtils.ts @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { workspaceUri, core } from "../globalVariables"; + +export function getPackageVersion(versionStr: string): string { + if (versionStr.includes("alpha")) { + return "alpha"; + } + + if (versionStr.includes("beta")) { + return "beta"; + } + + if (versionStr.includes("rc")) { + return "rc"; + } + + return "formal"; +} + +export async function getProjectId(): Promise { + if (!workspaceUri) { + return undefined; + } + try { + const ws = workspaceUri.fsPath; + const projInfoRes = await core.getProjectId(ws); + if (projInfoRes.isOk()) { + return projInfoRes.value; + } + } catch (e) {} + return undefined; +} diff --git a/packages/vscode-extension/test/chat/commands/create/createCommandHandler.test.ts b/packages/vscode-extension/test/chat/commands/create/createCommandHandler.test.ts index 112d5acad7..a16dceb7df 100644 --- a/packages/vscode-extension/test/chat/commands/create/createCommandHandler.test.ts +++ b/packages/vscode-extension/test/chat/commands/create/createCommandHandler.test.ts @@ -13,9 +13,13 @@ import { CancellationToken } from "../../../mocks/vsc"; chai.use(chaiPromised); describe("chat create command", () => { - const sandbox = sinon.createSandbox(); + afterEach(() => { + sinon.restore(); + }); describe("createCommandHandler()", () => { + const sandbox = sinon.createSandbox(); + afterEach(async () => { sandbox.restore(); }); diff --git a/packages/vscode-extension/test/chat/commands/create/helper.test.ts b/packages/vscode-extension/test/chat/commands/create/helper.test.ts index eee8bf179b..bbbdcc7f51 100644 --- a/packages/vscode-extension/test/chat/commands/create/helper.test.ts +++ b/packages/vscode-extension/test/chat/commands/create/helper.test.ts @@ -18,9 +18,13 @@ import { CancellationToken } from "../../../mocks/vsc"; chai.use(chaiPromised); describe("chat create helper", () => { - const sandbox = sinon.createSandbox(); + afterEach(() => { + sinon.restore(); + }); describe("matchProject()", () => { + const sandbox = sinon.createSandbox(); + afterEach(async () => { sandbox.restore(); }); @@ -109,6 +113,7 @@ describe("chat create helper", () => { }); describe("showFileTree()", () => { + const sandbox = sinon.createSandbox(); afterEach(async () => { sandbox.restore(); }); diff --git a/packages/vscode-extension/test/chat/commands/nextstep/nextstepCommandHandler.test.ts b/packages/vscode-extension/test/chat/commands/nextstep/nextstepCommandHandler.test.ts index 51bc1ea637..b8852c210f 100644 --- a/packages/vscode-extension/test/chat/commands/nextstep/nextstepCommandHandler.test.ts +++ b/packages/vscode-extension/test/chat/commands/nextstep/nextstepCommandHandler.test.ts @@ -18,9 +18,13 @@ import { CHAT_EXECUTE_COMMAND_ID, CHAT_OPENURL_COMMAND_ID } from "../../../../sr chai.use(chaiPromised); describe("chat nextstep handler", () => { - const sandbox = sinon.createSandbox(); + afterEach(() => { + sinon.restore(); + }); describe("nextstepCommandHandler()", () => { + const sandbox = sinon.createSandbox(); + afterEach(async () => { sandbox.restore(); }); diff --git a/packages/vscode-extension/test/chat/commands/nextstep/status.test.ts b/packages/vscode-extension/test/chat/commands/nextstep/status.test.ts index 52b5fc2047..d8f70a00ea 100644 --- a/packages/vscode-extension/test/chat/commands/nextstep/status.test.ts +++ b/packages/vscode-extension/test/chat/commands/nextstep/status.test.ts @@ -4,18 +4,19 @@ import * as sinon from "sinon"; import * as status from "../../../../src/chat/commands/nextstep/status"; import * as helper from "../../../../src/chat/commands/nextstep/helper"; import { MachineStatus, WholeStatus } from "../../../../src/chat/commands/nextstep/types"; -import { CommandKey } from "../../../../src/constants"; import * as projectStatusUtils from "../../../../src/utils/projectStatusUtils"; chai.use(chaiPromised); describe("chat nextstep status", () => { - const sandbox = sinon.createSandbox(); afterEach(() => { - sandbox.restore(); + // Restore the default sandbox here + sinon.restore(); }); describe("func: getWholeStatus", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { sandbox.restore(); }); @@ -96,14 +97,22 @@ describe("chat nextstep status", () => { }); }); - it("func: getMachineStatus", async () => { - sandbox.stub(helper, "checkCredential").resolves({ m365LoggedIn: true, azureLoggedIn: true }); - sandbox.stub(helper, "globalStateGet").resolves(true); - sandbox.stub(helper, "globalStateUpdate"); - await chai.expect(status.getMachineStatus()).to.eventually.deep.equal({ - azureLoggedIn: true, - firstInstalled: true, - m365LoggedIn: true, - } as MachineStatus); + describe("func: getMachineStatus", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + it("func: getMachineStatus", async () => { + sandbox.stub(helper, "checkCredential").resolves({ m365LoggedIn: true, azureLoggedIn: true }); + sandbox.stub(helper, "globalStateGet").resolves(true); + sandbox.stub(helper, "globalStateUpdate"); + await chai.expect(status.getMachineStatus()).to.eventually.deep.equal({ + azureLoggedIn: true, + firstInstalled: true, + m365LoggedIn: true, + } as MachineStatus); + }); }); }); diff --git a/packages/vscode-extension/test/chat/handlers.test.ts b/packages/vscode-extension/test/chat/handlers.test.ts index b07fb2e47e..8cd720aa1a 100644 --- a/packages/vscode-extension/test/chat/handlers.test.ts +++ b/packages/vscode-extension/test/chat/handlers.test.ts @@ -19,9 +19,12 @@ import { openUrlCommandHandler } from "../../src/chat/handlers"; import { CommandKey } from "../../src/constants"; describe("chat handlers", () => { - const sandbox = sinon.createSandbox(); + afterEach(() => { + sinon.restore(); + }); describe("chatRequestHandler()", () => { + const sandbox = sinon.createSandbox(); const response = { markdown: sandbox.stub(), button: sandbox.stub(), @@ -162,6 +165,7 @@ Usage: @teams Ask questions about Teams Development"`); }); describe("chatExecuteCommandHandler()", () => { + const sandbox = sinon.createSandbox(); afterEach(async () => { sandbox.restore(); }); @@ -196,6 +200,7 @@ Usage: @teams Ask questions about Teams Development"`); }); describe("openUrlCommandHandler()", () => { + const sandbox = sinon.createSandbox(); afterEach(async () => { sandbox.restore(); }); @@ -206,6 +211,7 @@ Usage: @teams Ask questions about Teams Development"`); }); describe("handleFeedback()", () => { + const sandbox = sinon.createSandbox(); afterEach(async () => { sandbox.restore(); }); diff --git a/packages/vscode-extension/test/chat/utils.test.ts b/packages/vscode-extension/test/chat/utils.test.ts index ae1a252d36..aee187246a 100644 --- a/packages/vscode-extension/test/chat/utils.test.ts +++ b/packages/vscode-extension/test/chat/utils.test.ts @@ -16,9 +16,14 @@ import { chai.use(chaiPromised); describe("chat utils", () => { - const sandbox = sinon.createSandbox(); + afterEach(() => { + // Restore the default sandbox here + sinon.restore(); + }); describe("verbatimCopilotInteraction()", () => { + const sandbox = sinon.createSandbox(); + afterEach(async () => { sandbox.restore(); }); @@ -66,6 +71,8 @@ describe("chat utils", () => { }); describe("getCopilotResponseAsString()", () => { + const sandbox = sinon.createSandbox(); + afterEach(async () => { sandbox.restore(); }); @@ -103,6 +110,8 @@ describe("chat utils", () => { }); describe("getSampleDownloadUrlInfo()", () => { + const sandbox = sinon.createSandbox(); + afterEach(async () => { sandbox.restore(); }); @@ -114,7 +123,7 @@ describe("chat utils", () => { ref: "test", dir: "test", }; - sinon.stub(sampleProvider, "SampleCollection").get(() => { + sandbox.stub(sampleProvider, "SampleCollection").get(() => { return Promise.resolve({ samples: [ { @@ -129,7 +138,7 @@ describe("chat utils", () => { }); it("throws error if not found", async () => { - sinon.stub(sampleProvider, "SampleCollection").get(() => { + sandbox.stub(sampleProvider, "SampleCollection").get(() => { return Promise.resolve({ samples: [ { @@ -146,6 +155,8 @@ describe("chat utils", () => { }); describe("countMessageTokens()", () => { + const sandbox = sinon.createSandbox(); + beforeEach(() => { sandbox.stub(Tokenizer.getInstance(), "tokenLength").callsFake((content): number => { return content.length; @@ -189,6 +200,8 @@ describe("chat utils", () => { }); describe("countMessagesTokens()", () => { + const sandbox = sinon.createSandbox(); + beforeEach(() => { sandbox.stub(Tokenizer.getInstance(), "tokenLength").callsFake((content): number => { return content.length; diff --git a/packages/vscode-extension/test/extension/codeLensProvider.test.ts b/packages/vscode-extension/test/extension/codeLensProvider.test.ts index 86b9a6d392..85cd7caa07 100644 --- a/packages/vscode-extension/test/extension/codeLensProvider.test.ts +++ b/packages/vscode-extension/test/extension/codeLensProvider.test.ts @@ -19,461 +19,477 @@ import * as globalVariables from "../../src/globalVariables"; import { TelemetryTriggerFrom } from "../../src/telemetry/extTelemetryEvents"; import path = require("path"); -describe("Manifest codelens", () => { +describe("CodeLens Provider", () => { afterEach(() => { sinon.restore(); }); - it("Template codelens - V3", async () => { - const url = - "https://developer.microsoft.com/en-us/json-schemas/teams/v1.14/MicrosoftTeams.schema.json"; - const document = { - fileName: "manifest.template.json", - getText: () => { - return `"$schema": "${url}",`; - }, - positionAt: () => { - return new vscode.Position(0, 0); - }, - lineAt: () => { - return { - lineNumber: 0, - text: `"$schema": "${url}",`, - }; - }, - } as any as vscode.TextDocument; - - const manifestProvider = new ManifestTemplateCodeLensProvider(); - const codelens: vscode.CodeLens[] = manifestProvider.provideCodeLenses( - document - ) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 1); - chai.expect(codelens[0].command).to.deep.equal({ - title: "Open schema", - command: "fx-extension.openSchema", - arguments: [{ url: url }], + describe("Manifest codelens", () => { + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + sandbox.stub(envUtil, "readEnv").resolves(ok({})); }); - }); - it("ResolveEnvironmentVariableCodelens", async () => { - sinon.stub(envUtil, "readEnv").resolves(ok({})); - - const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)); - const lens: PlaceholderCodeLens = new PlaceholderCodeLens( - "${{ TEAMS_APP_ID }}", - range, - "manifest.template.json" - ); - const manifestProvider = new ManifestTemplateCodeLensProvider(); - const cts = new vscode.CancellationTokenSource(); - - const res = await manifestProvider.resolveCodeLens(lens, cts.token); - chai.assert.equal(res.command?.command, "fx-extension.openConfigState"); - chai.assert.isTrue(res.command?.title.includes("👉")); - chai.expect(res.command?.arguments).to.deep.equal([{ type: "env", from: "manifest" }]); - }); + afterEach(() => { + sandbox.restore(); + }); - it("ResolveEnvironmentVariableCodelens for AAD manifest", async () => { - sinon.stub(envUtil, "readEnv").resolves(ok({})); - - const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)); - const lens: PlaceholderCodeLens = new PlaceholderCodeLens( - "${{ TEAMS_APP_ID }}", - range, - "aad.template.json" - ); - const aadProvider = new AadAppTemplateCodeLensProvider(); - const cts = new vscode.CancellationTokenSource(); - - const res = await aadProvider.resolveCodeLens(lens, cts.token); - chai.assert.equal(res.command?.command, "fx-extension.openConfigState"); - chai.assert.isTrue(res.command?.title.includes("👉")); - chai.expect(res.command?.arguments).to.deep.equal([{ type: "env", from: "aad" }]); - }); + it("Template codelens - V3", async () => { + const url = + "https://developer.microsoft.com/en-us/json-schemas/teams/v1.14/MicrosoftTeams.schema.json"; + const document = { + fileName: "manifest.template.json", + getText: () => { + return `"$schema": "${url}",`; + }, + positionAt: () => { + return new vscode.Position(0, 0); + }, + lineAt: () => { + return { + lineNumber: 0, + text: `"$schema": "${url}",`, + }; + }, + } as any as vscode.TextDocument; + + const manifestProvider = new ManifestTemplateCodeLensProvider(); + const codelens: vscode.CodeLens[] = manifestProvider.provideCodeLenses( + document + ) as vscode.CodeLens[]; + + chai.assert.equal(codelens.length, 1); + chai.expect(codelens[0].command).to.deep.equal({ + title: "Open schema", + command: "fx-extension.openSchema", + arguments: [{ url: url }], + }); + }); - it("ComputeTemplateCodeLenses for AAD manifest template", async () => { - sinon.stub(envUtil, "readEnv").resolves(ok({})); - const document = { - fileName: "./aad.manifest.json", - getText: () => { - return "{name: 'test'}"; - }, - }; - - const aadProvider = new AadAppTemplateCodeLensProvider(); - const res = await aadProvider.provideCodeLenses(document); - chai.assert.isTrue( - res != null && res[0].command!.command === "fx-extension.openPreviewAadFile" - ); - }); + it("ResolveEnvironmentVariableCodelens", async () => { + const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)); + const lens: PlaceholderCodeLens = new PlaceholderCodeLens( + "${{ TEAMS_APP_ID }}", + range, + "manifest.template.json" + ); + const manifestProvider = new ManifestTemplateCodeLensProvider(); + const cts = new vscode.CancellationTokenSource(); + + const res = await manifestProvider.resolveCodeLens(lens, cts.token); + chai.assert.equal(res.command?.command, "fx-extension.openConfigState"); + chai.assert.isTrue(res.command?.title.includes("👉")); + chai.expect(res.command?.arguments).to.deep.equal([{ type: "env", from: "manifest" }]); + }); - it("ComputeTemplateCodeLenses for aad manifest", async () => { - sinon.stub(envUtil, "readEnv").resolves(ok({})); - sinon.stub(fs, "pathExistsSync").returns(true); - const document = { - fileName: "./build/aad.manifest.dev.json", - getText: () => { - return "{name: 'test'}"; - }, - }; - - sinon.stub(vscode.workspace, "workspaceFolders").value([{ uri: { fsPath: "workspacePath" } }]); - - const aadProvider = new AadAppTemplateCodeLensProvider(); - const res = await aadProvider.provideCodeLenses(document); - console.log(res); - chai.assert.isTrue( - res != null && res[0].command!.command === "fx-extension.updateAadAppManifest" - ); - - chai.assert.isTrue( - res != null && res[1].command!.command === "fx-extension.editAadManifestTemplate" - ); - }); + it("ResolveEnvironmentVariableCodelens for AAD manifest", async () => { + const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)); + const lens: PlaceholderCodeLens = new PlaceholderCodeLens( + "${{ TEAMS_APP_ID }}", + range, + "aad.template.json" + ); + const aadProvider = new AadAppTemplateCodeLensProvider(); + const cts = new vscode.CancellationTokenSource(); + + const res = await aadProvider.resolveCodeLens(lens, cts.token); + chai.assert.equal(res.command?.command, "fx-extension.openConfigState"); + chai.assert.isTrue(res.command?.title.includes("👉")); + chai.expect(res.command?.arguments).to.deep.equal([{ type: "env", from: "aad" }]); + }); - it("ComputeTemplateCodeLenses for aad manifest if template not exist", async () => { - sinon.stub(envUtil, "readEnv").resolves(ok({})); - sinon.stub(fs, "pathExistsSync").returns(false); - const document = { - fileName: "./build/aad.manifest.dev.json", - getText: () => { - return "{name: 'test'}"; - }, - }; + it("ComputeTemplateCodeLenses for AAD manifest template", async () => { + const document = { + fileName: "./aad.manifest.json", + getText: () => { + return "{name: 'test'}"; + }, + }; - sinon.stub(vscode.workspace, "workspaceFolders").value([{ uri: { fsPath: "workspacePath" } }]); + const aadProvider = new AadAppTemplateCodeLensProvider(); + const res = await aadProvider.provideCodeLenses(document); + chai.assert.isTrue( + res != null && res[0].command!.command === "fx-extension.openPreviewAadFile" + ); + }); - const aadProvider = new AadAppTemplateCodeLensProvider(); - const res = await aadProvider.provideCodeLenses(document); + it("ComputeTemplateCodeLenses for aad manifest", async () => { + sandbox.stub(fs, "pathExistsSync").returns(true); + const document = { + fileName: "./build/aad.manifest.dev.json", + getText: () => { + return "{name: 'test'}"; + }, + }; + + sandbox + .stub(vscode.workspace, "workspaceFolders") + .value([{ uri: { fsPath: "workspacePath" } }]); + + const aadProvider = new AadAppTemplateCodeLensProvider(); + const res = await aadProvider.provideCodeLenses(document); + console.log(res); + chai.assert.isTrue( + res != null && res[0].command!.command === "fx-extension.updateAadAppManifest" + ); + + chai.assert.isTrue( + res != null && res[1].command!.command === "fx-extension.editAadManifestTemplate" + ); + }); - console.log(res); + it("ComputeTemplateCodeLenses for aad manifest if template not exist", async () => { + sandbox.stub(fs, "pathExistsSync").returns(false); + const document = { + fileName: "./build/aad.manifest.dev.json", + getText: () => { + return "{name: 'test'}"; + }, + }; - chai.assert.isTrue( - res != null && - res.length === 1 && - res[0].command!.command === "fx-extension.updateAadAppManifest" - ); - }); + sandbox + .stub(vscode.workspace, "workspaceFolders") + .value([{ uri: { fsPath: "workspacePath" } }]); - it("PermissionsJsonFileCodeLensProvider for Microsoft Entra manifest template", async () => { - sinon.stub(envUtil, "readEnv").resolves(ok({})); - sinon.stub(fs, "pathExistsSync").returns(true); - sinon.stub(vscode.workspace, "workspaceFolders").value([{ uri: { fsPath: "workspacePath" } }]); - const document = { - fileName: "./aad.manifest.json", - getText: () => { - return "{name: 'test'}"; - }, - }; - - const permissionsJsonFile = new PermissionsJsonFileCodeLensProvider(); - const res = await permissionsJsonFile.provideCodeLenses(document); - chai.assert.isTrue( - res != null && res[0].command!.command === "fx-extension.editAadManifestTemplate" - ); - }); -}); + const aadProvider = new AadAppTemplateCodeLensProvider(); + const res = await aadProvider.provideCodeLenses(document); -describe("Crypto CodeLensProvider", () => { - afterEach(() => { - sinon.restore(); - }); + console.log(res); - it("envData codelens", async () => { - const document = { - fileName: ".env.local", - getText: () => { - return "SECRET_VAR_2=crypto_abc"; - }, - lineAt: () => { - return { - lineNumber: 0, - text: "SECRET_VAR_2=crypto_abc", - }; - }, - positionAt: () => { - return { - character: 0, - line: 0, - }; - }, - } as unknown as vscode.TextDocument; - - const cryptoProvider = new CryptoCodeLensProvider(); - const codelens: vscode.CodeLens[] = cryptoProvider.provideCodeLenses( - document - ) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 1); - chai.expect(codelens[0].command?.title).equal("🔑Decrypt secret"); - chai.expect(codelens[0].command?.command).equal("fx-extension.decryptSecret"); - sinon.restore(); - }); + chai.assert.isTrue( + res != null && + res.length === 1 && + res[0].command!.command === "fx-extension.updateAadAppManifest" + ); + }); - it("hides when command is running", async () => { - sinon.stub(globalVariables, "commandIsRunning").value(true); - const document = { - fileName: ".env.local", - getText: () => { - return "SECRET_VAR_2=crypto_abc"; - }, - lineAt: () => { - return { - lineNumber: 0, - text: "SECRET_VAR_2=crypto_abc", - }; - }, - positionAt: () => { - return { - character: 0, - line: 0, - }; - }, - } as unknown as vscode.TextDocument; - - const cryptoProvider = new CryptoCodeLensProvider(); - const codelens: vscode.CodeLens[] = cryptoProvider.provideCodeLenses( - document - ) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 0); - }); -}); + it("PermissionsJsonFileCodeLensProvider for Microsoft Entra manifest template", async () => { + sandbox.stub(fs, "pathExistsSync").returns(true); + sandbox + .stub(vscode.workspace, "workspaceFolders") + .value([{ uri: { fsPath: "workspacePath" } }]); + const document = { + fileName: "./aad.manifest.json", + getText: () => { + return "{name: 'test'}"; + }, + }; -describe("API ME CodeLensProvider", () => { - afterEach(() => { - sinon.restore(); + const permissionsJsonFile = new PermissionsJsonFileCodeLensProvider(); + const res = await permissionsJsonFile.provideCodeLenses(document); + chai.assert.isTrue( + res != null && res[0].command!.command === "fx-extension.editAadManifestTemplate" + ); + }); }); - it("Add API", async () => { - const manifest = new TeamsAppManifest(); - manifest.composeExtensions = [ - { - composeExtensionType: "apiBased", - commands: [], - }, - ]; - const manifestString = JSON.stringify(manifest); - const document = { - fileName: "manifest.json", - getText: () => { - return manifestString; - }, - positionAt: () => { - return new vscode.Position(0, 0); - }, - lineAt: () => { - return { - lineNumber: 0, - text: manifestString, - }; - }, - } as any as vscode.TextDocument; - - const copilotPluginCodelensProvider = new CopilotPluginCodeLensProvider(); - const codelens: vscode.CodeLens[] = copilotPluginCodelensProvider.provideCodeLenses( - document - ) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 1); - chai.expect(codelens[0].command).to.deep.equal({ - title: "➕Add another API", - command: "fx-extension.copilotPluginAddAPI", - arguments: [{ fsPath: document.fileName }], + describe("Crypto CodeLensProvider", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); }); - }); - it("Do not show codelens for non-copilot plugin project", async () => { - const manifest = new TeamsAppManifest(); - const manifestString = JSON.stringify(manifest); - const document = { - fileName: "manifest.json", - getText: () => { - return manifestString; - }, - positionAt: () => { - return new vscode.Position(0, 0); - }, - lineAt: () => { - return { - lineNumber: 0, - text: manifestString, - }; - }, - } as any as vscode.TextDocument; - - const copilotPluginCodelensProvider = new CopilotPluginCodeLensProvider(); - const codelens: vscode.CodeLens[] = copilotPluginCodelensProvider.provideCodeLenses( - document - ) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 0); - }); -}); + it("envData codelens", async () => { + const document = { + fileName: ".env.local", + getText: () => { + return "SECRET_VAR_2=crypto_abc"; + }, + lineAt: () => { + return { + lineNumber: 0, + text: "SECRET_VAR_2=crypto_abc", + }; + }, + positionAt: () => { + return { + character: 0, + line: 0, + }; + }, + } as unknown as vscode.TextDocument; -describe("Api plugin CodeLensProvider", () => { - afterEach(() => { - sinon.restore(); + const cryptoProvider = new CryptoCodeLensProvider(); + const codelens: vscode.CodeLens[] = cryptoProvider.provideCodeLenses( + document + ) as vscode.CodeLens[]; + + chai.assert.equal(codelens.length, 1); + chai.expect(codelens[0].command?.title).equal("🔑Decrypt secret"); + chai.expect(codelens[0].command?.command).equal("fx-extension.decryptSecret"); + }); + + it("hides when command is running", async () => { + sandbox.stub(globalVariables, "commandIsRunning").value(true); + const document = { + fileName: ".env.local", + getText: () => { + return "SECRET_VAR_2=crypto_abc"; + }, + lineAt: () => { + return { + lineNumber: 0, + text: "SECRET_VAR_2=crypto_abc", + }; + }, + positionAt: () => { + return { + character: 0, + line: 0, + }; + }, + } as unknown as vscode.TextDocument; + + const cryptoProvider = new CryptoCodeLensProvider(); + const codelens: vscode.CodeLens[] = cryptoProvider.provideCodeLenses( + document + ) as vscode.CodeLens[]; + + chai.assert.equal(codelens.length, 0); + }); }); - it("Add API", async () => { - const manifest = new TeamsAppManifest(); - manifest.copilotExtensions = { - plugins: [ + describe("API ME CodeLensProvider", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + it("Add API", async () => { + const manifest = new TeamsAppManifest(); + manifest.composeExtensions = [ { - file: "test.json", - id: "plugin1", - }, - ], - }; - const openApiObject = { - openapi: "3.0", - }; - const text = JSON.stringify(openApiObject); - const document = { - fileName: "openapi.yaml", - getText: () => { - return text; - }, - positionAt: () => { - return new vscode.Position(0, 0); - }, - lineAt: () => { - return { - lineNumber: 0, - text, - }; - }, - } as any as vscode.TextDocument; - - sinon.stub(fs, "existsSync").returns(true); - sinon.stub(fs, "readFileSync").returns(JSON.stringify(manifest)); - sinon - .stub(globalVariables, "workspaceUri") - .value(vscode.Uri.parse(path.resolve(__dirname, "unknown"))); - const apiPluginCodelensProvider = new ApiPluginCodeLensProvider(); - const codelens: vscode.CodeLens[] = apiPluginCodelensProvider.provideCodeLenses( - document - ) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 1); - chai.expect(codelens[0].command!.title).to.equal("➕Add another API"); - chai.expect(codelens[0].command!.command).to.equal("fx-extension.copilotPluginAddAPI"); - chai.expect(codelens[0].command!.arguments![0].fsPath).to.equal(document.fileName); - chai.expect(codelens[0].command!.arguments![0].isFromApiPlugin).to.be.true; - }); + composeExtensionType: "apiBased", + commands: [], + }, + ]; + const manifestString = JSON.stringify(manifest); + const document = { + fileName: "manifest.json", + getText: () => { + return manifestString; + }, + positionAt: () => { + return new vscode.Position(0, 0); + }, + lineAt: () => { + return { + lineNumber: 0, + text: manifestString, + }; + }, + } as any as vscode.TextDocument; + + const copilotPluginCodelensProvider = new CopilotPluginCodeLensProvider(); + const codelens: vscode.CodeLens[] = copilotPluginCodelensProvider.provideCodeLenses( + document + ) as vscode.CodeLens[]; + + chai.assert.equal(codelens.length, 1); + chai.expect(codelens[0].command).to.deep.equal({ + title: "➕Add another API", + command: "fx-extension.copilotPluginAddAPI", + arguments: [{ fsPath: document.fileName }], + }); + }); - it("Do not show codelens for if not api spec file", async () => { - const openApiObject = { - unknown: "3.0", - }; - const text = JSON.stringify(openApiObject); - const document = { - fileName: "openapi.yaml", - getText: () => { - return text; - }, - positionAt: () => { - return new vscode.Position(0, 0); - }, - lineAt: () => { - return { - lineNumber: 0, - text, - }; - }, - } as any as vscode.TextDocument; - - sinon.stub(fs, "existsSync").returns(false); - sinon - .stub(globalVariables, "workspaceUri") - .value(vscode.Uri.parse(path.resolve(__dirname, "unknown"))); - const apiPluginCodelensProvider = new ApiPluginCodeLensProvider(); - const codelens: vscode.CodeLens[] = apiPluginCodelensProvider.provideCodeLenses( - document - ) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 0); - }); + it("Do not show codelens for non-copilot plugin project", async () => { + const manifest = new TeamsAppManifest(); + const manifestString = JSON.stringify(manifest); + const document = { + fileName: "manifest.json", + getText: () => { + return manifestString; + }, + positionAt: () => { + return new vscode.Position(0, 0); + }, + lineAt: () => { + return { + lineNumber: 0, + text: manifestString, + }; + }, + } as any as vscode.TextDocument; - it("Do not show codelens for if Teams manifest not exist", async () => { - const openApiObject = { - openapi: "3.0", - }; - const text = JSON.stringify(openApiObject); - const document = { - fileName: "openapi.yaml", - getText: () => { - return text; - }, - positionAt: () => { - return new vscode.Position(0, 0); - }, - lineAt: () => { - return { - lineNumber: 0, - text, - }; - }, - } as any as vscode.TextDocument; - - sinon.stub(fs, "existsSync").returns(false); - sinon - .stub(globalVariables, "workspaceUri") - .value(vscode.Uri.parse(path.resolve(__dirname, "unknown"))); - const apiPluginCodelensProvider = new ApiPluginCodeLensProvider(); - const codelens: vscode.CodeLens[] = apiPluginCodelensProvider.provideCodeLenses( - document - ) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 0); - }); + const copilotPluginCodelensProvider = new CopilotPluginCodeLensProvider(); + const codelens: vscode.CodeLens[] = copilotPluginCodelensProvider.provideCodeLenses( + document + ) as vscode.CodeLens[]; - it("Do not show codelens for if not API plugin project", async () => { - const manifest = new TeamsAppManifest(); - manifest.copilotExtensions = {}; - const openApiObject = { - openapi: "3.0", - }; - const text = JSON.stringify(openApiObject); - const document = { - fileName: "openapi.yaml", - getText: () => { - return text; - }, - positionAt: () => { - return new vscode.Position(0, 0); - }, - lineAt: () => { - return { - lineNumber: 0, - text, - }; - }, - } as any as vscode.TextDocument; - - sinon.stub(fs, "existsSync").returns(true); - sinon.stub(fs, "readFileSync").returns(JSON.stringify(manifest)); - sinon - .stub(globalVariables, "workspaceUri") - .value(vscode.Uri.parse(path.resolve(__dirname, "unknown"))); - const apiPluginCodelensProvider = new ApiPluginCodeLensProvider(); - const codelens: vscode.CodeLens[] = apiPluginCodelensProvider.provideCodeLenses( - document - ) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 0); + chai.assert.equal(codelens.length, 0); + }); }); -}); -describe("teamsapp.yml CodeLensProvider", () => { - afterEach(() => { - sinon.restore(); + describe("Api plugin CodeLensProvider", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + it("Add API", async () => { + const manifest = new TeamsAppManifest(); + manifest.copilotExtensions = { + plugins: [ + { + file: "test.json", + id: "plugin1", + }, + ], + }; + const openApiObject = { + openapi: "3.0", + }; + const text = JSON.stringify(openApiObject); + const document = { + fileName: "openapi.yaml", + getText: () => { + return text; + }, + positionAt: () => { + return new vscode.Position(0, 0); + }, + lineAt: () => { + return { + lineNumber: 0, + text, + }; + }, + } as any as vscode.TextDocument; + + sandbox.stub(fs, "existsSync").returns(true); + sandbox.stub(fs, "readFileSync").returns(JSON.stringify(manifest)); + sandbox + .stub(globalVariables, "workspaceUri") + .value(vscode.Uri.parse(path.resolve(__dirname, "unknown"))); + const apiPluginCodelensProvider = new ApiPluginCodeLensProvider(); + const codelens: vscode.CodeLens[] = apiPluginCodelensProvider.provideCodeLenses( + document + ) as vscode.CodeLens[]; + + chai.assert.equal(codelens.length, 1); + chai.expect(codelens[0].command!.title).to.equal("➕Add another API"); + chai.expect(codelens[0].command!.command).to.equal("fx-extension.copilotPluginAddAPI"); + chai.expect(codelens[0].command!.arguments![0].fsPath).to.equal(document.fileName); + chai.expect(codelens[0].command!.arguments![0].isFromApiPlugin).to.be.true; + }); + + it("Do not show codelens for if not api spec file", async () => { + const openApiObject = { + unknown: "3.0", + }; + const text = JSON.stringify(openApiObject); + const document = { + fileName: "openapi.yaml", + getText: () => { + return text; + }, + positionAt: () => { + return new vscode.Position(0, 0); + }, + lineAt: () => { + return { + lineNumber: 0, + text, + }; + }, + } as any as vscode.TextDocument; + + sandbox.stub(fs, "existsSync").returns(false); + sandbox + .stub(globalVariables, "workspaceUri") + .value(vscode.Uri.parse(path.resolve(__dirname, "unknown"))); + const apiPluginCodelensProvider = new ApiPluginCodeLensProvider(); + const codelens: vscode.CodeLens[] = apiPluginCodelensProvider.provideCodeLenses( + document + ) as vscode.CodeLens[]; + + chai.assert.equal(codelens.length, 0); + }); + + it("Do not show codelens for if Teams manifest not exist", async () => { + const openApiObject = { + openapi: "3.0", + }; + const text = JSON.stringify(openApiObject); + const document = { + fileName: "openapi.yaml", + getText: () => { + return text; + }, + positionAt: () => { + return new vscode.Position(0, 0); + }, + lineAt: () => { + return { + lineNumber: 0, + text, + }; + }, + } as any as vscode.TextDocument; + + sandbox.stub(fs, "existsSync").returns(false); + sandbox + .stub(globalVariables, "workspaceUri") + .value(vscode.Uri.parse(path.resolve(__dirname, "unknown"))); + const apiPluginCodelensProvider = new ApiPluginCodeLensProvider(); + const codelens: vscode.CodeLens[] = apiPluginCodelensProvider.provideCodeLenses( + document + ) as vscode.CodeLens[]; + + chai.assert.equal(codelens.length, 0); + }); + + it("Do not show codelens for if not API plugin project", async () => { + const manifest = new TeamsAppManifest(); + manifest.copilotExtensions = {}; + const openApiObject = { + openapi: "3.0", + }; + const text = JSON.stringify(openApiObject); + const document = { + fileName: "openapi.yaml", + getText: () => { + return text; + }, + positionAt: () => { + return new vscode.Position(0, 0); + }, + lineAt: () => { + return { + lineNumber: 0, + text, + }; + }, + } as any as vscode.TextDocument; + + sandbox.stub(fs, "existsSync").returns(true); + sandbox.stub(fs, "readFileSync").returns(JSON.stringify(manifest)); + sandbox + .stub(globalVariables, "workspaceUri") + .value(vscode.Uri.parse(path.resolve(__dirname, "unknown"))); + const apiPluginCodelensProvider = new ApiPluginCodeLensProvider(); + const codelens: vscode.CodeLens[] = apiPluginCodelensProvider.provideCodeLenses( + document + ) as vscode.CodeLens[]; + + chai.assert.equal(codelens.length, 0); + }); }); - it("should work with correct teamsapp.yml", async () => { - const text = ` + describe("teamsapp.yml CodeLensProvider", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + it("should work with correct teamsapp.yml", async () => { + const text = ` version: 1.1.0 provision: @@ -482,42 +498,44 @@ deploy: publish: 2 // this line shouldn't have codelens publish: ccc: 3`; - const document = { - fileName: "teamsapp.yml", - getText: () => { - return text; - }, - positionAt: () => { - return new vscode.Position(0, 0); - }, - lineAt: () => { - return { - lineNumber: 0, - text: text, - }; - }, - } as any as vscode.TextDocument; - - const provider = new TeamsAppYamlCodeLensProvider(); - const codelens: vscode.CodeLens[] = provider.provideCodeLenses(document) as vscode.CodeLens[]; - - chai.assert.equal(codelens.length, 3); - chai.expect(codelens[0].command?.command).eq("fx-extension.provision"); - chai.expect(codelens[0].command?.arguments).deep.eq([TelemetryTriggerFrom.CodeLens]); - chai.expect(codelens[1].command?.command).eq("fx-extension.deploy"); - chai.expect(codelens[1].command?.arguments).deep.eq([TelemetryTriggerFrom.CodeLens]); - chai.expect(codelens[2].command?.command).eq("fx-extension.publish"); - chai.expect(codelens[2].command?.arguments).deep.eq([TelemetryTriggerFrom.CodeLens]); + const document = { + fileName: "teamsapp.yml", + getText: () => { + return text; + }, + positionAt: () => { + return new vscode.Position(0, 0); + }, + lineAt: () => { + return { + lineNumber: 0, + text: text, + }; + }, + } as any as vscode.TextDocument; + + const provider = new TeamsAppYamlCodeLensProvider(); + const codelens: vscode.CodeLens[] = provider.provideCodeLenses(document) as vscode.CodeLens[]; + + chai.assert.equal(codelens.length, 3); + chai.expect(codelens[0].command?.command).eq("fx-extension.provision"); + chai.expect(codelens[0].command?.arguments).deep.eq([TelemetryTriggerFrom.CodeLens]); + chai.expect(codelens[1].command?.command).eq("fx-extension.deploy"); + chai.expect(codelens[1].command?.arguments).deep.eq([TelemetryTriggerFrom.CodeLens]); + chai.expect(codelens[2].command?.command).eq("fx-extension.publish"); + chai.expect(codelens[2].command?.arguments).deep.eq([TelemetryTriggerFrom.CodeLens]); + }); }); -}); -describe("manifest*.xml CodeLensProvider", () => { - afterEach(() => { - sinon.restore(); - }); + describe("manifest*.xml CodeLensProvider", () => { + const sandbox = sinon.createSandbox(); - it("should work with correct manifest.xml", async () => { - const text = ` + afterEach(() => { + sandbox.restore(); + }); + + it("should work with correct manifest.xml", async () => { + const text = ` 518f978a-6cf4-46f8-8f1e-10881613fe54 1.0.0.0 @@ -526,28 +544,29 @@ describe("manifest*.xml CodeLensProvider", () => { `; - const document = { - fileName: "manifest-localhost.yml", - getText: () => { - return text; - }, - positionAt: () => { - return new vscode.Position(0, 0); - }, - lineAt: () => { - return { - lineNumber: 0, - text: text, - }; - }, - } as any as vscode.TextDocument; - - const provider = new OfficeDevManifestCodeLensProvider(); - const codelens: vscode.CodeLens[] = provider.provideCodeLenses(document) as vscode.CodeLens[]; - chai.assert.equal(codelens.length, 1); - chai.expect(codelens[0].command?.command).eq("fx-extension.generateManifestGUID"); - chai - .expect(codelens[0].command?.arguments?.[0]) - .deep.eq("518f978a-6cf4-46f8-8f1e-10881613fe54"); + const document = { + fileName: "manifest-localhost.yml", + getText: () => { + return text; + }, + positionAt: () => { + return new vscode.Position(0, 0); + }, + lineAt: () => { + return { + lineNumber: 0, + text: text, + }; + }, + } as any as vscode.TextDocument; + + const provider = new OfficeDevManifestCodeLensProvider(); + const codelens: vscode.CodeLens[] = provider.provideCodeLenses(document) as vscode.CodeLens[]; + chai.assert.equal(codelens.length, 1); + chai.expect(codelens[0].command?.command).eq("fx-extension.generateManifestGUID"); + chai + .expect(codelens[0].command?.arguments?.[0]) + .deep.eq("518f978a-6cf4-46f8-8f1e-10881613fe54"); + }); }); }); diff --git a/packages/vscode-extension/test/extension/copilotChatHandlers.test.ts b/packages/vscode-extension/test/extension/copilotChatHandlers.test.ts index f3645e4323..5571413540 100644 --- a/packages/vscode-extension/test/extension/copilotChatHandlers.test.ts +++ b/packages/vscode-extension/test/extension/copilotChatHandlers.test.ts @@ -7,9 +7,14 @@ import { ExtTelemetry } from "../../src/telemetry/extTelemetry"; import * as extTelemetryEvents from "../../src/telemetry/extTelemetryEvents"; import VsCodeLogInstance from "../../src/commonlib/log"; +after(() => { + sinon.restore(); +}); + describe("invokeTeamsAgent", async () => { const sandbox = sinon.createSandbox(); let clock: sinon.SinonFakeTimers; + afterEach(() => { sandbox.restore(); if (clock) { diff --git a/packages/vscode-extension/test/extension/extTelemetry.test.ts b/packages/vscode-extension/test/extension/extTelemetry.test.ts index 4fea5827ec..8b589a915b 100644 --- a/packages/vscode-extension/test/extension/extTelemetry.test.ts +++ b/packages/vscode-extension/test/extension/extTelemetry.test.ts @@ -5,7 +5,7 @@ import { ExtTelemetry } from "../../src/telemetry/extTelemetry"; import * as telemetryModule from "../../src/telemetry/extTelemetry"; import { TelemetryEvent } from "../../src/telemetry/extTelemetryEvents"; import sinon = require("sinon"); -import * as commonUtils from "../../src/utils/commonUtils"; +import * as vscTelemetryUtils from "../../src/utils/telemetryUtils"; import * as fs from "fs-extra"; import * as globalVariables from "../../src/globalVariables"; import { Uri } from "vscode"; @@ -35,6 +35,10 @@ const reporterSpy = spy.interface({ }); describe("ExtTelemetry", () => { + afterEach(() => { + // Restore the default sandbox here + sinon.restore(); + }); describe("setHasSentTelemetry", () => { it("query-expfeature", () => { const eventName = "query-expfeature"; @@ -93,7 +97,7 @@ describe("ExtTelemetry", () => { describe("Send Telemetry", () => { const sandbox = sinon.createSandbox(); - before(() => { + beforeEach(() => { chai.util.addProperty(ExtTelemetry, "reporter", () => reporterSpy); chai.util.addProperty(ExtTelemetry, "settingsVersion", () => "1.0.0"); sandbox.stub(fs, "pathExistsSync").returns(false); @@ -102,7 +106,7 @@ describe("ExtTelemetry", () => { sandbox.stub(globalVariables, "isExistingUser").value("no"); }); - after(() => { + afterEach(() => { sandbox.restore(); }); @@ -193,10 +197,10 @@ describe("ExtTelemetry", () => { sandbox.restore(); }); it("cacheTelemetryEventAsync", async () => { - const clock = sinon.useFakeTimers(); + const clock = sandbox.useFakeTimers(); let state = ""; sandbox.stub(telemetryModule, "lastCorrelationId").value("correlation-id"); - sandbox.stub(commonUtils, "getProjectId").resolves("project-id"); + sandbox.stub(vscTelemetryUtils, "getProjectId").resolves("project-id"); const globalStateUpdateStub = sandbox .stub(globalState, "globalStateUpdate") .callsFake(async (key, value) => (state = value)); diff --git a/packages/vscode-extension/test/extension/featureFlags.test.ts b/packages/vscode-extension/test/extension/featureFlags.test.ts new file mode 100644 index 0000000000..be2073b206 --- /dev/null +++ b/packages/vscode-extension/test/extension/featureFlags.test.ts @@ -0,0 +1,18 @@ +import * as chai from "chai"; +import * as sinon from "sinon"; +import * as featureFlags from "../../src/featureFlags"; + +describe("Feature Flags", () => { + const sandbox = sinon.createSandbox(); + describe("Get All Feature Flags", () => { + afterEach(async () => { + sandbox.restore(); + }); + it("Should get one feature flag", () => { + process.env["__TEAMSFX_INSIDER_PREVIEW"] = "1"; + const result = featureFlags.getAllFeatureFlags(); + chai.expect(result).to.have.lengthOf(1); + process.env["__TEAMSFX_INSIDER_PREVIEW"] = undefined; + }); + }); +}); diff --git a/packages/vscode-extension/test/extension/globalVariables.test.ts b/packages/vscode-extension/test/extension/globalVariables.test.ts index bf1a8e508b..e6a5255bce 100644 --- a/packages/vscode-extension/test/extension/globalVariables.test.ts +++ b/packages/vscode-extension/test/extension/globalVariables.test.ts @@ -8,14 +8,20 @@ import * as projectSettingHelper from "@microsoft/teamsfx-core/build/common/proj describe("Global Variables", () => { describe("isSPFxProject", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + it("return false for non-spfx project", async () => { - sinon.stub(fs, "existsSync").callsFake((path: fs.PathLike) => { + sandbox.stub(fs, "existsSync").callsFake((path: fs.PathLike) => { return false; }); - sinon.stub(fs, "pathExistsSync").returns(true); - sinon.stub(projectSettingHelper, "isValidProject").returns(true); - sinon.stub(globalVariables, "workspaceUri").returns({ fsPath: "/test" }); - sinon.stub(fs, "readdirSync").returns(["package.json"] as any); + sandbox.stub(fs, "pathExistsSync").returns(true); + sandbox.stub(projectSettingHelper, "isValidProject").returns(true); + sandbox.stub(globalVariables, "workspaceUri").returns({ fsPath: "/test" }); + sandbox.stub(fs, "readdirSync").returns(["package.json"] as any); globalVariables.initializeGlobalVariables({ globalState: { @@ -25,20 +31,18 @@ describe("Global Variables", () => { } as unknown as ExtensionContext); chai.expect(globalVariables.isSPFxProject).equals(false); - - sinon.restore(); }); it("return true for spfx project", () => { - sinon.stub(fs, "existsSync").callsFake((path: fs.PathLike) => { + sandbox.stub(fs, "existsSync").callsFake((path: fs.PathLike) => { return false; }); - sinon.stub(fs, "pathExistsSync").resolves(true); - sinon.stub(projectSettingHelper, "isValidProject").returns(true); - sinon.stub(projectSettingHelper, "isValidOfficeAddInProject").returns(false); - sinon.stub(globalVariables, "workspaceUri").value({ fsPath: "/test" }); - sinon.stub(fs, "readdirSync").returns([".yo-rc.json"] as any); - sinon + sandbox.stub(fs, "pathExistsSync").resolves(true); + sandbox.stub(projectSettingHelper, "isValidProject").returns(true); + sandbox.stub(projectSettingHelper, "isValidOfficeAddInProject").returns(false); + sandbox.stub(globalVariables, "workspaceUri").value({ fsPath: "/test" }); + sandbox.stub(fs, "readdirSync").returns([".yo-rc.json"] as any); + sandbox .stub(fs, "readJsonSync") .returns({ "@microsoft/generator-sharepoint": { version: " 1.16.0" } }); @@ -52,13 +56,11 @@ describe("Global Variables", () => { } as unknown as ExtensionContext); chai.expect(globalVariables.isSPFxProject).equals(true); - - sinon.restore(); }); it("set log folder", () => { - sinon.stub(fs, "pathExists").resolves(false); - sinon.stub(fs, "mkdirSync").callsFake(() => {}); + sandbox.stub(fs, "pathExists").resolves(false); + sandbox.stub(fs, "mkdirSync").callsFake(() => {}); globalVariables.initializeGlobalVariables({ globalState: { get: () => undefined, @@ -68,21 +70,18 @@ describe("Global Variables", () => { }, } as unknown as ExtensionContext); chai.expect(globalVariables.defaultExtensionLogPath).equals("fakePath"); - sinon.restore(); }); it("set commandIsRunning", async () => { globalVariables.setCommandIsRunning(true); chai.expect(globalVariables.commandIsRunning).equals(true); - sinon.restore(); }); it("unsetIsTeamsFxProject()", async () => { globalVariables.unsetIsTeamsFxProject(); chai.expect(globalVariables.isTeamsFxProject).equals(false); - sinon.restore(); }); }); }); diff --git a/packages/vscode-extension/test/extension/handlers.test.ts b/packages/vscode-extension/test/extension/handlers.test.ts index ed3dff5de8..0891284fd1 100644 --- a/packages/vscode-extension/test/extension/handlers.test.ts +++ b/packages/vscode-extension/test/extension/handlers.test.ts @@ -20,8 +20,6 @@ import { Stage, SystemError, UserError, - Void, - VsCodeEnv, err, ok, } from "@microsoft/teamsfx-api"; @@ -52,13 +50,14 @@ import { PanelType } from "../../src/controls/PanelType"; import { WebviewPanel } from "../../src/controls/webviewPanel"; import * as debugCommonUtils from "../../src/debug/commonUtils"; import * as debugConstants from "../../src/debug/constants"; +import * as migrationUtils from "../../src/utils/migrationUtils"; import * as launch from "../../src/debug/launch"; import { ExtensionErrors } from "../../src/error"; import { TreatmentVariableValue } from "../../src/exp/treatmentVariables"; -import * as extension from "../../src/extension"; import * as globalVariables from "../../src/globalVariables"; import * as handlers from "../../src/handlers"; import { ProgressHandler } from "../../src/progressHandler"; +import * as vsc_ui from "../../src/qm/vsc_ui"; import { VsCodeUI } from "../../src/qm/vsc_ui"; import { ExtTelemetry } from "../../src/telemetry/extTelemetry"; import * as extTelemetryEvents from "../../src/telemetry/extTelemetryEvents"; @@ -67,6 +66,7 @@ import envTreeProviderInstance from "../../src/treeview/environmentTreeViewProvi import TreeViewManagerInstance from "../../src/treeview/treeViewManager"; import * as commonUtils from "../../src/utils/commonUtils"; import * as localizeUtils from "../../src/utils/localizeUtils"; +import * as environmentUtils from "../../src/utils/environmentUtils"; import { ExtensionSurvey } from "../../src/utils/survey"; import { MockCore } from "../mocks/mockCore"; import VsCodeLogInstance from "../../src/commonlib/log"; @@ -173,7 +173,7 @@ describe("handlers", () => { sandbox.stub(projectSettingsHelper, "isValidProject").returns(false); sandbox.stub(M365TokenInstance, "setStatusChangeMap"); sandbox.stub(FxCore.prototype, "on").throws(new Error("test")); - const showErrorMessageStub = sinon.stub(vscode.window, "showErrorMessage"); + const showErrorMessageStub = sandbox.stub(vscode.window, "showErrorMessage"); const result = await handlers.activate(); @@ -181,20 +181,15 @@ describe("handlers", () => { chai.assert.isTrue(showErrorMessageStub.called); }); }); + const sandbox = sinon.createSandbox(); afterEach(() => { sandbox.restore(); }); - it("getSystemInputs()", () => { - const input: Inputs = handlers.getSystemInputs(); - - chai.expect(input.platform).equals(Platform.VSCode); - }); - it("getSettingsVersion", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); - sandbox.stub(handlers, "getSystemInputs").returns({} as Inputs); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(environmentUtils, "getSystemInputs").returns({} as Inputs); sandbox .stub(MockCore.prototype, "projectVersionCheck") .resolves(ok({ currentVersion: "3.0.0" })); @@ -275,7 +270,7 @@ describe("handlers", () => { sandbox.stub(commonUtils, "isTriggerFromWalkThrough").returns(true); sandbox.stub(globalVariables, "checkIsSPFx").returns(true); sandbox.stub(projectSettingsHelper, "isValidOfficeAddInProject").returns(false); - const globalStateUpdateStub = sinon.stub(globalState, "globalStateUpdate"); + const globalStateUpdateStub = sandbox.stub(globalState, "globalStateUpdate"); await handlers.updateAutoOpenGlobalKey(false, vscode.Uri.file("test"), [ { type: "type", content: "content" }, @@ -285,19 +280,21 @@ describe("handlers", () => { }); describe("command handlers", function () { - this.afterEach(() => { - sinon.restore(); + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); }); it("createNewProjectHandler()", async () => { - const clock = sinon.useFakeTimers(); + const clock = sandbox.useFakeTimers(); - sinon.stub(handlers, "core").value(new MockCore()); - const sendTelemetryEventFunc = sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(globalVariables, "checkIsSPFx").returns(false); - const createProject = sinon.spy(handlers.core, "createProject"); - const executeCommandFunc = sinon.stub(vscode.commands, "executeCommand"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const sendTelemetryEventFunc = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(globalVariables, "checkIsSPFx").returns(false); + const createProject = sandbox.spy(globalVariables.core, "createProject"); + const executeCommandFunc = sandbox.stub(vscode.commands, "executeCommand"); await handlers.createNewProjectHandler(); @@ -309,19 +306,18 @@ describe("handlers", () => { ); sinon.assert.calledOnce(createProject); chai.assert.isTrue(executeCommandFunc.calledOnceWith("vscode.openFolder")); - sinon.restore(); clock.restore(); }); it("createNewProjectHandler - invoke Copilot", async () => { const mockCore = new MockCore(); - sinon + sandbox .stub(mockCore, "createProject") .resolves(ok({ projectPath: "", shouldInvokeTeamsAgent: true })); - sinon.stub(handlers, "core").value(mockCore); - const sendTelemetryEventFunc = sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(globalVariables, "checkIsSPFx").returns(false); + sandbox.stub(globalVariables, "core").value(mockCore); + const sendTelemetryEventFunc = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(globalVariables, "checkIsSPFx").returns(false); sandbox.stub(vscode.extensions, "getExtension").returns({ name: "github.copilot" } as any); const executeCommandStub = sandbox.stub(vscode.commands, "executeCommand").resolves(); @@ -336,68 +332,63 @@ describe("handlers", () => { chai.assert.equal(executeCommandStub.callCount, 2); chai.assert.equal(executeCommandStub.args[0][0], "workbench.panel.chat.view.copilot.focus"); chai.assert.equal(executeCommandStub.args[1][0], "workbench.action.chat.open"); - sinon.restore(); }); it("provisionHandler()", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const provisionResources = sinon.spy(handlers.core, "provisionResources"); - sinon.stub(envTreeProviderInstance, "reloadEnvironments"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + const provisionResources = sandbox.spy(globalVariables.core, "provisionResources"); + sandbox.stub(envTreeProviderInstance, "reloadEnvironments"); await handlers.provisionHandler(); sinon.assert.calledOnce(provisionResources); - sinon.restore(); }); it("deployHandler()", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const deployArtifacts = sinon.spy(handlers.core, "deployArtifacts"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + const deployArtifacts = sandbox.spy(globalVariables.core, "deployArtifacts"); await handlers.deployHandler(); sinon.assert.calledOnce(deployArtifacts); - sinon.restore(); }); it("publishHandler()", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const publishApplication = sinon.spy(handlers.core, "publishApplication"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + const publishApplication = sandbox.spy(globalVariables.core, "publishApplication"); await handlers.publishHandler(); sinon.assert.calledOnce(publishApplication); - sinon.restore(); }); it("buildPackageHandler()", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - const sendTelemetryErrorEvent = sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + const sendTelemetryErrorEvent = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); await handlers.buildPackageHandler(); // should show error for invalid project sinon.assert.calledOnce(sendTelemetryErrorEvent); - sinon.restore(); }); it("validateManifestHandler() - app package", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(localizeUtils, "localize").returns(""); - sinon.stub(projectSettingsHelper, "isValidProject").returns(true); - sinon.stub(handlers, "getSystemInputs").returns({} as Inputs); - const validateApplication = sinon.spy(handlers.core, "validateApplication"); - - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(localizeUtils, "localize").returns(""); + sandbox.stub(projectSettingsHelper, "isValidProject").returns(true); + sandbox.stub(environmentUtils, "getSystemInputs").returns({} as Inputs); + const validateApplication = sandbox.spy(globalVariables.core, "validateApplication"); + + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => { return Promise.resolve(ok({ type: "success", result: "validateAgainstPackage" })); }, @@ -408,8 +399,8 @@ describe("handlers", () => { }); it("API ME: copilotPluginAddAPIHandler()", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - const addAPIHanlder = sinon.spy(handlers.core, "copilotPluginAddAPI"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const addAPIHanlder = sandbox.spy(globalVariables.core, "copilotPluginAddAPI"); const args = [ { fsPath: "manifest.json", @@ -422,8 +413,8 @@ describe("handlers", () => { }); it("API Plugin: copilotPluginAddAPIHandler()", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - const addAPIHanlder = sinon.spy(handlers.core, "copilotPluginAddAPI"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const addAPIHanlder = sandbox.spy(globalVariables.core, "copilotPluginAddAPI"); const args = [ { fsPath: "openapi.yaml", @@ -438,12 +429,14 @@ describe("handlers", () => { }); it("treeViewPreviewHandler() - previewWithManifest error", async () => { - sinon.stub(localizeUtils, "localize").returns(""); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sandbox.stub(handlers, "getSystemInputs").returns({} as Inputs); - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(handlers.core, "previewWithManifest").resolves(err({ foo: "bar" } as any)); + sandbox.stub(localizeUtils, "localize").returns(""); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(environmentUtils, "getSystemInputs").returns({} as Inputs); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox + .stub(globalVariables.core, "previewWithManifest") + .resolves(err({ foo: "bar" } as any)); const result = await handlers.treeViewPreviewHandler("dev"); @@ -451,12 +444,12 @@ describe("handlers", () => { }); it("treeViewPreviewHandler() - happy path", async () => { - sinon.stub(localizeUtils, "localize").returns(""); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sandbox.stub(handlers, "getSystemInputs").returns({} as Inputs); - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(handlers.core, "previewWithManifest").resolves(ok("test-url")); + sandbox.stub(localizeUtils, "localize").returns(""); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(environmentUtils, "getSystemInputs").returns({} as Inputs); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(globalVariables.core, "previewWithManifest").resolves(ok("test-url")); sandbox.stub(launch, "openHubWebClient").resolves(); const result = await handlers.treeViewPreviewHandler("dev"); @@ -465,13 +458,13 @@ describe("handlers", () => { }); it("selectTutorialsHandler()", async () => { - sinon.stub(localizeUtils, "localize").returns(""); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(TreatmentVariableValue, "inProductDoc").value(true); - sinon.stub(globalVariables, "isSPFxProject").value(false); + sandbox.stub(localizeUtils, "localize").returns(""); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(TreatmentVariableValue, "inProductDoc").value(true); + sandbox.stub(globalVariables, "isSPFxProject").value(false); let tutorialOptions: OptionItem[] = []; - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: (options: any) => { tutorialOptions = options.options; return Promise.resolve(ok({ type: "success", result: { id: "test", data: "data" } })); @@ -487,13 +480,13 @@ describe("handlers", () => { }); it("selectTutorialsHandler() for SPFx projects - v3", async () => { - sinon.stub(localizeUtils, "localize").returns(""); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(TreatmentVariableValue, "inProductDoc").value(true); - sinon.stub(globalVariables, "isSPFxProject").value(true); + sandbox.stub(localizeUtils, "localize").returns(""); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(TreatmentVariableValue, "inProductDoc").value(true); + sandbox.stub(globalVariables, "isSPFxProject").value(true); let tutorialOptions: OptionItem[] = []; - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: (options: any) => { tutorialOptions = options.options; return Promise.resolve(ok({ type: "success", result: { id: "test", data: "data" } })); @@ -524,17 +517,22 @@ describe("handlers", () => { }); describe("runCommand()", function () { - this.afterEach(() => { - sinon.restore(); + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); }); + + afterEach(() => { + sandbox.restore(); + }); + it("openConfigStateFile() - InvalidArgs", async () => { const env = "local"; const tmpDir = fs.mkdtempSync(path.resolve("./tmp")); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - - sinon.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); + sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); const projectSettings: any = { appName: "myapp", version: "1.0.0", @@ -545,8 +543,8 @@ describe("handlers", () => { const settingsFile = path.resolve(configFolder, "projectSettings.json"); await fs.writeJSON(settingsFile, JSON.stringify(projectSettings, null, 4)); - sinon.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: env })), }); @@ -562,13 +560,10 @@ describe("handlers", () => { it("openConfigStateFile() - noOpenWorkspace", async () => { const env = "local"; - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(globalVariables, "workspaceUri").value({ fsPath: undefined }); - sinon.stub(globalVariables, "workspaceUri").value({ fsPath: undefined }); - - sinon.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: env })), }); @@ -584,13 +579,11 @@ describe("handlers", () => { const env = "local"; const tmpDir = fs.mkdtempSync(path.resolve("./tmp")); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(projectSettingsHelper, "isValidProject").returns(false); + sandbox.stub(projectSettingsHelper, "isValidProject").returns(false); - sinon.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); - sinon.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); + sandbox.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: env })), }); @@ -607,10 +600,7 @@ describe("handlers", () => { const env = "local"; const tmpDir = fs.mkdtempSync(path.resolve("./tmp")); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - - sinon.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); + sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); const projectSettings: any = { appName: "myapp", version: "1.0.0", @@ -621,13 +611,13 @@ describe("handlers", () => { const settingsFile = path.resolve(configFolder, "projectSettings.json"); await fs.writeJSON(settingsFile, JSON.stringify(projectSettings, null, 4)); - sinon.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(err({ error: "invalid target env" })), }); - sinon.stub(environmentManager, "listAllEnvConfigs").resolves(ok([])); - sinon.stub(fs, "pathExists").resolves(false); - sinon.stub(pathUtils, "getEnvFolderPath").resolves(ok(env)); + sandbox.stub(environmentManager, "listAllEnvConfigs").resolves(ok([])); + sandbox.stub(fs, "pathExists").resolves(false); + sandbox.stub(pathUtils, "getEnvFolderPath").resolves(ok(env)); const res = await handlers.openConfigStateFile([{ env: undefined, type: "env" }]); await fs.remove(tmpDir); @@ -641,10 +631,7 @@ describe("handlers", () => { const env = "local"; const tmpDir = fs.mkdtempSync(path.resolve("./tmp")); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - - sinon.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); + sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); const projectSettings: any = { appName: "myapp", version: "1.0.0", @@ -655,13 +642,13 @@ describe("handlers", () => { const settingsFile = path.resolve(configFolder, "projectSettings.json"); await fs.writeJSON(settingsFile, JSON.stringify(projectSettings, null, 4)); - sinon.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: env })), }); - sinon.stub(pathUtils, "getEnvFolderPath").resolves(ok(env)); - sinon.stub(fs, "pathExists").resolves(false); - sinon.stub(environmentManager, "listAllEnvConfigs").resolves(ok([])); + sandbox.stub(pathUtils, "getEnvFolderPath").resolves(ok(env)); + sandbox.stub(fs, "pathExists").resolves(false); + sandbox.stub(environmentManager, "listAllEnvConfigs").resolves(ok([])); const res = await handlers.openConfigStateFile([{ env: undefined, type: "env" }]); await fs.remove(tmpDir); @@ -676,10 +663,7 @@ describe("handlers", () => { const env = "local"; const tmpDir = fs.mkdtempSync(path.resolve("./tmp")); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - - sinon.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); + sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); const projectSettings: any = { appName: "myapp", version: "1.0.0", @@ -690,13 +674,13 @@ describe("handlers", () => { const settingsFile = path.resolve(configFolder, "projectSettings.json"); await fs.writeJSON(settingsFile, JSON.stringify(projectSettings, null, 4)); - sinon.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: env })), }); - sinon.stub(pathUtils, "getEnvFolderPath").resolves(err({ error: "unknown" } as any)); - sinon.stub(fs, "pathExists").resolves(true); - sinon.stub(vscode.workspace, "openTextDocument").resolves("" as any); + sandbox.stub(pathUtils, "getEnvFolderPath").resolves(err({ error: "unknown" } as any)); + sandbox.stub(fs, "pathExists").resolves(true); + sandbox.stub(vscode.workspace, "openTextDocument").resolves("" as any); const res = await handlers.openConfigStateFile([{ env: env, type: "env" }]); await fs.remove(tmpDir); @@ -710,10 +694,7 @@ describe("handlers", () => { const env = "local"; const tmpDir = fs.mkdtempSync(path.resolve("./tmp")); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - - sinon.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); + sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.file(tmpDir)); const projectSettings: any = { appName: "myapp", version: "1.0.0", @@ -724,13 +705,13 @@ describe("handlers", () => { const settingsFile = path.resolve(configFolder, "projectSettings.json"); await fs.writeJSON(settingsFile, JSON.stringify(projectSettings, null, 4)); - sinon.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "context").value({ extensionPath: path.resolve("../../") }); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: env })), }); - sinon.stub(pathUtils, "getEnvFolderPath").resolves(ok(env)); - sinon.stub(fs, "pathExists").resolves(true); - sinon.stub(vscode.workspace, "openTextDocument").returns(Promise.resolve("" as any)); + sandbox.stub(pathUtils, "getEnvFolderPath").resolves(ok(env)); + sandbox.stub(fs, "pathExists").resolves(true); + sandbox.stub(vscode.workspace, "openTextDocument").returns(Promise.resolve("" as any)); const res = await handlers.openConfigStateFile([{ env: env, type: "env" }]); await fs.remove(tmpDir); @@ -741,11 +722,12 @@ describe("handlers", () => { }); it("create sample with projectid", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - const sendTelemetryEvent = sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const createProject = sinon.spy(handlers.core, "createProject"); - sinon.stub(vscode.commands, "executeCommand"); + sandbox.restore(); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const sendTelemetryEvent = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + const createProject = sandbox.spy(globalVariables.core, "createProject"); + sandbox.stub(vscode.commands, "executeCommand"); const inputs = { projectId: uuid.v4(), platform: Platform.VSCode }; await handlers.runCommand(Stage.create, inputs); @@ -756,40 +738,33 @@ describe("handlers", () => { }); it("create from scratch without projectid", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - const sendTelemetryEvent = sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const createProject = sinon.spy(handlers.core, "createProject"); - sinon.stub(vscode.commands, "executeCommand"); + sandbox.restore(); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const sendTelemetryEvent = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + const createProject = sandbox.spy(globalVariables.core, "createProject"); + sandbox.stub(vscode.commands, "executeCommand"); await handlers.runCommand(Stage.create); - - sinon.restore(); sinon.assert.calledOnce(createProject); chai.assert.isTrue(createProject.args[0][0].projectId != undefined); chai.assert.isTrue(sendTelemetryEvent.args[0][1]!["new-project-id"] != undefined); }); it("provisionResources", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const provisionResources = sinon.spy(handlers.core, "provisionResources"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const provisionResources = sandbox.spy(globalVariables.core, "provisionResources"); await handlers.runCommand(Stage.provision); - - sinon.restore(); sinon.assert.calledOnce(provisionResources); }); it("provisionResources - local", async () => { const mockCore = new MockCore(); - const mockCoreStub = sinon + const mockCoreStub = sandbox .stub(mockCore, "provisionResources") .resolves(err(new UserError("test", "test", "test"))); - sinon.stub(handlers, "core").value(mockCore); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(globalVariables, "core").value(mockCore); const res = await handlers.runCommand(Stage.provision, { platform: Platform.VSCode, @@ -802,75 +777,57 @@ describe("handlers", () => { debugConstants.RecommendedOperations.DebugInTestTool ); } - sinon.restore(); sinon.assert.calledOnce(mockCoreStub); }); it("deployArtifacts", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const deployArtifacts = sinon.spy(handlers.core, "deployArtifacts"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const deployArtifacts = sandbox.spy(globalVariables.core, "deployArtifacts"); await handlers.runCommand(Stage.deploy); - - sinon.restore(); sinon.assert.calledOnce(deployArtifacts); }); it("deployArtifacts - local", async () => { const mockCore = new MockCore(); - const mockCoreStub = sinon + const mockCoreStub = sandbox .stub(mockCore, "deployArtifacts") .resolves(err(new UserError("test", "test", "test"))); - sinon.stub(handlers, "core").value(mockCore); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(globalVariables, "core").value(mockCore); await handlers.runCommand(Stage.deploy, { platform: Platform.VSCode, env: "local", } as Inputs); - - sinon.restore(); sinon.assert.calledOnce(mockCoreStub); }); it("deployAadManifest", async () => { - const sandbox = sinon.createSandbox(); - sandbox.stub(handlers, "core").value(new MockCore()); - sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); - sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const deployAadManifest = sandbox.spy(handlers.core, "deployAadManifest"); - const input: Inputs = handlers.getSystemInputs(); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const deployAadManifest = sandbox.spy(globalVariables.core, "deployAadManifest"); + const input: Inputs = environmentUtils.getSystemInputs(); await handlers.runCommand(Stage.deployAad, input); sandbox.assert.calledOnce(deployAadManifest); - sandbox.restore(); }); it("deployAadManifest happy path", async () => { - const sandbox = sinon.createSandbox(); - sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); - sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sandbox.stub(handlers.core, "deployAadManifest").resolves(ok(undefined)); - const input: Inputs = handlers.getSystemInputs(); + sandbox.stub(globalVariables.core, "deployAadManifest").resolves(ok(undefined)); + const input: Inputs = environmentUtils.getSystemInputs(); const res = await handlers.runCommand(Stage.deployAad, input); chai.assert.isTrue(res.isOk()); if (res.isOk()) { chai.assert.strictEqual(res.value, undefined); } - sandbox.restore(); }); it("localDebug", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(globalVariables, "core").value(new MockCore()); let ignoreEnvInfo: boolean | undefined = undefined; let localDebugCalled = 0; - sinon - .stub(handlers.core, "localDebug") + sandbox + .stub(globalVariables.core, "localDebug") .callsFake(async (inputs: Inputs): Promise> => { ignoreEnvInfo = inputs.ignoreEnvInfo; localDebugCalled += 1; @@ -878,88 +835,28 @@ describe("handlers", () => { }); await handlers.runCommand(Stage.debug); - - sinon.restore(); chai.expect(ignoreEnvInfo).to.equal(false); chai.expect(localDebugCalled).equals(1); }); it("publishApplication", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const publishApplication = sinon.spy(handlers.core, "publishApplication"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const publishApplication = sandbox.spy(globalVariables.core, "publishApplication"); await handlers.runCommand(Stage.publish); - - sinon.restore(); sinon.assert.calledOnce(publishApplication); }); it("createEnv", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const createEnv = sinon.spy(handlers.core, "createEnv"); - sinon.stub(vscode.commands, "executeCommand"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const createEnv = sandbox.spy(globalVariables.core, "createEnv"); + sandbox.stub(vscode.commands, "executeCommand"); await handlers.runCommand(Stage.createEnv); - - sinon.restore(); sinon.assert.calledOnce(createEnv); }); }); - describe("detectVsCodeEnv()", function () { - this.afterEach(() => { - sinon.restore(); - }); - - it("locally run", () => { - const expectedResult = { - extensionKind: vscode.ExtensionKind.UI, - id: "", - extensionUri: vscode.Uri.file(""), - extensionPath: "", - isActive: true, - packageJSON: {}, - exports: undefined, - activate: sinon.spy(), - }; - const getExtension = sinon - .stub(vscode.extensions, "getExtension") - .callsFake((name: string) => { - return expectedResult; - }); - - chai.expect(handlers.detectVsCodeEnv()).equals(VsCodeEnv.local); - getExtension.restore(); - }); - - it("Remotely run", () => { - const expectedResult = { - extensionKind: vscode.ExtensionKind.Workspace, - id: "", - extensionUri: vscode.Uri.file(""), - extensionPath: "", - isActive: true, - packageJSON: {}, - exports: undefined, - activate: sinon.spy(), - }; - const getExtension = sinon - .stub(vscode.extensions, "getExtension") - .callsFake((name: string) => { - return expectedResult; - }); - - chai - .expect(handlers.detectVsCodeEnv()) - .oneOf([VsCodeEnv.remote, VsCodeEnv.codespaceVsCode, VsCodeEnv.codespaceBrowser]); - getExtension.restore(); - }); - }); - it("openWelcomeHandler", async () => { sandbox.stub(featureFlags, "isChatParticipantEnabled").returns(false); const executeCommands = sandbox.stub(vscode.commands, "executeCommand"); @@ -1040,7 +937,7 @@ describe("handlers", () => { it("openReadMeHandler - create project", async () => { sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); sandbox.stub(globalVariables, "isTeamsFxProject").value(false); - sandbox.stub(handlers, "core").value(undefined); + sandbox.stub(globalVariables, "core").value(undefined); const showMessageStub = sandbox .stub(vscode.window, "showInformationMessage") .callsFake( @@ -1059,7 +956,7 @@ describe("handlers", () => { it("openReadMeHandler - open folder", async () => { sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); sandbox.stub(globalVariables, "isTeamsFxProject").value(false); - sandbox.stub(handlers, "core").value(undefined); + sandbox.stub(globalVariables, "core").value(undefined); const executeCommandStub = sandbox.stub(vscode.commands, "executeCommand"); const showMessageStub = sandbox .stub(vscode.window, "showInformationMessage") @@ -1139,26 +1036,29 @@ describe("handlers", () => { }); describe("decryptSecret", function () { - this.afterEach(() => { - sinon.restore(); + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); }); + it("successfully update secret", async () => { - sinon.stub(globalVariables, "context").value({ extensionPath: "" }); - sinon.stub(handlers, "core").value(new MockCore()); - const sendTelemetryEvent = sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - const sendTelemetryErrorEvent = sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const decrypt = sinon.spy(handlers.core, "decrypt"); - const encrypt = sinon.spy(handlers.core, "encrypt"); - sinon.stub(vscode.commands, "executeCommand"); - const editBuilder = sinon.spy(); - sinon.stub(vscode.window, "activeTextEditor").value({ + sandbox.stub(globalVariables, "context").value({ extensionPath: "" }); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const sendTelemetryEvent = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + const sendTelemetryErrorEvent = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + const decrypt = sandbox.spy(globalVariables.core, "decrypt"); + const encrypt = sandbox.spy(globalVariables.core, "encrypt"); + sandbox.stub(vscode.commands, "executeCommand"); + const editBuilder = sandbox.spy(); + sandbox.stub(vscode.window, "activeTextEditor").value({ edit: function (callback: (eb: any) => void) { callback({ replace: editBuilder, }); }, }); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ inputText: () => Promise.resolve(ok({ type: "success", result: "inputValue" })), }); const range = new vscode.Range(new vscode.Position(0, 10), new vscode.Position(0, 15)); @@ -1170,27 +1070,26 @@ describe("handlers", () => { sinon.assert.calledOnce(editBuilder); sinon.assert.calledTwice(sendTelemetryEvent); sinon.assert.notCalled(sendTelemetryErrorEvent); - sinon.restore(); }); it("failed to update due to corrupted secret", async () => { - sinon.stub(globalVariables, "context").value({ extensionPath: "" }); - sinon.stub(handlers, "core").value(new MockCore()); - const sendTelemetryEvent = sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - const sendTelemetryErrorEvent = sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const decrypt = sinon.stub(handlers.core, "decrypt"); + sandbox.stub(globalVariables, "context").value({ extensionPath: "" }); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const sendTelemetryEvent = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + const sendTelemetryErrorEvent = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + const decrypt = sandbox.stub(globalVariables.core, "decrypt"); decrypt.returns(Promise.resolve(err(new UserError("", "fake error", "")))); - const encrypt = sinon.spy(handlers.core, "encrypt"); - sinon.stub(vscode.commands, "executeCommand"); - const editBuilder = sinon.spy(); - sinon.stub(vscode.window, "activeTextEditor").value({ + const encrypt = sandbox.spy(globalVariables.core, "encrypt"); + sandbox.stub(vscode.commands, "executeCommand"); + const editBuilder = sandbox.spy(); + sandbox.stub(vscode.window, "activeTextEditor").value({ edit: function (callback: (eb: any) => void) { callback({ replace: editBuilder, }); }, }); - const showMessage = sinon.stub(vscode.window, "showErrorMessage"); + const showMessage = sandbox.stub(vscode.window, "showErrorMessage"); const range = new vscode.Range(new vscode.Position(0, 10), new vscode.Position(0, 15)); await handlers.decryptSecret("test", range); @@ -1201,7 +1100,6 @@ describe("handlers", () => { sinon.assert.calledOnce(showMessage); sinon.assert.calledOnce(sendTelemetryEvent); sinon.assert.calledOnce(sendTelemetryErrorEvent); - sinon.restore(); }); }); @@ -1213,8 +1111,8 @@ describe("handlers", () => { }); it("happy path: grant permission", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); - sandbox.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: "grantPermission" })), }); sandbox.stub(MockCore.prototype, "grantPermission").returns( @@ -1242,8 +1140,8 @@ describe("handlers", () => { }); it("happy path: list collaborator", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); - sandbox.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: "listCollaborator" })), }); sandbox.stub(MockCore.prototype, "listCollaborator").returns( @@ -1278,8 +1176,8 @@ describe("handlers", () => { }); it("happy path: list collaborator throws error", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); - sandbox.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: "listCollaborator" })), }); sandbox.stub(MockCore.prototype, "listCollaborator").throws(new Error("Error")); @@ -1300,8 +1198,8 @@ describe("handlers", () => { }); it("happy path: list collaborator throws login error", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); - sandbox.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(ok({ type: "success", result: "listCollaborator" })), }); const showErrorMessageStub = sandbox.stub(vscode.window, "showErrorMessage"); @@ -1326,8 +1224,8 @@ describe("handlers", () => { }); it("User Cancel", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); - sandbox.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ selectOption: () => Promise.resolve(err(new UserError("source", "errorName", "errorMessage"))), }); @@ -1339,11 +1237,15 @@ describe("handlers", () => { describe("checkUpgrade", function () { const sandbox = sinon.createSandbox(); - const mockCore = new MockCore(); beforeEach(() => { - sandbox.stub(handlers, "getSystemInputs").returns({} as Inputs); - sandbox.stub(handlers, "core").value(mockCore); + sandbox.stub(environmentUtils, "getSystemInputs").returns({ + locale: "en-us", + platform: "vsc", + projectPath: undefined, + vscodeEnv: "local", + } as Inputs); + sandbox.stub(globalVariables, "core").value(new MockCore()); }); afterEach(() => { @@ -1352,7 +1254,7 @@ describe("handlers", () => { it("calls phantomMigrationV3 with isNonmodalMessage when auto triggered", async () => { const phantomMigrationV3Stub = sandbox - .stub(mockCore, "phantomMigrationV3") + .stub(globalVariables.core, "phantomMigrationV3") .resolves(ok(undefined)); await handlers.checkUpgrade([extTelemetryEvents.TelemetryTriggerFrom.Auto]); chai.assert.isTrue( @@ -1368,7 +1270,7 @@ describe("handlers", () => { it("calls phantomMigrationV3 with skipUserConfirm trigger from sideBar and command palette", async () => { const phantomMigrationV3Stub = sandbox - .stub(mockCore, "phantomMigrationV3") + .stub(globalVariables.core, "phantomMigrationV3") .resolves(ok(undefined)); await handlers.checkUpgrade([extTelemetryEvents.TelemetryTriggerFrom.SideBar]); chai.assert.isTrue( @@ -1401,7 +1303,7 @@ describe("handlers", () => { ); error.helpLink = "test helpLink"; const phantomMigrationV3Stub = sandbox - .stub(mockCore, "phantomMigrationV3") + .stub(globalVariables.core, "phantomMigrationV3") .resolves(err(error)); sandbox.stub(localizeUtils, "localize").returns(""); const showErrorMessageStub = sandbox.stub(vscode.window, "showErrorMessage"); @@ -1422,6 +1324,8 @@ describe("handlers", () => { }); describe("downloadSampleApp", function () { + const sandbox = sinon.createSandbox(); + this.beforeEach(() => { sandbox.stub(globalVariables, "checkIsSPFx").returns(false); sandbox.stub(vscode.commands, "executeCommand"); @@ -1432,10 +1336,10 @@ describe("handlers", () => { }); it("happy path", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); + sandbox.stub(globalVariables, "core").value(new MockCore()); sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const errorEventStub = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const createProject = sandbox.spy(handlers.core, "createSampleProject"); + const createProject = sandbox.spy(globalVariables.core, "createSampleProject"); await handlers.downloadSampleApp(extTelemetryEvents.TelemetryTriggerFrom.CopilotChat, "test"); @@ -1444,12 +1348,12 @@ describe("handlers", () => { }); it("has error", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); + sandbox.stub(globalVariables, "core").value(new MockCore()); sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const errorEventStub = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); sandbox.stub(projectSettingsHelper, "isValidOfficeAddInProject").returns(false); sandbox - .stub(handlers.core, "createSampleProject") + .stub(globalVariables.core, "createSampleProject") .rejects(err(new Error("Cannot get user login information"))); await handlers.downloadSampleApp(extTelemetryEvents.TelemetryTriggerFrom.CopilotChat, "test"); @@ -1463,8 +1367,8 @@ describe("handlers", () => { scratch: "no", platform: Platform.VSCode, }; - sandbox.stub(handlers, "core").value(new MockCore()); - const createProject = sandbox.spy(handlers.core, "createSampleProject"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const createProject = sandbox.spy(globalVariables.core, "createSampleProject"); await handlers.downloadSample(inputs); @@ -1477,10 +1381,10 @@ describe("handlers", () => { scratch: "no", platform: Platform.VSCode, }; - sandbox.stub(handlers, "core").value(new MockCore()); + sandbox.stub(globalVariables, "core").value(new MockCore()); const showErrorMessageStub = sandbox.stub(vscode.window, "showErrorMessage"); const createProject = sandbox - .stub(handlers.core, "createSampleProject") + .stub(globalVariables.core, "createSampleProject") .rejects(err(new Error("Cannot get user login information"))); await handlers.downloadSample(inputs); @@ -1495,20 +1399,20 @@ describe("handlers", () => { scratch: "no", platform: Platform.VSCode, }; - sandbox.stub(handlers, "core").value(new MockCore()); + sandbox.stub(globalVariables, "core").value(new MockCore()); const showErrorMessageStub = sandbox.stub(vscode.window, "showErrorMessage"); const createProject = sandbox - .stub(handlers.core, "createProject") + .stub(globalVariables.core, "createProject") .resolves(err(new SystemError("test", "test", "Cannot get user login information"))); await handlers.downloadSample(inputs); }); it("deployAadAppmanifest", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); + sandbox.stub(globalVariables, "core").value(new MockCore()); sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - const deployAadManifest = sandbox.spy(handlers.core, "deployAadManifest"); + const deployAadManifest = sandbox.spy(globalVariables.core, "deployAadManifest"); await handlers.updateAadAppManifest([{ fsPath: "path/aad.dev.template" }]); sandbox.assert.calledOnce(deployAadManifest); deployAadManifest.restore(); @@ -1549,7 +1453,7 @@ describe("handlers", () => { const error = new UserError("test source", "test name", "test message", "test displayMessage"); error.recommendedOperation = "debug-in-test-tool"; sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.file("path")); - sinon.stub(fs, "pathExistsSync").returns(true); + sandbox.stub(fs, "pathExistsSync").returns(true); await handlers.showError(error); @@ -1610,6 +1514,7 @@ describe("handlers", () => { buttonNum: 3, }, ].forEach(({ type, buildError, buttonNum }) => { + const sandbox = sinon.createSandbox(); it(`showError - ${type} - recommend test tool`, async () => { sandbox.stub(localizeUtils, "localize").returns(""); const showErrorMessageStub = sandbox.stub(vscode.window, "showErrorMessage"); @@ -1618,17 +1523,20 @@ describe("handlers", () => { sandbox.stub(vscode.commands, "executeCommand"); const error = buildError(); await handlers.showError(error); - chai.assert.equal(showErrorMessageStub.firstCall.args.length, buttonNum + 1); + sandbox.restore(); }); }); describe("getDotnetPathHandler", async () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); + it("dotnet is installed", async () => { - sinon.stub(DepsManager.prototype, "getStatus").resolves([ + sandbox.stub(DepsManager.prototype, "getStatus").resolves([ { name: ".NET Core SDK", type: DepsType.Dotnet, @@ -1648,7 +1556,7 @@ describe("handlers", () => { }); it("dotnet is not installed", async () => { - sinon.stub(DepsManager.prototype, "getStatus").resolves([ + sandbox.stub(DepsManager.prototype, "getStatus").resolves([ { name: ".NET Core SDK", type: DepsType.Dotnet, @@ -1668,26 +1576,30 @@ describe("handlers", () => { }); it("failed to get dotnet path", async () => { - sinon.stub(DepsManager.prototype, "getStatus").rejects(new Error("failed to get status")); + sandbox.stub(DepsManager.prototype, "getStatus").rejects(new Error("failed to get status")); const dotnetPath = await handlers.getDotnetPathHandler(); chai.assert.equal(dotnetPath, `${path.delimiter}`); }); }); describe("scaffoldFromDeveloperPortalHandler", async () => { + const sandbox = sinon.createSandbox(); + beforeEach(() => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").resolves(); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent").resolves(); - sinon.stub(globalVariables, "checkIsSPFx").returns(false); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").resolves(); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent").resolves(); + sandbox.stub(globalVariables, "checkIsSPFx").returns(false); }); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); + it("missing args", async () => { const progressHandler = new ProgressHandler("title", 1); - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); - const createProgressBar = sinon - .stub(extension.VS_CODE_UI, "createProgressBar") + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + const createProgressBar = sandbox + .stub(vsc_ui.VS_CODE_UI, "createProgressBar") .returns(progressHandler); const res = await handlers.scaffoldFromDeveloperPortalHandler(); @@ -1698,9 +1610,9 @@ describe("handlers", () => { it("incorrect number of args", async () => { const progressHandler = new ProgressHandler("title", 1); - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); - const createProgressBar = sinon - .stub(extension.VS_CODE_UI, "createProgressBar") + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + const createProgressBar = sandbox + .stub(vsc_ui.VS_CODE_UI, "createProgressBar") .returns(progressHandler); const res = await handlers.scaffoldFromDeveloperPortalHandler(); @@ -1710,15 +1622,15 @@ describe("handlers", () => { }); it("general error when signing in M365", async () => { - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); const progressHandler = new ProgressHandler("title", 1); - const startProgress = sinon.stub(progressHandler, "start").resolves(); - const endProgress = sinon.stub(progressHandler, "end").resolves(); - sinon.stub(M365TokenInstance, "signInWhenInitiatedFromTdp").throws("error1"); - const createProgressBar = sinon - .stub(extension.VS_CODE_UI, "createProgressBar") + const startProgress = sandbox.stub(progressHandler, "start").resolves(); + const endProgress = sandbox.stub(progressHandler, "end").resolves(); + sandbox.stub(M365TokenInstance, "signInWhenInitiatedFromTdp").throws("error1"); + const createProgressBar = sandbox + .stub(vsc_ui.VS_CODE_UI, "createProgressBar") .returns(progressHandler); - const showErrorMessage = sinon.stub(vscode.window, "showErrorMessage"); + const showErrorMessage = sandbox.stub(vscode.window, "showErrorMessage"); const res = await handlers.scaffoldFromDeveloperPortalHandler(["appId"]); chai.assert.isTrue(res.isErr()); @@ -1732,17 +1644,17 @@ describe("handlers", () => { }); it("error when signing M365", async () => { - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); const progressHandler = new ProgressHandler("title", 1); - const startProgress = sinon.stub(progressHandler, "start").resolves(); - const endProgress = sinon.stub(progressHandler, "end").resolves(); - sinon + const startProgress = sandbox.stub(progressHandler, "start").resolves(); + const endProgress = sandbox.stub(progressHandler, "end").resolves(); + sandbox .stub(M365TokenInstance, "signInWhenInitiatedFromTdp") .resolves(err(new UserError("source", "name", "message", "displayMessage"))); - const createProgressBar = sinon - .stub(extension.VS_CODE_UI, "createProgressBar") + const createProgressBar = sandbox + .stub(vsc_ui.VS_CODE_UI, "createProgressBar") .returns(progressHandler); - const showErrorMessage = sinon.stub(vscode.window, "showErrorMessage"); + const showErrorMessage = sandbox.stub(vscode.window, "showErrorMessage"); const res = await handlers.scaffoldFromDeveloperPortalHandler(["appId"]); @@ -1754,17 +1666,17 @@ describe("handlers", () => { }); it("error when signing in M365 but missing display message", async () => { - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); const progressHandler = new ProgressHandler("title", 1); - const startProgress = sinon.stub(progressHandler, "start").resolves(); - const endProgress = sinon.stub(progressHandler, "end").resolves(); - sinon + const startProgress = sandbox.stub(progressHandler, "start").resolves(); + const endProgress = sandbox.stub(progressHandler, "end").resolves(); + sandbox .stub(M365TokenInstance, "signInWhenInitiatedFromTdp") .resolves(err(new UserError("source", "name", "", ""))); - const createProgressBar = sinon - .stub(extension.VS_CODE_UI, "createProgressBar") + const createProgressBar = sandbox + .stub(vsc_ui.VS_CODE_UI, "createProgressBar") .returns(progressHandler); - const showErrorMessage = sinon.stub(vscode.window, "showErrorMessage"); + const showErrorMessage = sandbox.stub(vscode.window, "showErrorMessage"); const res = await handlers.scaffoldFromDeveloperPortalHandler(["appId"]); @@ -1776,21 +1688,21 @@ describe("handlers", () => { }); it("failed to get teams app", async () => { - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); const progressHandler = new ProgressHandler("title", 1); - const startProgress = sinon.stub(progressHandler, "start").resolves(); - const endProgress = sinon.stub(progressHandler, "end").resolves(); - sinon.stub(M365TokenInstance, "signInWhenInitiatedFromTdp").resolves(ok("token")); - sinon + const startProgress = sandbox.stub(progressHandler, "start").resolves(); + const endProgress = sandbox.stub(progressHandler, "end").resolves(); + sandbox.stub(M365TokenInstance, "signInWhenInitiatedFromTdp").resolves(ok("token")); + sandbox .stub(M365TokenInstance, "getAccessToken") .resolves(err(new SystemError("source", "name", "", ""))); - const createProgressBar = sinon - .stub(extension.VS_CODE_UI, "createProgressBar") + const createProgressBar = sandbox + .stub(vsc_ui.VS_CODE_UI, "createProgressBar") .returns(progressHandler); - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(vscode.commands, "executeCommand"); - sinon.stub(globalState, "globalStateUpdate"); - const getApp = sinon.stub(AppStudioClient, "getApp").throws("error"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(vscode.commands, "executeCommand"); + sandbox.stub(globalState, "globalStateUpdate"); + const getApp = sandbox.stub(AppStudioClient, "getApp").throws("error"); const res = await handlers.scaffoldFromDeveloperPortalHandler(["appId"]); @@ -1802,24 +1714,24 @@ describe("handlers", () => { }); it("happy path", async () => { - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); const progressHandler = new ProgressHandler("title", 1); - const startProgress = sinon.stub(progressHandler, "start").resolves(); - const endProgress = sinon.stub(progressHandler, "end").resolves(); - sinon.stub(M365TokenInstance, "signInWhenInitiatedFromTdp").resolves(ok("token")); - sinon.stub(M365TokenInstance, "getAccessToken").resolves(ok("authSvcToken")); - sinon.stub(commonTools, "setRegion").resolves(); - const createProgressBar = sinon - .stub(extension.VS_CODE_UI, "createProgressBar") + const startProgress = sandbox.stub(progressHandler, "start").resolves(); + const endProgress = sandbox.stub(progressHandler, "end").resolves(); + sandbox.stub(M365TokenInstance, "signInWhenInitiatedFromTdp").resolves(ok("token")); + sandbox.stub(M365TokenInstance, "getAccessToken").resolves(ok("authSvcToken")); + sandbox.stub(commonTools, "setRegion").resolves(); + const createProgressBar = sandbox + .stub(vsc_ui.VS_CODE_UI, "createProgressBar") .returns(progressHandler); - sinon.stub(handlers, "core").value(new MockCore()); - const createProject = sinon.spy(handlers.core, "createProject"); - sinon.stub(vscode.commands, "executeCommand"); - sinon.stub(globalState, "globalStateUpdate"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + const createProject = sandbox.spy(globalVariables.core, "createProject"); + sandbox.stub(vscode.commands, "executeCommand"); + sandbox.stub(globalState, "globalStateUpdate"); const appDefinition: AppDefinition = { teamsAppId: "mock-id", }; - sinon.stub(AppStudioClient, "getApp").resolves(appDefinition); + sandbox.stub(AppStudioClient, "getApp").resolves(appDefinition); const res = await handlers.scaffoldFromDeveloperPortalHandler("appId", "testuser"); @@ -1832,29 +1744,31 @@ describe("handlers", () => { }); describe("publishInDeveloperPortalHandler", async () => { + const sandbox = sinon.createSandbox(); + beforeEach(() => { - sinon.stub(globalVariables, "workspaceUri").value(vscode.Uri.file("path")); + sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.file("path")); }); afterEach(() => { - sinon.restore(); + sandbox.restore(); }); it("publish in developer portal - success", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); - sinon - .stub(extension.VS_CODE_UI, "selectFile") + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox + .stub(vsc_ui.VS_CODE_UI, "selectFile") .resolves(ok({ type: "success", result: "test.zip" })); - const publish = sinon.spy(handlers.core, "publishInDeveloperPortal"); - sinon - .stub(extension.VS_CODE_UI, "selectOption") + const publish = sandbox.spy(globalVariables.core, "publishInDeveloperPortal"); + sandbox + .stub(vsc_ui.VS_CODE_UI, "selectOption") .resolves(ok({ type: "success", result: "test.zip" })); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(vscode.commands, "executeCommand"); - sinon.stub(fs, "pathExists").resolves(true); - sinon.stub(fs, "readdir").resolves(["test.zip", "test.json"] as any); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(vscode.commands, "executeCommand"); + sandbox.stub(fs, "pathExists").resolves(true); + sandbox.stub(fs, "readdir").resolves(["test.zip", "test.json"] as any); const res = await handlers.publishInDeveloperPortalHandler(); if (res.isErr()) { @@ -1865,18 +1779,18 @@ describe("handlers", () => { }); it("publish in developer portal - cancelled", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); - sinon - .stub(extension.VS_CODE_UI, "selectFile") + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox + .stub(vsc_ui.VS_CODE_UI, "selectFile") .resolves(ok({ type: "success", result: "test2.zip" })); - const publish = sinon.spy(handlers.core, "publishInDeveloperPortal"); - sinon.stub(extension.VS_CODE_UI, "selectOption").resolves(err(new UserCancelError("VSC"))); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(vscode.commands, "executeCommand"); - sinon.stub(fs, "pathExists").resolves(true); - sinon.stub(fs, "readdir").resolves(["test.zip", "test.json"] as any); + const publish = sandbox.spy(globalVariables.core, "publishInDeveloperPortal"); + sandbox.stub(vsc_ui.VS_CODE_UI, "selectOption").resolves(err(new UserCancelError("VSC"))); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(vscode.commands, "executeCommand"); + sandbox.stub(fs, "pathExists").resolves(true); + sandbox.stub(fs, "readdir").resolves(["test.zip", "test.json"] as any); const res = await handlers.publishInDeveloperPortalHandler(); if (res.isErr()) { @@ -1887,15 +1801,15 @@ describe("handlers", () => { }); it("select file error", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); - sinon.stub(extension.VS_CODE_UI, "selectFile").resolves(err(new UserCancelError("VSC"))); - const publish = sinon.spy(handlers.core, "publishInDeveloperPortal"); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(vscode.commands, "executeCommand"); - sinon.stub(fs, "pathExists").resolves(true); - sinon.stub(fs, "readdir").resolves(["test.zip", "test.json"] as any); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(vsc_ui.VS_CODE_UI, "selectFile").resolves(err(new UserCancelError("VSC"))); + const publish = sandbox.spy(globalVariables.core, "publishInDeveloperPortal"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(vscode.commands, "executeCommand"); + sandbox.stub(fs, "pathExists").resolves(true); + sandbox.stub(fs, "readdir").resolves(["test.zip", "test.json"] as any); const res = await handlers.publishInDeveloperPortalHandler(); chai.assert.isTrue(res.isOk()); @@ -1904,24 +1818,26 @@ describe("handlers", () => { }); describe("openAppManagement", async () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); it("open link with loginHint", async () => { - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(M365TokenInstance, "getStatus").resolves( + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(M365TokenInstance, "getStatus").resolves( ok({ status: signedIn, token: undefined, accountInfo: { upn: "test" }, }) ); - const openUrl = sinon.stub(extension.VS_CODE_UI, "openUrl").resolves(ok(true)); + const openUrl = sandbox.stub(vsc_ui.VS_CODE_UI, "openUrl").resolves(ok(true)); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); const res = await handlers.openAppManagement(); @@ -1931,18 +1847,18 @@ describe("handlers", () => { }); it("open link without loginHint", async () => { - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); - sinon.stub(M365TokenInstance, "getStatus").resolves( + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(M365TokenInstance, "getStatus").resolves( ok({ status: signedOut, token: undefined, accountInfo: { upn: "test" }, }) ); - const openUrl = sinon.stub(extension.VS_CODE_UI, "openUrl").resolves(ok(true)); + const openUrl = sandbox.stub(vsc_ui.VS_CODE_UI, "openUrl").resolves(ok(true)); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); const res = await handlers.openAppManagement(); @@ -1953,19 +1869,21 @@ describe("handlers", () => { }); describe("installAppInTeams", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); it("happy path", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").resolves(); const result = await handlers.installAppInTeams(); chai.assert.equal(result, undefined); }); it("migration error", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); - sinon.stub(handlers, "showError").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); + sandbox.stub(handlers, "showError").resolves(); const result = await handlers.installAppInTeams(); chai.assert.equal(result, "1"); }); @@ -1973,9 +1891,9 @@ describe("handlers", () => { describe("callBackFunctions", () => { it("checkCopilotCallback()", async () => { - sinon.stub(localizeUtils, "localize").returns(""); + sandbox.stub(localizeUtils, "localize").returns(""); let showMessageCalledCount = 0; - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: async () => { showMessageCalledCount += 1; return Promise.resolve(ok("Enroll")); @@ -1985,30 +1903,28 @@ describe("handlers", () => { handlers.checkCopilotCallback(); chai.expect(showMessageCalledCount).to.be.equal(1); - sinon.restore(); }); it("checkSideloadingCallback()", async () => { - sinon.stub(localizeUtils, "localize").returns(""); + sandbox.stub(localizeUtils, "localize").returns(""); let showMessageCalledCount = 0; - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: async () => { showMessageCalledCount += 1; return Promise.resolve(ok("Get More Info")); }, }); - const createOrShow = sinon.stub(WebviewPanel, "createOrShow"); + const createOrShow = sandbox.stub(WebviewPanel, "createOrShow"); handlers.checkSideloadingCallback(); chai.expect(showMessageCalledCount).to.be.equal(1); sinon.assert.calledOnceWithExactly(createOrShow, PanelType.AccountHelp); - sinon.restore(); }); it("signinAzureCallback", async () => { - sinon.stub(AzureAccountManager.prototype, "getAccountInfo").returns({}); - const getIdentityCredentialStub = sinon.stub( + sandbox.stub(AzureAccountManager.prototype, "getAccountInfo").returns({}); + const getIdentityCredentialStub = sandbox.stub( AzureAccountManager.prototype, "getIdentityCredentialAsync" ); @@ -2016,65 +1932,66 @@ describe("handlers", () => { await handlers.signinAzureCallback([{}, { status: 0 }]); chai.assert.isTrue(getIdentityCredentialStub.calledOnce); - sinon.restore(); }); it("signinAzureCallback with error", async () => { - sinon.stub(AzureAccountManager.prototype, "getAccountInfo").returns({}); - sinon.stub(AzureAccountManager.prototype, "getIdentityCredentialAsync").throws(new Error()); + sandbox.stub(AzureAccountManager.prototype, "getAccountInfo").returns({}); + sandbox.stub(AzureAccountManager.prototype, "getIdentityCredentialAsync").throws(new Error()); const res = await handlers.signinAzureCallback([{}, { status: 0 }]); chai.assert.isTrue(res.isErr()); - sinon.restore(); }); it("signinAzureCallback with cancel error", async () => { - sinon.stub(AzureAccountManager.prototype, "getAccountInfo").returns({}); - sinon + sandbox.stub(AzureAccountManager.prototype, "getAccountInfo").returns({}); + sandbox .stub(AzureAccountManager.prototype, "getIdentityCredentialAsync") .throws(new UserCancelError("")); const res = await handlers.signinAzureCallback([{}, { status: 0 }]); chai.assert.isTrue(res.isOk()); - sinon.restore(); }); }); describe("validateAzureDependenciesHandler", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); it("happy path", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").resolves(); const result = await handlers.validateAzureDependenciesHandler(); chai.assert.equal(result, undefined); }); it("migration error", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); - sinon.stub(handlers, "showError").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); + sandbox.stub(handlers, "showError").resolves(); const result = await handlers.validateAzureDependenciesHandler(); chai.assert.equal(result, "1"); }); }); describe("validateLocalPrerequisitesHandler", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); it("happy path", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").resolves(); const result = await handlers.validateLocalPrerequisitesHandler(); chai.assert.equal(result, undefined); }); it("migration error", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); - sinon.stub(handlers, "showError").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); + sandbox.stub(handlers, "showError").resolves(); const result = await handlers.validateLocalPrerequisitesHandler(); chai.assert.equal(result, "1"); }); @@ -2082,55 +1999,53 @@ describe("handlers", () => { describe("backendExtensionsInstallHandler", () => { it("happy path", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").resolves(); const result = await handlers.backendExtensionsInstallHandler(); chai.assert.equal(result, undefined); - sinon.restore(); }); it("migration error", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); - sinon.stub(handlers, "showError").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); + sandbox.stub(handlers, "showError").resolves(); const result = await handlers.backendExtensionsInstallHandler(); chai.assert.equal(result, "1"); - sinon.restore(); }); }); describe("preDebugCheckHandler", () => { it("happy path", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").resolves(); const result = await handlers.preDebugCheckHandler(); chai.assert.equal(result, undefined); - sinon.restore(); }); it("happy path", async () => { - sinon.stub(debugCommonUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); - sinon.stub(handlers, "showError").resolves(); + sandbox.stub(migrationUtils, "triggerV3Migration").throws(err({ foo: "bar" } as any)); + sandbox.stub(handlers, "showError").resolves(); const result = await handlers.preDebugCheckHandler(); chai.assert.equal(result, "1"); - sinon.restore(); }); }); describe("migrateTeamsTabAppHandler", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); it("happy path", async () => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").returns(); - sinon.stub(localizeUtils, "localize").callsFake((key: string) => key); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").returns(); + sandbox.stub(localizeUtils, "localize").callsFake((key: string) => key); const progressHandler = new ProgressHandler("title", 1); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: () => Promise.resolve(ok("teamstoolkit.migrateTeamsTabApp.upgrade")), selectFolder: () => Promise.resolve(ok({ type: "success", result: "test" })), createProgressBar: () => progressHandler, }); - sinon.stub(VsCodeLogInstance, "info").returns(); - sinon.stub(TeamsAppMigrationHandler.prototype, "updatePackageJson").resolves(ok(true)); - sinon.stub(TeamsAppMigrationHandler.prototype, "updateCodes").resolves(ok([])); + sandbox.stub(VsCodeLogInstance, "info").returns(); + sandbox.stub(TeamsAppMigrationHandler.prototype, "updatePackageJson").resolves(ok(true)); + sandbox.stub(TeamsAppMigrationHandler.prototype, "updateCodes").resolves(ok([])); const result = await handlers.migrateTeamsTabAppHandler(); @@ -2138,18 +2053,18 @@ describe("handlers", () => { }); it("happy path: failed files", async () => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").returns(); - sinon.stub(localizeUtils, "localize").callsFake((key: string) => key); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").returns(); + sandbox.stub(localizeUtils, "localize").callsFake((key: string) => key); const progressHandler = new ProgressHandler("title", 1); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: () => Promise.resolve(ok("teamstoolkit.migrateTeamsTabApp.upgrade")), selectFolder: () => Promise.resolve(ok({ type: "success", result: "test" })), createProgressBar: () => progressHandler, }); - sinon.stub(VsCodeLogInstance, "info").returns(); - const warningStub = sinon.stub(VsCodeLogInstance, "warning"); - sinon.stub(TeamsAppMigrationHandler.prototype, "updatePackageJson").resolves(ok(true)); - sinon + sandbox.stub(VsCodeLogInstance, "info").returns(); + const warningStub = sandbox.stub(VsCodeLogInstance, "warning"); + sandbox.stub(TeamsAppMigrationHandler.prototype, "updatePackageJson").resolves(ok(true)); + sandbox .stub(TeamsAppMigrationHandler.prototype, "updateCodes") .resolves(ok(["test1", "test2"])); @@ -2160,18 +2075,18 @@ describe("handlers", () => { }); it("error", async () => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").returns(); - const sendTelemetryErrorEventStub = sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(localizeUtils, "localize").callsFake((key: string) => key); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").returns(); + const sendTelemetryErrorEventStub = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(localizeUtils, "localize").callsFake((key: string) => key); const progressHandler = new ProgressHandler("title", 1); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: () => Promise.resolve(ok("teamstoolkit.migrateTeamsTabApp.upgrade")), selectFolder: () => Promise.resolve(ok({ type: "success", result: "test" })), createProgressBar: () => progressHandler, }); - sinon.stub(VsCodeLogInstance, "info").returns(); - sinon.stub(TeamsAppMigrationHandler.prototype, "updatePackageJson").resolves(ok(true)); - sinon + sandbox.stub(VsCodeLogInstance, "info").returns(); + sandbox.stub(TeamsAppMigrationHandler.prototype, "updatePackageJson").resolves(ok(true)); + sandbox .stub(TeamsAppMigrationHandler.prototype, "updateCodes") .resolves(err({ foo: "bar" } as any)); @@ -2182,10 +2097,10 @@ describe("handlers", () => { }); it("user cancel", async () => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").returns(); - sinon.stub(localizeUtils, "localize").callsFake((key: string) => key); - const sendTelemetryErrorEventStub = sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").returns(); + sandbox.stub(localizeUtils, "localize").callsFake((key: string) => key); + const sendTelemetryErrorEventStub = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: () => Promise.resolve(ok("teamstoolkit.migrateTeamsTabApp.upgrade")), selectFolder: () => Promise.resolve(ok({ type: "skip" })), }); @@ -2197,10 +2112,10 @@ describe("handlers", () => { }); it("user cancel: skip folder selection", async () => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").returns(); - sinon.stub(localizeUtils, "localize").callsFake((key: string) => key); - const sendTelemetryErrorEventStub = sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").returns(); + sandbox.stub(localizeUtils, "localize").callsFake((key: string) => key); + const sendTelemetryErrorEventStub = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: () => Promise.resolve(ok("cancel")), }); @@ -2211,17 +2126,17 @@ describe("handlers", () => { }); it("no change in package.json", async () => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").returns(); - sinon.stub(localizeUtils, "localize").callsFake((key: string) => key); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").returns(); + sandbox.stub(localizeUtils, "localize").callsFake((key: string) => key); const progressHandler = new ProgressHandler("title", 1); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: () => Promise.resolve(ok("teamstoolkit.migrateTeamsTabApp.upgrade")), selectFolder: () => Promise.resolve(ok({ type: "success", result: "test" })), createProgressBar: () => progressHandler, }); - sinon.stub(VsCodeLogInstance, "info").returns(); - sinon.stub(VsCodeLogInstance, "warning").returns(); - sinon.stub(TeamsAppMigrationHandler.prototype, "updatePackageJson").resolves(ok(false)); + sandbox.stub(VsCodeLogInstance, "info").returns(); + sandbox.stub(VsCodeLogInstance, "warning").returns(); + sandbox.stub(TeamsAppMigrationHandler.prototype, "updatePackageJson").resolves(ok(false)); const result = await handlers.migrateTeamsTabAppHandler(); @@ -2230,21 +2145,23 @@ describe("handlers", () => { }); describe("migrateTeamsManifestHandler", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); it("happy path", async () => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").returns(); - sinon.stub(localizeUtils, "localize").callsFake((key: string) => key); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").returns(); + sandbox.stub(localizeUtils, "localize").callsFake((key: string) => key); const progressHandler = new ProgressHandler("title", 1); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: () => Promise.resolve(ok("teamstoolkit.migrateTeamsManifest.upgrade")), selectFile: () => Promise.resolve(ok({ type: "success", result: "test" })), createProgressBar: () => progressHandler, }); - sinon.stub(VsCodeLogInstance, "info").returns(); - sinon.stub(TeamsAppMigrationHandler.prototype, "updateManifest").resolves(ok(null)); + sandbox.stub(VsCodeLogInstance, "info").returns(); + sandbox.stub(TeamsAppMigrationHandler.prototype, "updateManifest").resolves(ok(null)); const result = await handlers.migrateTeamsManifestHandler(); @@ -2252,17 +2169,17 @@ describe("handlers", () => { }); it("user cancel: skip file selection", async () => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").returns(); - const sendTelemetryErrorEventStub = sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sinon.stub(localizeUtils, "localize").callsFake((key: string) => key); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").returns(); + const sendTelemetryErrorEventStub = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(localizeUtils, "localize").callsFake((key: string) => key); const progressHandler = new ProgressHandler("title", 1); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: () => Promise.resolve(ok("teamstoolkit.migrateTeamsManifest.upgrade")), selectFile: () => Promise.resolve(ok({ type: "skip" })), createProgressBar: () => progressHandler, }); - sinon.stub(VsCodeLogInstance, "info").returns(); - sinon.stub(TeamsAppMigrationHandler.prototype, "updateManifest").resolves(ok(null)); + sandbox.stub(VsCodeLogInstance, "info").returns(); + sandbox.stub(TeamsAppMigrationHandler.prototype, "updateManifest").resolves(ok(null)); const result = await handlers.migrateTeamsManifestHandler(); @@ -2271,20 +2188,20 @@ describe("handlers", () => { }); it("error", async () => { - sinon.stub(ExtTelemetry, "sendTelemetryEvent").returns(); - sinon.stub(localizeUtils, "localize").callsFake((key: string) => key); - const sendTelemetryErrorEventStub = sinon.stub(ExtTelemetry, "sendTelemetryErrorEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent").returns(); + sandbox.stub(localizeUtils, "localize").callsFake((key: string) => key); + const sendTelemetryErrorEventStub = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); const progressHandler = new ProgressHandler("title", 1); - sinon.stub(extension, "VS_CODE_UI").value({ + sandbox.stub(vsc_ui, "VS_CODE_UI").value({ showMessage: () => Promise.resolve(ok("teamstoolkit.migrateTeamsManifest.upgrade")), selectFile: () => Promise.resolve(ok({ type: "success", result: "test" })), createProgressBar: () => progressHandler, }); - sinon.stub(VsCodeLogInstance, "info").returns(); - sinon + sandbox.stub(VsCodeLogInstance, "info").returns(); + sandbox .stub(TeamsAppMigrationHandler.prototype, "updateManifest") .resolves(err(new UserError("source", "name", ""))); - sinon.stub(handlers, "showError").callsFake(async () => {}); + sandbox.stub(handlers, "showError").callsFake(async () => {}); const result = await handlers.migrateTeamsManifestHandler(); @@ -2302,8 +2219,8 @@ describe("handlers", () => { it("opens upgrade guide when clicked from sidebar", async () => { const sendTelemetryStub = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); - const openUrl = sandbox.stub(extension.VS_CODE_UI, "openUrl").resolves(ok(true)); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + const openUrl = sandbox.stub(vsc_ui.VS_CODE_UI, "openUrl").resolves(ok(true)); await handlers.openDocumentHandler( extTelemetryEvents.TelemetryTriggerFrom.SideBar, @@ -2373,7 +2290,8 @@ describe("handlers", () => { }; const hideStub = sandbox.stub(stubQuickPick, "hide"); sandbox.stub(vscode.window, "createQuickPick").returns(stubQuickPick as any); - sandbox.stub(extension.VS_CODE_UI, "selectOption").resolves(ok({ result: "unknown" } as any)); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(vsc_ui.VS_CODE_UI, "selectOption").resolves(ok({ result: "unknown" } as any)); await handlers.cmpAccountsHandler([]); changeSelectionCallback([stubQuickPick.items[1]]); @@ -2388,7 +2306,7 @@ describe("handlers", () => { }); it("updatePreviewManifest", async () => { - sandbox.stub(handlers, "core").value(new MockCore()); + sandbox.stub(globalVariables, "core").value(new MockCore()); sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); const openTextDocumentStub = sandbox @@ -2409,11 +2327,12 @@ describe("openPreviewAadFile", () => { }); it("manifest file not exists", async () => { const core = new MockCore(); - sandbox.stub(handlers, "core").value(core); + sandbox.stub(globalVariables, "core").value(core); sandbox.stub(projectSettingsHelper, "isValidProject").returns(true); sandbox.stub(fs, "existsSync").returns(false); sandbox.stub(environmentManager, "listAllEnvConfigs").resolves(ok(["dev"])); - sandbox.stub(extension.VS_CODE_UI, "selectOption").resolves( + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(vsc_ui.VS_CODE_UI, "selectOption").resolves( ok({ type: "success", result: "dev", @@ -2421,7 +2340,7 @@ describe("openPreviewAadFile", () => { ); sandbox.stub(handlers, "askTargetEnvironment").resolves(ok("dev")); sandbox.stub(handlers, "showError").callsFake(async () => {}); - sandbox.stub(handlers.core, "buildAadManifest").resolves(ok(undefined)); + sandbox.stub(globalVariables.core, "buildAadManifest").resolves(ok(undefined)); sandbox.stub(ExtTelemetry, "sendTelemetryEvent").resolves(); const res = await handlers.openPreviewAadFile([]); chai.assert.isTrue(res.isErr()); @@ -2429,11 +2348,12 @@ describe("openPreviewAadFile", () => { it("happy path", async () => { const core = new MockCore(); - sandbox.stub(handlers, "core").value(core); + sandbox.stub(globalVariables, "core").value(core); sandbox.stub(projectSettingsHelper, "isValidProject").returns(true); sandbox.stub(fs, "existsSync").returns(true); sandbox.stub(environmentManager, "listAllEnvConfigs").resolves(ok(["dev"])); - sandbox.stub(extension.VS_CODE_UI, "selectOption").resolves( + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + sandbox.stub(vsc_ui.VS_CODE_UI, "selectOption").resolves( ok({ type: "success", result: "dev", @@ -2441,7 +2361,7 @@ describe("openPreviewAadFile", () => { ); sandbox.stub(handlers, "askTargetEnvironment").resolves(ok("dev")); sandbox.stub(handlers, "showError").callsFake(async () => {}); - sandbox.stub(handlers.core, "buildAadManifest").resolves(ok(undefined)); + sandbox.stub(globalVariables.core, "buildAadManifest").resolves(ok(undefined)); sandbox.stub(ExtTelemetry, "sendTelemetryEvent").resolves(); sandbox.stub(vscode.workspace, "openTextDocument").resolves(); sandbox.stub(vscode.window, "showTextDocument").resolves(); @@ -2461,7 +2381,7 @@ describe("editAadManifestTemplate", () => { it("happy path", async () => { const workspacePath = "/test/workspace/path"; const workspaceUri = vscode.Uri.file(workspacePath); - sinon.stub(globalVariables, "workspaceUri").value(workspaceUri); + sandbox.stub(globalVariables, "workspaceUri").value(workspaceUri); const openTextDocumentStub = sandbox .stub(vscode.workspace, "openTextDocument") @@ -2479,7 +2399,7 @@ describe("editAadManifestTemplate", () => { it("happy path: no parameter", async () => { const workspacePath = "/test/workspace/path"; const workspaceUri = vscode.Uri.file(workspacePath); - sinon.stub(globalVariables, "workspaceUri").value(workspaceUri); + sandbox.stub(globalVariables, "workspaceUri").value(workspaceUri); const openTextDocumentStub = sandbox .stub(vscode.workspace, "openTextDocument") @@ -2493,7 +2413,7 @@ describe("editAadManifestTemplate", () => { it("happy path: workspaceUri is undefined", async () => { const workspaceUri = undefined; - sinon.stub(globalVariables, "workspaceUri").value(undefined); + sandbox.stub(globalVariables, "workspaceUri").value(undefined); const openTextDocumentStub = sandbox .stub(vscode.workspace, "openTextDocument") @@ -2532,6 +2452,26 @@ describe("autoOpenProjectHandler", () => { chai.assert.isTrue(executeCommandFunc.calledOnce); }); + it("opens walk through if workspace Uri exists", async () => { + sandbox.stub(globalState, "globalStateGet").callsFake(async (key: string) => { + if (key === "fx-extension.openWalkThrough") { + return true; + } else { + return false; + } + }); + const globalStateUpdateStub = sandbox.stub(globalState, "globalStateUpdate"); + sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.parse("test")); + const sendTelemetryStub = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + const executeCommandFunc = sandbox.stub(vscode.commands, "executeCommand"); + + await handlers.autoOpenProjectHandler(); + + chai.assert.isTrue(sendTelemetryStub.calledOnce); + chai.assert.isTrue(executeCommandFunc.calledOnce); + chai.assert.isTrue(globalStateUpdateStub.calledTwice); + }); + it("opens README", async () => { sandbox.stub(globalVariables, "workspaceUri").value(vscode.Uri.file("test")); sandbox.stub(globalVariables, "isTeamsFxProject").resolves(false); @@ -2783,7 +2723,8 @@ describe("autoOpenProjectHandler", () => { } }); const globalStateStub = sandbox.stub(globalState, "globalStateUpdate"); - const runCommandStub = sandbox.stub(extension.VS_CODE_UI, "runCommand"); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + const runCommandStub = sandbox.stub(vsc_ui.VS_CODE_UI, "runCommand"); sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); await handlers.autoOpenProjectHandler(); @@ -2803,7 +2744,7 @@ describe("autoOpenProjectHandler", () => { it("runUserTask() - error", async () => { const sendTelemetryStub = sandbox.stub(ExtTelemetry, "sendTelemetryErrorEvent"); - sandbox.stub(handlers, "core").value(undefined); + sandbox.stub(globalVariables, "core").value(undefined); sandbox.stub(commonUtils, "getTeamsAppTelemetryInfoByEnv"); sandbox.stub(VsCodeLogInstance, "error"); @@ -3173,28 +3114,26 @@ describe("autoOpenProjectHandler", () => { }); it("treeViewDebugInTestToolHandler", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - const executeCommandStub = sinon.stub(vscode.commands, "executeCommand"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + const executeCommandStub = sandbox.stub(vscode.commands, "executeCommand"); await handlers.debugInTestToolHandler("treeview")(); chai.assert.isTrue( executeCommandStub.calledOnceWith("workbench.action.quickOpen", "debug Debug in Test Tool") ); - sinon.restore(); }); it("messageDebugInTestToolHandler", async () => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); - const executeCommandStub = sinon.stub(vscode.commands, "executeCommand"); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + const executeCommandStub = sandbox.stub(vscode.commands, "executeCommand"); await handlers.debugInTestToolHandler("message")(); chai.assert.isTrue( executeCommandStub.calledOnceWith("workbench.action.quickOpen", "debug Debug in Test Tool") ); - sinon.restore(); }); }); diff --git a/packages/vscode-extension/test/extension/hoverProvider.test.ts b/packages/vscode-extension/test/extension/hoverProvider.test.ts index 9c1bb5396c..cefd22e7ef 100644 --- a/packages/vscode-extension/test/extension/hoverProvider.test.ts +++ b/packages/vscode-extension/test/extension/hoverProvider.test.ts @@ -8,11 +8,12 @@ import * as sinon from "sinon"; import { v4 } from "uuid"; import * as vscode from "vscode"; import { environmentVariableRegex } from "../../src/constants"; -import * as handlers from "../../src/handlers"; +import * as globalVariables from "../../src/globalVariables"; import { ManifestTemplateHoverProvider } from "../../src/hoverProvider"; import { MockCore } from "../mocks/mockCore"; describe("Manifest template hover - V3", async () => { + const sandbox = sinon.createSandbox(); const text = `{ "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.14/MicrosoftTeams.schema.json", "manifestVersion": "1.14", @@ -37,17 +38,17 @@ describe("Manifest template hover - V3", async () => { } as any; beforeEach(() => { - sinon.stub(handlers, "core").value(new MockCore()); - sinon.stub(envUtil, "listEnv").resolves(ok(["local", "dev"])); + sandbox.stub(globalVariables, "core").value(new MockCore()); + sandbox.stub(envUtil, "listEnv").resolves(ok(["local", "dev"])); }); afterEach(() => { - sinon.restore(); + sandbox.restore(); environmentVariableRegex.lastIndex = 0; }); it("hover - match", async () => { - sinon.stub(envUtil, "readEnv").resolves( + sandbox.stub(envUtil, "readEnv").resolves( ok({ ["TEAMS_APP_ID"]: v4(), }) @@ -65,7 +66,7 @@ describe("Manifest template hover - V3", async () => { }); it("hover - local", async () => { - sinon.stub(envUtil, "readEnv").resolves( + sandbox.stub(envUtil, "readEnv").resolves( ok({ ["TEAMS_APP_ID"]: v4(), }) @@ -100,7 +101,7 @@ describe("Manifest template hover - V3", async () => { }); it("hover-undefined", async () => { - sinon.stub(envUtil, "readEnv").resolves( + sandbox.stub(envUtil, "readEnv").resolves( ok({ ["TEAMS_APP_ID"]: v4(), }) @@ -115,7 +116,7 @@ describe("Manifest template hover - V3", async () => { }); it("hover - no value", async () => { - sinon.stub(envUtil, "readEnv").resolves(ok({})); + sandbox.stub(envUtil, "readEnv").resolves(ok({})); const hoverProvider = new ManifestTemplateHoverProvider(); const position = new vscode.Position(5, 15); diff --git a/packages/vscode-extension/test/extension/officeDevHandler.test.ts b/packages/vscode-extension/test/extension/officeDevHandler.test.ts index 7e57b31fa3..296917f5e6 100644 --- a/packages/vscode-extension/test/extension/officeDevHandler.test.ts +++ b/packages/vscode-extension/test/extension/officeDevHandler.test.ts @@ -7,15 +7,14 @@ import * as sinon from "sinon"; import * as vscode from "vscode"; import { Terminal } from "vscode"; import { OfficeDevTerminal, TriggerCmdType } from "../../src/debug/taskTerminal/officeDevTerminal"; -import * as extension from "../../src/extension"; import * as globalVariables from "../../src/globalVariables"; import * as handlers from "../../src/handlers"; import * as officeDevHandlers from "../../src/officeDevHandlers"; import { generateManifestGUID, stopOfficeAddInDebug } from "../../src/officeDevHandlers"; import { VsCodeUI } from "../../src/qm/vsc_ui"; +import * as vsc_ui from "../../src/qm/vsc_ui"; import { ExtTelemetry } from "../../src/telemetry/extTelemetry"; import * as localizeUtils from "../../src/utils/localizeUtils"; -import * as teamsfxCore from "@microsoft/teamsfx-core"; import * as projectSettingsHelper from "@microsoft/teamsfx-core/build/common/projectSettingsHelper"; describe("officeDevHandler", () => { @@ -30,8 +29,8 @@ describe("officeDevHandler", () => { openLinkFunc: (args?: any[]) => Promise>, urlPath: string ) { - sinon.stub(extension, "VS_CODE_UI").value(new VsCodeUI({})); - const openUrl = sinon.stub(extension.VS_CODE_UI, "openUrl").resolves(ok(true)); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + const openUrl = sandbox.stub(vsc_ui.VS_CODE_UI, "openUrl").resolves(ok(true)); const res = await openLinkFunc(undefined); chai.assert.isTrue(openUrl.calledOnce); chai.assert.isTrue(res.isOk()); @@ -278,17 +277,19 @@ describe("autoOpenOfficeDevProjectHandler", () => { }); describe("OfficeDevTerminal", () => { + const sandbox = sinon.createSandbox(); let getInstanceStub: any, showStub: any, sendTextStub: any; beforeEach(() => { - getInstanceStub = sinon.stub(OfficeDevTerminal, "getInstance"); - showStub = sinon.stub(); - sendTextStub = sinon.stub(); + getInstanceStub = sandbox.stub(OfficeDevTerminal, "getInstance"); + showStub = sandbox.stub(); + sendTextStub = sandbox.stub(); getInstanceStub.returns({ show: showStub, sendText: sendTextStub }); }); afterEach(() => { getInstanceStub.restore(); + sandbox.restore(); }); it("should validate Office AddIn Manifest", async () => { @@ -334,18 +335,22 @@ describe("stopOfficeAddInDebug", () => { let getInstanceStub: sinon.SinonStub; let showStub: sinon.SinonStub; let sendTextStub: sinon.SinonStub; + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); it("should call getInstance, show and sendText", async () => { const terminalStub = new TerminalStub(); - getInstanceStub = sinon.stub(OfficeDevTerminal, "getInstance").returns(terminalStub); - showStub = sinon.stub(terminalStub, "show"); - sendTextStub = sinon.stub(terminalStub, "sendText"); + getInstanceStub = sandbox.stub(OfficeDevTerminal, "getInstance").returns(terminalStub); + showStub = sandbox.stub(terminalStub, "show"); + sendTextStub = sandbox.stub(terminalStub, "sendText"); await stopOfficeAddInDebug(); sinon.assert.calledOnce(getInstanceStub); sinon.assert.calledOnce(showStub); sinon.assert.calledOnce(sendTextStub); - sinon.restore(); }); }); @@ -353,12 +358,17 @@ describe("generateManifestGUID", () => { let getInstanceStub: sinon.SinonStub; let showStub: sinon.SinonStub; let sendTextStub: sinon.SinonStub; + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); it("should call getInstance, show and sendText with correct arguments", async () => { const terminalStub = new TerminalStub(); - getInstanceStub = sinon.stub(OfficeDevTerminal, "getInstance").returns(terminalStub); - showStub = sinon.stub(terminalStub, "show"); - sendTextStub = sinon.stub(terminalStub, "sendText"); + getInstanceStub = sandbox.stub(OfficeDevTerminal, "getInstance").returns(terminalStub); + showStub = sandbox.stub(terminalStub, "show"); + sendTextStub = sandbox.stub(terminalStub, "sendText"); await generateManifestGUID(); @@ -366,6 +376,5 @@ describe("generateManifestGUID", () => { sinon.assert.calledOnce(showStub); sinon.assert.calledOnce(sendTextStub); sinon.assert.calledWithExactly(sendTextStub, TriggerCmdType.triggerGenerateGUID); - sinon.restore(); }); }); diff --git a/packages/vscode-extension/test/extension/progressHandler.test.ts b/packages/vscode-extension/test/extension/progressHandler.test.ts index 1c380152d6..073e1068fc 100644 --- a/packages/vscode-extension/test/extension/progressHandler.test.ts +++ b/packages/vscode-extension/test/extension/progressHandler.test.ts @@ -7,14 +7,20 @@ import * as chai from "chai"; import { window } from "vscode"; import { ProgressHandler } from "../../src/progressHandler"; -import * as commonUtils from "../../src/utils/commonUtils"; +import * as vsc_ui from "@microsoft/vscode-ui"; import * as localizeUtils from "../../src/utils/localizeUtils"; import * as vscodeMocks from "../mocks/vsc"; +afterEach(() => { + sinon.restore(); +}); + describe("ProgressHandler", () => { let message: string | undefined = undefined; + const sandbox = sinon.createSandbox(); + beforeEach(() => { - sinon.stub(window, "withProgress").callsFake(async (options, task) => { + sandbox.stub(window, "withProgress").callsFake(async (options, task) => { return await task( { report: (value) => { @@ -24,8 +30,8 @@ describe("ProgressHandler", () => { new vscodeMocks.CancellationToken() ); }); - sinon.stub(commonUtils, "sleep").callsFake(async () => {}); - sinon.stub(localizeUtils, "localize").callsFake((key) => { + sandbox.stub(vsc_ui, "sleep").callsFake(async () => {}); + sandbox.stub(localizeUtils, "localize").callsFake((key) => { if (key === "teamstoolkit.progressHandler.showOutputLink") { return "Check [output window](%s) for details."; } else if (key === "teamstoolkit.progressHandler.showTerminalLink") { @@ -40,7 +46,7 @@ describe("ProgressHandler", () => { }); afterEach(() => { - sinon.restore(); + sandbox.restore(); }); it("terminal", async () => { @@ -53,7 +59,6 @@ describe("ProgressHandler", () => { expected = "test title: [1/1] test message. Check [terminal window](command:workbench.action.terminal.focus) for details. (Notice: You can reload the window and retry if task spends too long time.)"; chai.assert.equal(message, expected); - sinon.restore(); }); it("output", async () => { @@ -66,7 +71,6 @@ describe("ProgressHandler", () => { expected = "test title: [1/1] test message. Check [output window](command:fx-extension.showOutputChannel) for details. (Notice: You can reload the window and retry if task spends too long time.)"; chai.assert.equal(message, expected); - sinon.restore(); }); it("not started", async () => { diff --git a/packages/vscode-extension/test/extension/qm/vsc_ui.test.ts b/packages/vscode-extension/test/extension/qm/vsc_ui.test.ts index 285885381b..1f1aa0c01c 100644 --- a/packages/vscode-extension/test/extension/qm/vsc_ui.test.ts +++ b/packages/vscode-extension/test/extension/qm/vsc_ui.test.ts @@ -19,26 +19,21 @@ import { import { err, - FxError, - InputResult, ok, - Result, SelectFileConfig, - SelectFileResult, SelectFolderConfig, SingleFileOrInputConfig, SingleSelectConfig, UserError, } from "@microsoft/teamsfx-api"; -import { FxQuickPickItem, UserCancelError } from "@microsoft/vscode-ui"; +import { FxQuickPickItem, sleep, UserCancelError } from "@microsoft/vscode-ui"; import { VsCodeUI } from "../../../src/qm/vsc_ui"; import { ExtTelemetry } from "../../../src/telemetry/extTelemetry"; -import { sleep } from "../../../src/utils/commonUtils"; import { VsCodeLogProvider } from "../../../src/commonlib/log"; describe("UI Unit Tests", async () => { - before(() => { - // Mock user input. + afterEach(() => { + sinon.restore(); }); describe("Manually", () => { @@ -64,6 +59,12 @@ describe("UI Unit Tests", async () => { }); describe("Select Folder", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + it("has returns default folder", async function (this: Mocha.Context) { const ui = new VsCodeUI({}); const config: SelectFolderConfig = { @@ -90,10 +91,10 @@ describe("UI Unit Tests", async () => { mockQuickPick.selectedItems = [{ id: "default" } as FxQuickPickItem]; acceptListener(); }); - sinon.stub(window, "createQuickPick").callsFake(() => { + sandbox.stub(window, "createQuickPick").callsFake(() => { return mockQuickPick; }); - // const telemetryStub = sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + // const telemetryStub = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFolder(config); @@ -106,7 +107,6 @@ describe("UI Unit Tests", async () => { // "selected-option": "default", // }) // ).is.true; - sinon.restore(); }); it("has returns user cancel", async function (this: Mocha.Context) { @@ -135,11 +135,11 @@ describe("UI Unit Tests", async () => { mockQuickPick.selectedItems = [{ id: "browse" } as FxQuickPickItem]; acceptListener(); }); - sinon.stub(window, "createQuickPick").callsFake(() => { + sandbox.stub(window, "createQuickPick").callsFake(() => { return mockQuickPick; }); - sinon.stub(window, "showOpenDialog").resolves(undefined); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(window, "showOpenDialog").resolves(undefined); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFolder(config); @@ -147,11 +147,16 @@ describe("UI Unit Tests", async () => { if (result.isErr()) { expect(result.error instanceof UserCancelError).is.true; } - sinon.restore(); }); }); describe("Select File", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + it("has returns default file", async function (this: Mocha.Context) { const ui = new VsCodeUI({}); const config: SelectFileConfig = { @@ -178,10 +183,10 @@ describe("UI Unit Tests", async () => { mockQuickPick.selectedItems = [{ id: "default" } as FxQuickPickItem]; acceptListener(); }); - sinon.stub(window, "createQuickPick").callsFake(() => { + sandbox.stub(window, "createQuickPick").callsFake(() => { return mockQuickPick; }); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFile(config); @@ -189,7 +194,6 @@ describe("UI Unit Tests", async () => { if (result.isOk()) { expect(result.value.result).to.equal("default file"); } - sinon.restore(); }); it("has returns user cancel", async function (this: Mocha.Context) { @@ -218,11 +222,11 @@ describe("UI Unit Tests", async () => { mockQuickPick.selectedItems = [{ id: "browse" } as FxQuickPickItem]; onHideListener(); }); - sinon.stub(window, "createQuickPick").callsFake(() => { + sandbox.stub(window, "createQuickPick").callsFake(() => { return mockQuickPick; }); - sinon.stub(window, "showOpenDialog").resolves(undefined); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(window, "showOpenDialog").resolves(undefined); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFile(config); @@ -230,7 +234,6 @@ describe("UI Unit Tests", async () => { if (result.isErr()) { expect(result.error instanceof UserCancelError).is.true; } - sinon.restore(); }); it("has returns item in possible files", async function (this: Mocha.Context) { @@ -269,10 +272,10 @@ describe("UI Unit Tests", async () => { mockQuickPick.selectedItems = [{ id: "1" } as FxQuickPickItem]; acceptListener(); }); - sinon.stub(window, "createQuickPick").callsFake(() => { + sandbox.stub(window, "createQuickPick").callsFake(() => { return mockQuickPick; }); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFile(config); @@ -280,7 +283,6 @@ describe("UI Unit Tests", async () => { if (result.isOk()) { expect(result.value.result).to.equal("1"); } - sinon.restore(); }); it("has returns invalid input item id", async function (this: Mocha.Context) { @@ -304,7 +306,6 @@ describe("UI Unit Tests", async () => { if (result.isErr()) { expect(result.error.name).to.equal("InvalidInput"); } - sinon.restore(); }); it("selects a file which pass validation", async function (this: Mocha.Context) { @@ -339,16 +340,14 @@ describe("UI Unit Tests", async () => { mockQuickPick.selectedItems = [{ id: "default" } as FxQuickPickItem]; acceptListener(); }); - sinon.stub(window, "createQuickPick").callsFake(() => { + sandbox.stub(window, "createQuickPick").callsFake(() => { return mockQuickPick; }); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const res = await ui.selectFile(config); expect(res.isOk()).is.true; - - sinon.restore(); }); it("selects a file with error thrown when validating result", async function (this: Mocha.Context) { @@ -380,36 +379,39 @@ describe("UI Unit Tests", async () => { mockQuickPick.selectedItems = [{ id: "default" } as FxQuickPickItem]; acceptListener(); }); - sinon.stub(window, "createQuickPick").callsFake(() => { + sandbox.stub(window, "createQuickPick").callsFake(() => { return mockQuickPick; }); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const res = await ui.selectFile(config); expect(res.isErr()).is.true; - - sinon.restore(); }); }); describe("Open File", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + it("open the preview of Markdown file", async function (this: Mocha.Context) { const ui = new VsCodeUI({}); - sinon.stub(workspace, "openTextDocument").resolves({} as TextDocument); + sandbox.stub(workspace, "openTextDocument").resolves({} as TextDocument); let executedCommand = ""; - sinon.stub(commands, "executeCommand").callsFake((command: string, ...args: any[]) => { + sandbox.stub(commands, "executeCommand").callsFake((command: string, ...args: any[]) => { executedCommand = command; return Promise.resolve(); }); - const showTextStub = sinon.stub(window, "showTextDocument"); + const showTextStub = sandbox.stub(window, "showTextDocument"); const result = await ui.openFile("test.md"); expect(result.isOk()).is.true; expect(showTextStub.calledOnce).to.be.false; expect(executedCommand).to.equal("markdown.showPreview"); - sinon.restore(); }); }); @@ -452,8 +454,8 @@ describe("UI Unit Tests", async () => { const timer = sandbox.useFakeTimers(); const ui = new VsCodeUI({}); const mockTerminal = { - show: sinon.stub(), - sendText: sinon.stub(), + show: sandbox.stub(), + sendText: sandbox.stub(), processId: new Promise((resolve: (value: string) => void, reject) => { const wait = setTimeout(() => { clearTimeout(wait); @@ -527,10 +529,10 @@ describe("UI Unit Tests", async () => { mockQuickPick.selectedItems = [{ id: "1" } as FxQuickPickItem]; acceptListener(); }); - sinon.stub(window, "createQuickPick").callsFake(() => { + sandbox.stub(window, "createQuickPick").callsFake(() => { return mockQuickPick; }); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectOption(config); @@ -538,7 +540,6 @@ describe("UI Unit Tests", async () => { if (result.isOk()) { expect(result.value.result).to.equal("1"); } - sinon.restore(); }); it("select fail with validation", async function (this: Mocha.Context) { @@ -574,16 +575,14 @@ describe("UI Unit Tests", async () => { mockQuickPick.selectedItems = [{ id: "1" } as FxQuickPickItem]; acceptListener(); }); - sinon.stub(window, "createQuickPick").callsFake(() => { + sandbox.stub(window, "createQuickPick").callsFake(() => { return mockQuickPick; }); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectOption(config); expect(result.isErr()).is.true; - - sinon.restore(); }); it("loads dynamic options in a short time", async function (this: Mocha.Context) { @@ -772,6 +771,12 @@ describe("UI Unit Tests", async () => { }); describe("Select local file or input", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + it("selects local file successfully", async function (this: Mocha.Context) { const ui = new VsCodeUI({}); const config: SingleFileOrInputConfig = { @@ -789,10 +794,10 @@ describe("UI Unit Tests", async () => { }, }; - sinon + sandbox .stub(VsCodeUI.prototype, "selectFile") .resolves(ok({ type: "success", result: "file" })); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFileOrInput(config); @@ -800,7 +805,6 @@ describe("UI Unit Tests", async () => { if (result.isOk()) { expect(result.value.result).to.equal("file"); } - sinon.restore(); }); it("selects local file error", async function (this: Mocha.Context) { @@ -820,10 +824,10 @@ describe("UI Unit Tests", async () => { }, }; - sinon + sandbox .stub(VsCodeUI.prototype, "selectFile") .resolves(err(new UserError("source", "name", "msg", "msg"))); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFileOrInput(config); @@ -831,7 +835,6 @@ describe("UI Unit Tests", async () => { if (result.isErr()) { expect(result.error.name).to.equal("name"); } - sinon.restore(); }); it("inputs a value sucessfully", async function (this: Mocha.Context) { @@ -851,13 +854,13 @@ describe("UI Unit Tests", async () => { }, }; - sinon + sandbox .stub(VsCodeUI.prototype, "selectFile") .resolves(ok({ type: "success", result: "input" })); - sinon + sandbox .stub(VsCodeUI.prototype, "inputText") .resolves(ok({ type: "success", result: "testUrl" })); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFileOrInput(config); @@ -865,7 +868,6 @@ describe("UI Unit Tests", async () => { if (result.isOk()) { expect(result.value.result).to.equal("testUrl"); } - sinon.restore(); }); it("inputs a value error", async function (this: Mocha.Context) { @@ -885,13 +887,13 @@ describe("UI Unit Tests", async () => { }, }; - sinon + sandbox .stub(VsCodeUI.prototype, "selectFile") .resolves(ok({ type: "success", result: "input" })); - sinon + sandbox .stub(VsCodeUI.prototype, "inputText") .resolves(err(new UserError("source", "name", "msg", "msg"))); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFileOrInput(config); @@ -899,7 +901,6 @@ describe("UI Unit Tests", async () => { if (result.isErr()) { expect(result.error.name).to.equal("name"); } - sinon.restore(); }); it("inputs a value back and then sucessfully", async function (this: Mocha.Context) { @@ -919,16 +920,16 @@ describe("UI Unit Tests", async () => { }, }; - sinon + sandbox .stub(VsCodeUI.prototype, "selectFile") .resolves(ok({ type: "success", result: "input" })); - sinon + sandbox .stub(VsCodeUI.prototype, "inputText") .onFirstCall() .resolves(ok({ type: "back" })) .onSecondCall() .resolves(ok({ type: "success", result: "testUrl" })); - sinon.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const result = await ui.selectFileOrInput(config); @@ -936,7 +937,6 @@ describe("UI Unit Tests", async () => { if (result.isOk()) { expect(result.value.result).to.equal("testUrl"); } - sinon.restore(); }); }); }); diff --git a/packages/vscode-extension/test/extension/telemetry.test.ts b/packages/vscode-extension/test/extension/telemetry.test.ts index ab57c5ea1d..0aae9845a3 100644 --- a/packages/vscode-extension/test/extension/telemetry.test.ts +++ b/packages/vscode-extension/test/extension/telemetry.test.ts @@ -7,6 +7,8 @@ import * as chai from "chai"; import * as spies from "chai-spies"; import { TelemetryReporter } from "@microsoft/teamsfx-api"; +import { VSCodeTelemetryReporter } from "../../src/commonlib/telemetry"; +import { getAllFeatureFlags } from "../../src/featureFlags"; chai.use(spies); const expect = chai.expect; @@ -42,9 +44,6 @@ mock("@vscode/extension-telemetry", { }, }); -import { VSCodeTelemetryReporter } from "../../src/commonlib/telemetry"; -import { getAllFeatureFlags } from "../../src/utils/commonUtils"; - const featureFlags = getAllFeatureFlags()?.join(";") ?? ""; describe("telemetry", () => { diff --git a/packages/vscode-extension/test/extension/treeview/treeViewManager.test.ts b/packages/vscode-extension/test/extension/treeview/treeViewManager.test.ts index ce614734b6..4aa891ef0b 100644 --- a/packages/vscode-extension/test/extension/treeview/treeViewManager.test.ts +++ b/packages/vscode-extension/test/extension/treeview/treeViewManager.test.ts @@ -59,7 +59,7 @@ describe("TreeViewManager", () => { subscriptions: [], } as unknown as vscode.ExtensionContext); const command = (treeViewManager as any).commandMap.get("fx-extension.create"); - const setStatusStub = sinon.stub(command, "setStatus"); + const setStatusStub = sandbox.stub(command, "setStatus"); treeViewManager.setRunningCommand("fx-extension.create", ["fx-extension.openSamples"]); chai.assert.equal(setStatusStub.callCount, 1); diff --git a/packages/vscode-extension/test/extension/uriHandler.test.ts b/packages/vscode-extension/test/extension/uriHandler.test.ts index 44b49988fb..bdfc13e233 100644 --- a/packages/vscode-extension/test/extension/uriHandler.test.ts +++ b/packages/vscode-extension/test/extension/uriHandler.test.ts @@ -5,11 +5,13 @@ import * as vscode from "vscode"; import { UriHandler, setUriEventHandler } from "../../src/uriHandler"; import { TelemetryTriggerFrom } from "../../src/telemetry/extTelemetryEvents"; +afterEach(() => { + sinon.restore(); +}); + describe("uri handler", () => { const sandbox = sinon.createSandbox(); - beforeEach(() => { - sandbox.restore(); - }); + afterEach(() => { sandbox.restore(); }); @@ -131,7 +133,5 @@ describe("uri handler", () => { it("set uri handler", async () => { const uriHandler = new UriHandler(); setUriEventHandler(uriHandler); - - sinon.restore(); }); }); diff --git a/packages/vscode-extension/test/extension/commonUtils.test.ts b/packages/vscode-extension/test/extension/utils/commonUtils.test.ts similarity index 78% rename from packages/vscode-extension/test/extension/commonUtils.test.ts rename to packages/vscode-extension/test/extension/utils/commonUtils.test.ts index 16f1cdae1f..350aac3631 100644 --- a/packages/vscode-extension/test/extension/commonUtils.test.ts +++ b/packages/vscode-extension/test/extension/utils/commonUtils.test.ts @@ -7,101 +7,53 @@ import * as vscode from "vscode"; import { Uri } from "vscode"; import { err, ok, UserError } from "@microsoft/teamsfx-api"; import { envUtil, metadataUtil, pathUtils } from "@microsoft/teamsfx-core"; -import * as extensionPackage from "../../package.json"; -import * as globalVariables from "../../src/globalVariables"; -import * as handlers from "../../src/handlers"; -import { TelemetryProperty, TelemetryTriggerFrom } from "../../src/telemetry/extTelemetryEvents"; -import * as commonUtils from "../../src/utils/commonUtils"; -import { MockCore } from "../mocks/mockCore"; +import * as globalVariables from "../../../src/globalVariables"; +import { TelemetryProperty, TelemetryTriggerFrom } from "../../../src/telemetry/extTelemetryEvents"; +import * as commonUtils from "../../../src/utils/commonUtils"; +import * as telemetryUtils from "../../../src/utils/telemetryUtils"; +import { MockCore } from "../../mocks/mockCore"; import * as coreUtils from "@microsoft/teamsfx-core/build/common/projectSettingsHelper"; import * as mockfs from "mock-fs"; describe("CommonUtils", () => { - describe("getPackageVersion", () => { - it("alpha version", () => { - const version = "1.1.1-alpha.4"; - - chai.expect(commonUtils.getPackageVersion(version)).equals("alpha"); - }); - - it("beta version", () => { - const version = "1.1.1-beta.2"; - - chai.expect(commonUtils.getPackageVersion(version)).equals("beta"); - }); - - it("rc version", () => { - const version = "1.0.0-rc.3"; - - chai.expect(commonUtils.getPackageVersion(version)).equals("rc"); - }); + afterEach(() => { + // Restore the default sandbox here + sinon.restore(); + }); - it("formal version", () => { - const version = "4.6.0"; + describe("openFolderInExplorer", () => { + const sandbox = sinon.createSandbox(); - chai.expect(commonUtils.getPackageVersion(version)).equals("formal"); + afterEach(() => { + sandbox.restore(); }); - }); - describe("openFolderInExplorer", () => { it("happy path", () => { const folderPath = "fakePath"; - sinon.stub(cp, "exec"); + sandbox.stub(cp, "exec"); commonUtils.openFolderInExplorer(folderPath); }); }); - describe("isFeatureFlag", () => { - it("return true when enabled", () => { - sinon.stub(extensionPackage, "featureFlag").value("true"); - - chai.expect(commonUtils.isFeatureFlag()).equals(true); - - sinon.restore(); - }); - - it("return false when disabled", () => { - sinon.stub(extensionPackage, "featureFlag").value("false"); - - chai.expect(commonUtils.isFeatureFlag()).equals(false); - - sinon.restore(); - }); - }); - - describe("sleep", () => { - it("sleep should be accurate", async () => { - const start = Date.now(); - - commonUtils.sleep(1000).then(() => { - const millis = Date.now() - start; - - chai.expect(millis).gte(1000); + describe("os assertion", () => { + const sandbox = sinon.createSandbox(); - chai.expect(millis).lte(1100); - }); + afterEach(() => { + sandbox.restore(); }); - }); - describe("os assertion", () => { it("should return exactly result according to os.type", async () => { - sinon.stub(os, "type").returns("Windows_NT"); - + sandbox.stub(os, "type").returns("Windows_NT"); chai.expect(commonUtils.isWindows()).equals(true); + sandbox.restore(); - sinon.restore(); - - sinon.stub(os, "type").returns("Linux"); - + sandbox.stub(os, "type").returns("Linux"); chai.expect(commonUtils.isLinux()).equals(true); + sandbox.restore(); - sinon.restore(); - - sinon.stub(os, "type").returns("Darwin"); - + sandbox.stub(os, "type").returns("Darwin"); chai.expect(commonUtils.isMacOS()).equals(true); - - sinon.restore(); + sandbox.restore(); }); }); @@ -110,7 +62,7 @@ describe("CommonUtils", () => { const core = new MockCore(); beforeEach(() => { - sandbox.stub(handlers, "core").value(core); + sandbox.stub(globalVariables, "core").value(core); }); afterEach(() => { @@ -120,24 +72,24 @@ describe("CommonUtils", () => { it("happy path", async () => { sandbox.stub(globalVariables, "workspaceUri").value(Uri.file(".")); sandbox.stub(core, "getProjectId").resolves(ok("mock-project-id")); - const result = await commonUtils.getProjectId(); + const result = await telemetryUtils.getProjectId(); chai.expect(result).equals("mock-project-id"); }); it("workspaceUri is undefined", async () => { sandbox.stub(globalVariables, "workspaceUri").value(undefined); - const result = await commonUtils.getProjectId(); + const result = await telemetryUtils.getProjectId(); chai.expect(result).equals(undefined); }); it("return error", async () => { sandbox.stub(globalVariables, "workspaceUri").value(Uri.file(".")); sandbox.stub(core, "getProjectId").resolves(err(new UserError({}))); - const result = await commonUtils.getProjectId(); + const result = await telemetryUtils.getProjectId(); chai.expect(result).equals(undefined); }); it("throw error", async () => { sandbox.stub(globalVariables, "workspaceUri").value(Uri.file(".")); sandbox.stub(core, "getProjectId").rejects(new UserError({})); - const result = await commonUtils.getProjectId(); + const result = await telemetryUtils.getProjectId(); chai.expect(result).equals(undefined); }); }); @@ -147,7 +99,7 @@ describe("CommonUtils", () => { const core = new MockCore(); beforeEach(() => { - sandbox.stub(handlers, "core").value(core); + sandbox.stub(globalVariables, "core").value(core); }); afterEach(() => { @@ -190,7 +142,7 @@ describe("CommonUtils", () => { const core = new MockCore(); beforeEach(() => { - sandbox.stub(handlers, "core").value(core); + sandbox.stub(globalVariables, "core").value(core); }); afterEach(() => { @@ -355,6 +307,50 @@ describe("CommonUtils", () => { chai.expect(result).equals(false); }); }); + + describe("getProvisionResultJson", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + it("returns undefined if no workspace Uri", async () => { + sandbox.stub(globalVariables, "workspaceUri").value(undefined); + const result = await commonUtils.getProvisionResultJson("test"); + chai.expect(result).equals(undefined); + }); + + it("returns undefined if is not TeamsFx project", async () => { + sandbox.stub(globalVariables, "workspaceUri").value(Uri.file("test")); + sandbox.stub(globalVariables, "isTeamsFxProject").value(false); + const result = await commonUtils.getProvisionResultJson("test"); + chai.expect(result).deep.equals(undefined); + }); + + it("returns undefined if provision output file does not exists", async () => { + sandbox.stub(globalVariables, "workspaceUri").value(Uri.file("test")); + sandbox.stub(globalVariables, "isTeamsFxProject").value(true); + sandbox.stub(fs, "pathExists").resolves(true); + sandbox.stub(fs, "existsSync").returns(false); + + const result = await commonUtils.getProvisionResultJson("test"); + chai.expect(result).equals(undefined); + }); + + it("returns provision output file result", async () => { + const expectedResult = { test: "test" }; + sandbox.stub(globalVariables, "workspaceUri").value(Uri.file("test")); + sandbox.stub(globalVariables, "isTeamsFxProject").value(true); + sandbox.stub(fs, "pathExists").resolves(true); + sandbox.stub(fs, "existsSync").returns(true); + sandbox.stub(fs, "readJSON").resolves(expectedResult); + + const result = await commonUtils.getProvisionResultJson("test"); + chai.expect(result).equals(expectedResult); + }); + }); + describe("hasAdaptiveCardInWorkspace()", () => { const sandbox = sinon.createSandbox(); @@ -427,50 +423,9 @@ describe("CommonUtils", () => { }); }); - describe("anonymizeFilePaths()", () => { - const sandbox = sinon.createSandbox(); - - afterEach(() => { - mockfs.restore(); - sandbox.restore(); - }); - - it("undefined", async () => { - const result = await commonUtils.anonymizeFilePaths(); - chai.assert.equal(result, ""); - }); - - it("happy path 1", async () => { - const result = await commonUtils.anonymizeFilePaths( - "at Object.require.extensions. [as .ts] (C:\\Users\\AppData\\Roaming\\npm\\node_modules\\ts-node\\src\\index.ts:1621:12)" - ); - chai.assert.equal( - result, - "at Object.require.extensions. [as .ts] (/index.ts:1621:12)" - ); - }); - it("happy path 2", async () => { - const result = await commonUtils.anonymizeFilePaths( - "at Object.require.extensions. [as .ts] (/user/test/index.ts:1621:12)" - ); - chai.assert.equal( - result, - "at Object.require.extensions. [as .ts] (/index.ts:1621:12)" - ); - }); - it("happy path 3", async () => { - const result = await commonUtils.anonymizeFilePaths( - "some user stack trace at (C:/fake_path/fake_file:1:1)" - ); - chai.assert.equal( - result, - "some user stack trace at (/fake_file:1:1)" - ); - }); - }); - describe("getLocalDebugMessageTemplate()", () => { const sandbox = sinon.createSandbox(); + afterEach(() => { sandbox.restore(); }); diff --git a/packages/vscode-extension/test/extension/utils/environmentUtils.test.ts b/packages/vscode-extension/test/extension/utils/environmentUtils.test.ts new file mode 100644 index 0000000000..bbc13085db --- /dev/null +++ b/packages/vscode-extension/test/extension/utils/environmentUtils.test.ts @@ -0,0 +1,78 @@ +import * as chai from "chai"; +import * as sinon from "sinon"; +import * as vscode from "vscode"; +import * as environmentUtils from "../../../src/utils/environmentUtils"; +import { Inputs, Platform, VsCodeEnv } from "@microsoft/teamsfx-api"; + +describe("EnvironmentUtils", () => { + afterEach(() => { + // Restore the default sandbox here + sinon.restore(); + }); + + describe("detectVsCodeEnv()", function () { + const sandbox = sinon.createSandbox(); + + this.afterEach(() => { + sandbox.restore(); + }); + + it("locally run", () => { + const expectedResult = { + extensionKind: vscode.ExtensionKind.UI, + id: "", + extensionUri: vscode.Uri.file(""), + extensionPath: "", + isActive: true, + packageJSON: {}, + exports: undefined, + activate: sandbox.spy(), + }; + const getExtension = sandbox + .stub(vscode.extensions, "getExtension") + .callsFake((name: string) => { + return expectedResult; + }); + + chai.expect(environmentUtils.detectVsCodeEnv()).equals(VsCodeEnv.local); + getExtension.restore(); + }); + + it("Remotely run", () => { + const expectedResult = { + extensionKind: vscode.ExtensionKind.Workspace, + id: "", + extensionUri: vscode.Uri.file(""), + extensionPath: "", + isActive: true, + packageJSON: {}, + exports: undefined, + activate: sandbox.spy(), + }; + const getExtension = sandbox + .stub(vscode.extensions, "getExtension") + .callsFake((name: string) => { + return expectedResult; + }); + + chai + .expect(environmentUtils.detectVsCodeEnv()) + .oneOf([VsCodeEnv.remote, VsCodeEnv.codespaceVsCode, VsCodeEnv.codespaceBrowser]); + getExtension.restore(); + }); + }); + + describe("getSystemInputs()", function () { + const sandbox = sinon.createSandbox(); + + this.afterEach(() => { + sandbox.restore(); + }); + + it("getSystemInputs()", () => { + const input: Inputs = environmentUtils.getSystemInputs(); + + chai.expect(input.platform).equals(Platform.VSCode); + }); + }); +}); diff --git a/packages/vscode-extension/test/extension/utils/fileSystemUtils.test.ts b/packages/vscode-extension/test/extension/utils/fileSystemUtils.test.ts new file mode 100644 index 0000000000..063ff72d66 --- /dev/null +++ b/packages/vscode-extension/test/extension/utils/fileSystemUtils.test.ts @@ -0,0 +1,48 @@ +import * as chai from "chai"; +import * as sinon from "sinon"; +import * as fileSystemUtils from "../../../src/utils/fileSystemUtils"; +import * as mockfs from "mock-fs"; + +describe("FileSystemUtils", () => { + describe("anonymizeFilePaths()", () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + mockfs.restore(); + sandbox.restore(); + }); + + it("undefined", async () => { + const result = await fileSystemUtils.anonymizeFilePaths(); + chai.assert.equal(result, ""); + }); + + it("happy path 1", async () => { + const result = await fileSystemUtils.anonymizeFilePaths( + "at Object.require.extensions. [as .ts] (C:\\Users\\AppData\\Roaming\\npm\\node_modules\\ts-node\\src\\index.ts:1621:12)" + ); + chai.assert.equal( + result, + "at Object.require.extensions. [as .ts] (/index.ts:1621:12)" + ); + }); + it("happy path 2", async () => { + const result = await fileSystemUtils.anonymizeFilePaths( + "at Object.require.extensions. [as .ts] (/user/test/index.ts:1621:12)" + ); + chai.assert.equal( + result, + "at Object.require.extensions. [as .ts] (/index.ts:1621:12)" + ); + }); + it("happy path 3", async () => { + const result = await fileSystemUtils.anonymizeFilePaths( + "some user stack trace at (C:/fake_path/fake_file:1:1)" + ); + chai.assert.equal( + result, + "some user stack trace at (/fake_file:1:1)" + ); + }); + }); +}); diff --git a/packages/vscode-extension/test/extension/utils/localizeUtils.test.ts b/packages/vscode-extension/test/extension/utils/localizeUtils.test.ts index dab6904cef..c7dd2fab68 100644 --- a/packages/vscode-extension/test/extension/utils/localizeUtils.test.ts +++ b/packages/vscode-extension/test/extension/utils/localizeUtils.test.ts @@ -9,22 +9,29 @@ import { parseLocale, } from "../../../src/utils/localizeUtils"; +afterEach(() => { + sinon.restore(); +}); + describe("localizeUtils", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { _resetCollections(); - sinon.restore(); + sandbox.restore(); }); + describe("loadLocalizedStrings", () => { it("should log error if no default string collection", () => { - sinon.stub(fs, "pathExistsSync").callsFake((directory: string) => { + sandbox.stub(fs, "pathExistsSync").callsFake((directory: string) => { if (directory.includes("package.nls.json")) { return false; } return true; }); - sinon.stub(fs, "readJsonSync").returns({}); - sinon.stub(globalVariables, "context").value({ extensionPath: "" }); - const vscodeLogStub = sinon.stub(VsCodeLogInstance, "error"); + sandbox.stub(fs, "readJsonSync").returns({}); + sandbox.stub(globalVariables, "context").value({ extensionPath: "" }); + const vscodeLogStub = sandbox.stub(VsCodeLogInstance, "error"); _resetCollections(); loadLocalizedStrings(); @@ -33,16 +40,16 @@ describe("localizeUtils", () => { }); it("should log error if no string file found for current locale", () => { - sinon.stub(process, "env").value({ VSCODE_NLS_CONFIG: '{ "locale": "zh-cn" }' }); - sinon.stub(fs, "pathExistsSync").callsFake((directory: string) => { + sandbox.stub(process, "env").value({ VSCODE_NLS_CONFIG: '{ "locale": "zh-cn" }' }); + sandbox.stub(fs, "pathExistsSync").callsFake((directory: string) => { if (directory.includes("package.nls.json")) { return true; } return false; }); - sinon.stub(fs, "readJsonSync").returns({}); - sinon.stub(globalVariables, "context").value({ extensionPath: "" }); - const vscodeLogStub = sinon.stub(VsCodeLogInstance, "error"); + sandbox.stub(fs, "readJsonSync").returns({}); + sandbox.stub(globalVariables, "context").value({ extensionPath: "" }); + const vscodeLogStub = sandbox.stub(VsCodeLogInstance, "error"); _resetCollections(); loadLocalizedStrings(); @@ -53,7 +60,7 @@ describe("localizeUtils", () => { describe("parseLocale", () => { it("should return current locale", () => { - sinon.stub(process, "env").value({ VSCODE_NLS_CONFIG: '{ "locale": "zh-cn" }' }); + sandbox.stub(process, "env").value({ VSCODE_NLS_CONFIG: '{ "locale": "zh-cn" }' }); const locale = parseLocale(); diff --git a/packages/vscode-extension/test/extension/utils/migrationUtils.test.ts b/packages/vscode-extension/test/extension/utils/migrationUtils.test.ts new file mode 100644 index 0000000000..60bab70f7d --- /dev/null +++ b/packages/vscode-extension/test/extension/utils/migrationUtils.test.ts @@ -0,0 +1,73 @@ +import * as chai from "chai"; +import * as sinon from "sinon"; +import { ExtensionContext } from "vscode"; +import * as migrationUtils from "../../../src/utils/migrationUtils"; +import * as environmentUtils from "../../../src/utils/environmentUtils"; +import * as globalVariables from "../../../src/globalVariables"; +import { Inputs, UserError, err, ok } from "@microsoft/teamsfx-api"; +import { MockCore } from "../../mocks/mockCore"; +import * as vsc_ui from "../../../src/qm/vsc_ui"; +import { VsCodeUI } from "../../../src/qm/vsc_ui"; + +describe("migrationUtils", () => { + const sandbox = sinon.createSandbox(); + + describe("triggerV3Migration", () => { + beforeEach(() => { + sandbox.stub(environmentUtils, "getSystemInputs").returns({ + locale: "en-us", + platform: "vsc", + projectPath: undefined, + vscodeEnv: "local", + } as Inputs); + sandbox.stub(globalVariables, "core").value(new MockCore()); + }); + + afterEach(async () => { + sandbox.restore(); + }); + + it("Stop debugging if phantomMigrationV3() returns error", async () => { + const error = new UserError( + "test source", + "test name", + "test message", + "test displayMessage" + ); + const phantomMigrationV3Stub = sandbox + .stub(globalVariables.core, "phantomMigrationV3") + .resolves(err(error)); + migrationUtils.triggerV3Migration().catch((e) => { + chai.assert.equal(e, error); + }); + chai.assert.isTrue( + phantomMigrationV3Stub.calledOnceWith({ + locale: "en-us", + platform: "vsc", + projectPath: undefined, + vscodeEnv: "local", + stage: "debug", + } as Inputs) + ); + }); + + it("Reload window if phantomMigrationV3() returns ok", async () => { + const phantomMigrationV3Stub = sandbox + .stub(globalVariables.core, "phantomMigrationV3") + .resolves(ok(undefined)); + sandbox.stub(vsc_ui, "VS_CODE_UI").value(new VsCodeUI({})); + const vscUIReloadStub = sandbox.stub(vsc_ui.VS_CODE_UI, "reload").resolves(); + await migrationUtils.triggerV3Migration(); + chai.assert.isTrue( + phantomMigrationV3Stub.calledOnceWith({ + locale: "en-us", + platform: "vsc", + projectPath: undefined, + vscodeEnv: "local", + stage: "debug", + } as Inputs) + ); + chai.assert.isTrue(vscUIReloadStub.calledOnce); + }); + }); +}); diff --git a/packages/vscode-extension/test/extension/utils/projectChecker.test.ts b/packages/vscode-extension/test/extension/utils/projectChecker.test.ts index 36d8c46fa9..65fc367f6f 100644 --- a/packages/vscode-extension/test/extension/utils/projectChecker.test.ts +++ b/packages/vscode-extension/test/extension/utils/projectChecker.test.ts @@ -2,12 +2,15 @@ import { UserError, err, ok } from "@microsoft/teamsfx-api"; import "mocha"; import * as sinon from "sinon"; import * as global from "../../../src/globalVariables"; -import * as handler from "../../../src/handlers"; import { checkProjectTypeAndSendTelemetry } from "../../../src/utils/projectChecker"; import { MockCore } from "../../mocks/mockCore"; import * as vscode from "vscode"; import { ExtTelemetry } from "../../../src/telemetry/extTelemetry"; +afterEach(() => { + sinon.restore(); +}); + describe("checkProjectTypeAndSendTelemetry", () => { const sandbox = sinon.createSandbox(); const core = new MockCore(); @@ -16,7 +19,7 @@ describe("checkProjectTypeAndSendTelemetry", () => { }); it("happy", async () => { sandbox.stub(global, "workspaceUri").value(vscode.Uri.file("./")); - sandbox.stub(handler, "core").value(core); + sandbox.stub(global, "core").value(core); sandbox.stub(core, "checkProjectType").resolves( ok({ isTeamsFx: true, @@ -30,7 +33,7 @@ describe("checkProjectTypeAndSendTelemetry", () => { }); it("error", async () => { sandbox.stub(global, "workspaceUri").value(vscode.Uri.file("./")); - sandbox.stub(handler, "core").value(core); + sandbox.stub(global, "core").value(core); sandbox.stub(core, "checkProjectType").resolves(err(new UserError({}))); await checkProjectTypeAndSendTelemetry(); }); diff --git a/packages/vscode-extension/test/extension/utils/releaseNote.test.ts b/packages/vscode-extension/test/extension/utils/releaseNote.test.ts index 353bb880a0..d8fd8e7bac 100644 --- a/packages/vscode-extension/test/extension/utils/releaseNote.test.ts +++ b/packages/vscode-extension/test/extension/utils/releaseNote.test.ts @@ -29,168 +29,176 @@ const reporterSpy = spy.interface({ ): void {}, }); const ShowWhatIsNewNotification = "show-what-is-new-notification"; - -describe("stable version shows changelog", () => { - const sandbox = sinon.createSandbox(); - let context: vscode.ExtensionContext; - let telemetryStub: sinon.SinonStub; - const mockGlobalState: vscode.Memento = { - keys: gloablStateKeys, - get: globalStateGet, - update: globalStateUpdate, - }; - beforeEach(() => { - context = { - subscriptions: [], - globalState: mockGlobalState, - } as unknown as vscode.ExtensionContext; - sandbox.stub(versionUtil, "getExtensionId").returns(""); - sandbox.stub(vscode.extensions, "getExtension").returns({ - packageJSON: { version: "5.0.0" }, - id: "", - extensionPath: "", - isActive: true, - exports: {}, - extensionKind: vscode.ExtensionKind.UI, - extensionUri: vscode.Uri.parse("https://www.test.com"), - activate(): Thenable { - return Promise.resolve(); - }, - }); - telemetryStub = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); - sinon.stub(globalVariables, "context").value({ extensionPath: "" }); - }); +describe("Release Note", () => { afterEach(() => { - sandbox.restore(); + sinon.restore(); }); - it("show changelog notification happy path", async () => { - const contextSpy = sandbox.spy(context.globalState, "update"); - sandbox.stub(context.globalState, "get").returns("4.99.0"); - let title = ""; - sandbox - .stub(vscode.window, "showInformationMessage") - .callsFake((_message: string, option: any, ...items: vscode.MessageItem[]) => { - title = option.title; - return Promise.resolve(option); + + describe("stable version shows changelog", () => { + const sandbox = sinon.createSandbox(); + let context: vscode.ExtensionContext; + let telemetryStub: sinon.SinonStub; + const mockGlobalState: vscode.Memento = { + keys: gloablStateKeys, + get: globalStateGet, + update: globalStateUpdate, + }; + beforeEach(() => { + context = { + subscriptions: [], + globalState: mockGlobalState, + } as unknown as vscode.ExtensionContext; + sandbox.stub(versionUtil, "getExtensionId").returns(""); + sandbox.stub(vscode.extensions, "getExtension").returns({ + packageJSON: { version: "5.0.0" }, + id: "", + extensionPath: "", + isActive: true, + exports: {}, + extensionKind: vscode.ExtensionKind.UI, + extensionUri: vscode.Uri.parse("https://www.test.com"), + activate(): Thenable { + return Promise.resolve(); + }, }); - const instance = new ReleaseNote(context); - await instance.show(); - chai.assert(title === "Changelog"); - chai.assert(contextSpy.callCount == 2); - chai.assert(telemetryStub.calledWith("show-what-is-new-notification")); - }); - it("should not show changelog if button is not clicked", async () => { - const contextSpy = sandbox.spy(context.globalState, "update"); - sandbox.stub(context.globalState, "get").returns("4.99.0"); - sandbox.stub(vscode.window, "showInformationMessage").resolves(undefined); - const instance = new ReleaseNote(context); - await instance.show(); - chai.assert(contextSpy.callCount == 2); - chai.assert(telemetryStub.calledOnce); - }); - it("should not show changelog when version is not changed", async () => { - const contextSpy = sandbox.spy(context.globalState, "update"); - sandbox.stub(context.globalState, "get").returns("5.0.0"); - sandbox.stub(vscode.window, "showInformationMessage").resolves(); - const instance = new ReleaseNote(context); - await instance.show(); - sinon.assert.notCalled(contextSpy); - chai.assert(telemetryStub.notCalled); + telemetryStub = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); + sandbox.stub(globalVariables, "context").value({ extensionPath: "" }); + }); + afterEach(() => { + sandbox.restore(); + }); + it("show changelog notification happy path", async () => { + const contextSpy = sandbox.spy(context.globalState, "update"); + sandbox.stub(context.globalState, "get").returns("4.99.0"); + let title = ""; + sandbox + .stub(vscode.window, "showInformationMessage") + .callsFake((_message: string, option: any, ...items: vscode.MessageItem[]) => { + title = option.title; + return Promise.resolve(option); + }); + const instance = new ReleaseNote(context); + await instance.show(); + chai.assert(title === "Changelog"); + chai.assert(contextSpy.callCount == 2); + chai.assert(telemetryStub.calledWith("show-what-is-new-notification")); + }); + it("should not show changelog if button is not clicked", async () => { + const contextSpy = sandbox.spy(context.globalState, "update"); + sandbox.stub(context.globalState, "get").returns("4.99.0"); + sandbox.stub(vscode.window, "showInformationMessage").resolves(undefined); + const instance = new ReleaseNote(context); + await instance.show(); + chai.assert(contextSpy.callCount == 2); + chai.assert(telemetryStub.calledOnce); + }); + it("should not show changelog when version is not changed", async () => { + const contextSpy = sandbox.spy(context.globalState, "update"); + sandbox.stub(context.globalState, "get").returns("5.0.0"); + sandbox.stub(vscode.window, "showInformationMessage").resolves(); + const instance = new ReleaseNote(context); + await instance.show(); + sinon.assert.notCalled(contextSpy); + chai.assert(telemetryStub.notCalled); + }); }); -}); -describe("prerelease version shows prerelease note", () => { - const sandbox = sinon.createSandbox(); - let context: ExtensionContext; - const mockGlobalState: vscode.Memento = { - keys: gloablStateKeys, - get: globalStateGet, - update: globalStateUpdate, - }; - before(() => { - chai.util.addProperty(ExtTelemetry, "reporter", () => reporterSpy); - }); - beforeEach(() => { - sandbox.restore(); - sandbox.stub(vscode.workspace, "openTextDocument").resolves(); - sandbox.stub(vscode.commands, "executeCommand").resolves(); - context = { - subscriptions: [], - globalState: mockGlobalState, - } as unknown as ExtensionContext; - }); - afterEach(() => { - sandbox.restore(); - }); - it("success", async () => { - sandbox.stub(vscode.extensions, "getExtension").returns({ - packageJSON: { version: "5.1.2023072000" }, - id: "", - extensionPath: "", - isActive: true, - exports: {}, - extensionKind: vscode.ExtensionKind.UI, - extensionUri: vscode.Uri.parse("https://www.test.com"), - activate(): Thenable { - return Promise.resolve(); - }, + describe("prerelease version shows prerelease note", () => { + const sandbox = sinon.createSandbox(); + let context: ExtensionContext; + const mockGlobalState: vscode.Memento = { + keys: gloablStateKeys, + get: globalStateGet, + update: globalStateUpdate, + }; + before(() => { + chai.util.addProperty(ExtTelemetry, "reporter", () => reporterSpy); }); - sandbox.stub(context.globalState, "get").returns("5.0.1"); - const instance = new ReleaseNote(context); - const spyChecker = sandbox.spy(context.globalState, "update"); - await instance.show(); - chai.assert(spyChecker.callCount == 1); - chai.expect(reporterSpy.sendTelemetryEvent).to.have.been.called.with(ShowWhatIsNewNotification); - spyChecker.restore(); - }); - it("returns prerelease version undefined", async () => { - sandbox.stub(vscode.extensions, "getExtension").returns({ - packageJSON: { version: "5.1.2023072000" }, - id: "", - extensionPath: "", - isActive: true, - exports: {}, - extensionKind: vscode.ExtensionKind.UI, - extensionUri: vscode.Uri.parse("https://www.test.com"), - activate(): Thenable { - return Promise.resolve(); - }, + beforeEach(() => { + sandbox.stub(vscode.workspace, "openTextDocument").resolves(); + sandbox.stub(vscode.commands, "executeCommand").resolves(); + context = { + subscriptions: [], + globalState: mockGlobalState, + } as unknown as ExtensionContext; }); - sandbox.stub(context.globalState, "get").returns(undefined); - const instance = new ReleaseNote(context); - const spyChecker = sandbox.spy(context.globalState, "update"); - chai.expect(reporterSpy.sendTelemetryEvent).to.have.been.called.with(ShowWhatIsNewNotification); - await instance.show(); - chai.assert(spyChecker.callCount == 1); - spyChecker.restore(); - }); - it("has same version", async () => { - sandbox.stub(vscode.extensions, "getExtension").returns({ - packageJSON: { version: "5.1.2023072000" }, - id: "", - extensionPath: "", - isActive: true, - exports: {}, - extensionKind: vscode.ExtensionKind.UI, - extensionUri: vscode.Uri.parse("https://www.test.com"), - activate(): Thenable { - return Promise.resolve(); - }, + afterEach(() => { + sandbox.restore(); + }); + it("success", async () => { + sandbox.stub(vscode.extensions, "getExtension").returns({ + packageJSON: { version: "5.1.2023072000" }, + id: "", + extensionPath: "", + isActive: true, + exports: {}, + extensionKind: vscode.ExtensionKind.UI, + extensionUri: vscode.Uri.parse("https://www.test.com"), + activate(): Thenable { + return Promise.resolve(); + }, + }); + sandbox.stub(context.globalState, "get").returns("5.0.1"); + const instance = new ReleaseNote(context); + const spyChecker = sandbox.spy(context.globalState, "update"); + await instance.show(); + chai.assert(spyChecker.callCount == 1); + chai + .expect(reporterSpy.sendTelemetryEvent) + .to.have.been.called.with(ShowWhatIsNewNotification); + spyChecker.restore(); + }); + it("returns prerelease version undefined", async () => { + sandbox.stub(vscode.extensions, "getExtension").returns({ + packageJSON: { version: "5.1.2023072000" }, + id: "", + extensionPath: "", + isActive: true, + exports: {}, + extensionKind: vscode.ExtensionKind.UI, + extensionUri: vscode.Uri.parse("https://www.test.com"), + activate(): Thenable { + return Promise.resolve(); + }, + }); + sandbox.stub(context.globalState, "get").returns(undefined); + const instance = new ReleaseNote(context); + const spyChecker = sandbox.spy(context.globalState, "update"); + chai + .expect(reporterSpy.sendTelemetryEvent) + .to.have.been.called.with(ShowWhatIsNewNotification); + await instance.show(); + chai.assert(spyChecker.callCount == 1); + spyChecker.restore(); + }); + it("has same version", async () => { + sandbox.stub(vscode.extensions, "getExtension").returns({ + packageJSON: { version: "5.1.2023072000" }, + id: "", + extensionPath: "", + isActive: true, + exports: {}, + extensionKind: vscode.ExtensionKind.UI, + extensionUri: vscode.Uri.parse("https://www.test.com"), + activate(): Thenable { + return Promise.resolve(); + }, + }); + sandbox.stub(context.globalState, "get").returns("5.1.2023072000"); + const instance = new ReleaseNote(context); + const spyChecker = sandbox.spy(context.globalState, "update"); + await instance.show(); + chai.assert(spyChecker.callCount == 0); + spyChecker.restore(); + }); + it("has undefined version", async () => { + sandbox.stub(vscode.extensions, "getExtension").returns(undefined); + sandbox.stub(context.globalState, "get").returns("5.0.0"); + const instance = new ReleaseNote(context); + const spyChecker = sandbox.spy(context.globalState, "update"); + await instance.show(); + chai.assert(spyChecker.callCount == 0); + spyChecker.restore(); }); - sandbox.stub(context.globalState, "get").returns("5.1.2023072000"); - const instance = new ReleaseNote(context); - const spyChecker = sandbox.spy(context.globalState, "update"); - await instance.show(); - chai.assert(spyChecker.callCount == 0); - spyChecker.restore(); - }); - it("has undefined version", async () => { - sandbox.stub(vscode.extensions, "getExtension").returns(undefined); - sandbox.stub(context.globalState, "get").returns("5.0.0"); - const instance = new ReleaseNote(context); - const spyChecker = sandbox.spy(context.globalState, "update"); - await instance.show(); - chai.assert(spyChecker.callCount == 0); - spyChecker.restore(); }); }); diff --git a/packages/vscode-extension/test/extension/utils/telemetryUtils.test.ts b/packages/vscode-extension/test/extension/utils/telemetryUtils.test.ts new file mode 100644 index 0000000000..2af271d56b --- /dev/null +++ b/packages/vscode-extension/test/extension/utils/telemetryUtils.test.ts @@ -0,0 +1,76 @@ +import * as chai from "chai"; +import * as sinon from "sinon"; +import { Uri } from "vscode"; +import { err, ok, UserError } from "@microsoft/teamsfx-api"; +import * as globalVariables from "../../../src/globalVariables"; +import * as telemetryUtils from "../../../src/utils/telemetryUtils"; +import { MockCore } from "../../mocks/mockCore"; + +describe("TelemetryUtils", () => { + afterEach(() => { + sinon.restore(); + }); + + describe("getPackageVersion", () => { + it("alpha version", () => { + const version = "1.1.1-alpha.4"; + + chai.expect(telemetryUtils.getPackageVersion(version)).equals("alpha"); + }); + + it("beta version", () => { + const version = "1.1.1-beta.2"; + + chai.expect(telemetryUtils.getPackageVersion(version)).equals("beta"); + }); + + it("rc version", () => { + const version = "1.0.0-rc.3"; + + chai.expect(telemetryUtils.getPackageVersion(version)).equals("rc"); + }); + + it("formal version", () => { + const version = "4.6.0"; + + chai.expect(telemetryUtils.getPackageVersion(version)).equals("formal"); + }); + }); + + describe("getProjectId", async () => { + const sandbox = sinon.createSandbox(); + const core = new MockCore(); + + beforeEach(() => { + sandbox.stub(globalVariables, "core").value(core); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("happy path", async () => { + sandbox.stub(globalVariables, "workspaceUri").value(Uri.file(".")); + sandbox.stub(core, "getProjectId").resolves(ok("mock-project-id")); + const result = await telemetryUtils.getProjectId(); + chai.expect(result).equals("mock-project-id"); + }); + it("workspaceUri is undefined", async () => { + sandbox.stub(globalVariables, "workspaceUri").value(undefined); + const result = await telemetryUtils.getProjectId(); + chai.expect(result).equals(undefined); + }); + it("return error", async () => { + sandbox.stub(globalVariables, "workspaceUri").value(Uri.file(".")); + sandbox.stub(core, "getProjectId").resolves(err(new UserError({}))); + const result = await telemetryUtils.getProjectId(); + chai.expect(result).equals(undefined); + }); + it("throw error", async () => { + sandbox.stub(globalVariables, "workspaceUri").value(Uri.file(".")); + sandbox.stub(core, "getProjectId").rejects(new UserError({})); + const result = await telemetryUtils.getProjectId(); + chai.expect(result).equals(undefined); + }); + }); +}); diff --git a/packages/vscode-extension/test/handlers/walkthrough.test.ts b/packages/vscode-extension/test/handlers/walkthrough.test.ts index 2f769e1b0c..1df6baaad5 100644 --- a/packages/vscode-extension/test/handlers/walkthrough.test.ts +++ b/packages/vscode-extension/test/handlers/walkthrough.test.ts @@ -1,4 +1,5 @@ import * as handlers from "../../src/handlers"; +import * as environmentUtils from "../../src/utils/environmentUtils"; import { ExtTelemetry } from "../../src/telemetry/extTelemetry"; import { createProjectFromWalkthroughHandler } from "../../src/handlers/walkthrough"; @@ -9,8 +10,6 @@ import { Inputs, ok } from "@microsoft/teamsfx-api"; describe("walkthrough", () => { const sandbox = sinon.createSandbox(); - beforeEach(() => {}); - afterEach(() => { sandbox.restore(); }); @@ -19,7 +18,7 @@ describe("walkthrough", () => { const sendTelemetryEventStub = sandbox.stub(ExtTelemetry, "sendTelemetryEvent"); const inputs = {} as Inputs; - const systemInputsStub = sandbox.stub(handlers, "getSystemInputs").callsFake(() => { + const systemInputsStub = sandbox.stub(environmentUtils, "getSystemInputs").callsFake(() => { return inputs; }); //const systemInputsStub = sandbox.stub(handlers, "getSystemInputs").returns({} as Inputs); diff --git a/packages/vscode-extension/test/localdebug/devTunnelTaskTerminal.test.ts b/packages/vscode-extension/test/localdebug/devTunnelTaskTerminal.test.ts index 50a7f406b9..1089d8362b 100644 --- a/packages/vscode-extension/test/localdebug/devTunnelTaskTerminal.test.ts +++ b/packages/vscode-extension/test/localdebug/devTunnelTaskTerminal.test.ts @@ -19,8 +19,7 @@ import { ManagementApiVersions, } from "@microsoft/dev-tunnels-management"; import { FxError, ok, Result, UserError } from "@microsoft/teamsfx-api"; -import { envUtil } from "@microsoft/teamsfx-core"; -import { pathUtils } from "@microsoft/teamsfx-core"; +import { envUtil, pathUtils } from "@microsoft/teamsfx-core"; import VsCodeLogInstance from "../../src/commonlib/log"; import { localTelemetryReporter } from "../../src/debug/localTelemetryReporter"; @@ -36,7 +35,6 @@ import { DevTunnelManager } from "../../src/debug/taskTerminal/utils/devTunnelMa import { ExtensionErrors, ExtensionSource } from "../../src/error"; import * as globalVariables from "../../src/globalVariables"; -import { tools } from "../../src/handlers"; chai.use(chaiAsPromised); @@ -73,6 +71,10 @@ class TestDevTunnelTaskTerminal extends DevTunnelTaskTerminal { describe("devTunnelTaskTerminal", () => { const baseDir = path.resolve(__dirname, "data", "devTunnelTaskTerminal"); + afterEach(() => { + sinon.restore(); + }); + describe("do", () => { const sandbox = sinon.createSandbox(); let filePath: string | undefined = undefined; @@ -114,7 +116,7 @@ describe("devTunnelTaskTerminal", () => { const mockTunnelArray: Tunnel[] = initTunnel; sandbox.stub(process, "env").value({ TEAMSFX_DEV_TUNNEL_TEST: "true" }); sandbox - .stub(tools.tokenProvider.m365TokenProvider, "getAccessToken") + .stub(globalVariables.tools.tokenProvider.m365TokenProvider, "getAccessToken") .resolves(ok("test-token")); sandbox.stub(TunnelManagementHttpClient.prototype, "getTunnel").callsFake(async (t) => { return ( @@ -593,7 +595,7 @@ describe("devTunnelTaskTerminal", () => { }, }, ]); - sandbox.assert.calledWith(writeEnvStub, sinon.match.any, "local", { + sandbox.assert.calledWith(writeEnvStub, sandbox.match.any, "local", { BOT_ENDPOINT: "https://id-port.cluster.devtunnels.ms", BOT_DOMAIN: "id-port.cluster.devtunnels.ms", }); @@ -625,7 +627,7 @@ describe("devTunnelTaskTerminal", () => { writeToEnvironmentFile: {}, }, ]); - sandbox.assert.calledWith(writeEnvStub, sinon.match.any, "local", { + sandbox.assert.calledWith(writeEnvStub, sandbox.match.any, "local", { BOT_DOMAIN: "id-3978.cluster.devtunnels.ms", TAB_ENDPOINT: "https://id-53000.cluster.devtunnels.ms", }); diff --git a/packages/vscode-extension/test/localdebug/localTelemetryReporter.test.ts b/packages/vscode-extension/test/localdebug/localTelemetryReporter.test.ts index b1d3f6d5bf..6fd2c35b68 100644 --- a/packages/vscode-extension/test/localdebug/localTelemetryReporter.test.ts +++ b/packages/vscode-extension/test/localdebug/localTelemetryReporter.test.ts @@ -92,23 +92,25 @@ describe("LocalTelemetryReporter", () => { }); describe("getTaskInfo()", () => { + const sandbox = sinon.createSandbox(); + afterEach(async () => { - sinon.restore(); + sandbox.restore(); }); it("Failed to get task.json", async () => { - sinon.stub(globalVariables, "isTeamsFxProject").value(true); - sinon + sandbox.stub(globalVariables, "isTeamsFxProject").value(true); + sandbox .stub(globalVariables, "workspaceUri") .value(vscode.Uri.parse(path.resolve(__dirname, "unknown"))); - sinon.stub(LocalEnvManager.prototype, "getTaskJson").returns(Promise.resolve(undefined)); + sandbox.stub(LocalEnvManager.prototype, "getTaskJson").returns(Promise.resolve(undefined)); const res = await getTaskInfo(); chai.assert.isUndefined(res); }); it("Failed to get renamed label", async () => { - sinon.stub(globalVariables, "isTeamsFxProject").value(true); - sinon + sandbox.stub(globalVariables, "isTeamsFxProject").value(true); + sandbox .stub(globalVariables, "workspaceUri") .value(vscode.Uri.parse(path.resolve(__dirname, "data", "renameLabel"))); const res = await getTaskInfo(); @@ -117,8 +119,8 @@ describe("LocalTelemetryReporter", () => { }); it("task.json of old tab project", async () => { - sinon.stub(globalVariables, "isTeamsFxProject").value(true); - sinon + sandbox.stub(globalVariables, "isTeamsFxProject").value(true); + sandbox .stub(globalVariables, "workspaceUri") .value(vscode.Uri.parse(path.resolve(__dirname, "data", "oldTab"))); const res = await getTaskInfo(); @@ -147,8 +149,8 @@ describe("LocalTelemetryReporter", () => { }); it("task.json of a tab + bot + func project", async () => { - sinon.stub(globalVariables, "isTeamsFxProject").value(true); - sinon + sandbox.stub(globalVariables, "isTeamsFxProject").value(true); + sandbox .stub(globalVariables, "workspaceUri") .value(vscode.Uri.parse(path.resolve(__dirname, "data", "tabbotfunc"))); const res = await getTaskInfo(); @@ -203,8 +205,8 @@ describe("LocalTelemetryReporter", () => { }); it("task.json of a m365 project", async () => { - sinon.stub(globalVariables, "isTeamsFxProject").value(true); - sinon + sandbox.stub(globalVariables, "isTeamsFxProject").value(true); + sandbox .stub(globalVariables, "workspaceUri") .value(vscode.Uri.parse(path.resolve(__dirname, "data", "m365"))); const res = await getTaskInfo(); @@ -288,8 +290,8 @@ describe("LocalTelemetryReporter", () => { ); }); it("task.json of user customized project", async () => { - sinon.stub(globalVariables, "isTeamsFxProject").value(true); - sinon + sandbox.stub(globalVariables, "isTeamsFxProject").value(true); + sandbox .stub(globalVariables, "workspaceUri") .value(vscode.Uri.parse(path.resolve(__dirname, "data", "customized"))); const res = await getTaskInfo(); diff --git a/packages/vscode-extension/test/localdebug/progressHelper.test.ts b/packages/vscode-extension/test/localdebug/progressHelper.test.ts index 8156d13ca3..6519895ab3 100644 --- a/packages/vscode-extension/test/localdebug/progressHelper.test.ts +++ b/packages/vscode-extension/test/localdebug/progressHelper.test.ts @@ -6,10 +6,17 @@ import * as chai from "chai"; import { ProgressHelper } from "../../src/debug/progressHelper"; import { ProgressHandler } from "../../src/progressHandler"; +afterEach(() => { + // Restore the default sandbox here + sinon.restore(); +}); + describe("[debug > ProgressHelper]", () => { describe("ParallelProgressHelper", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { - sinon.restore(); + sandbox.restore(); }); const testData = [ @@ -90,9 +97,10 @@ describe("[debug > ProgressHelper]", () => { expected: ["test1"], }, ]; + testData.forEach((data) => { it(data.name, async () => { - const mockProgressHandler = sinon.createSandbox().createStubInstance(ProgressHandler); + const mockProgressHandler = sandbox.createStubInstance(ProgressHandler); const testProgressHelper = new ProgressHelper(mockProgressHandler); await testProgressHelper.start(data.input); for (const callMessage of data.calledMessage) { @@ -100,6 +108,7 @@ describe("[debug > ProgressHelper]", () => { } const called = mockProgressHandler.next.getCalls().map(({ args }) => args[0]); chai.assert.deepEqual(called, data.expected); + sandbox.restore(); }); }); }); diff --git a/packages/vscode-extension/test/officeChat/commands/nextstep/officeNextstepCommandHelper.test.ts b/packages/vscode-extension/test/officeChat/commands/nextstep/officeNextstepCommandHelper.test.ts index ea0265e003..a22ff8feeb 100644 --- a/packages/vscode-extension/test/officeChat/commands/nextstep/officeNextstepCommandHelper.test.ts +++ b/packages/vscode-extension/test/officeChat/commands/nextstep/officeNextstepCommandHelper.test.ts @@ -15,6 +15,11 @@ import * as officeSteps from "../../../../src/officeChat/commands/nextStep/offic import { CHAT_EXECUTE_COMMAND_ID, CHAT_OPENURL_COMMAND_ID } from "../../../../src/chat/consts"; import { OfficeWholeStatus } from "../../../../src/officeChat/commands/nextStep/types"; +afterEach(() => { + // Restore the default sandbox here + sinon.restore(); +}); + describe("office steps: officeNextStepCommandHandler", () => { const sandbox = sinon.createSandbox(); @@ -33,7 +38,6 @@ describe("office steps: officeNextStepCommandHandler", () => { afterEach(() => { sandbox.restore(); - sinon.restore(); }); it("prompt is unempty", async () => { diff --git a/packages/vscode-extension/test/officeChat/handlers.test.ts b/packages/vscode-extension/test/officeChat/handlers.test.ts index c49f1431d4..6519fc9734 100644 --- a/packages/vscode-extension/test/officeChat/handlers.test.ts +++ b/packages/vscode-extension/test/officeChat/handlers.test.ts @@ -25,9 +25,8 @@ import { Correlator } from "@microsoft/teamsfx-core"; chai.use(chaipromised); describe("File: officeChat/handlers.ts", () => { - const sandbox = sinon.createSandbox(); - describe("Method: officeChatRequestHandler", () => { + const sandbox = sinon.createSandbox(); const response = { markdown: sandbox.stub(), button: sandbox.stub(), @@ -165,6 +164,7 @@ Usage: @office Ask questions about Office Add-ins development.`); }); describe("method: chatCreateOfficeProjectCommandHandler", () => { + const sandbox = sinon.createSandbox(); afterEach(async () => { sandbox.restore(); }); @@ -318,6 +318,8 @@ Usage: @office Ask questions about Office Add-ins development.`); }); describe("Method: handleOfficeFeedback", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { sandbox.restore(); });