diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts index 3aa249ea8923..f8387e73d429 100644 --- a/src/client/application/diagnostics/checks/envPathVariable.ts +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -17,7 +17,7 @@ import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '. import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing characters (\';\', \'"\', \'%\' or \';;\').' + - ' The existence of such characters are known to have caused the {1} extension not load.'; + ' The existence of such characters are known to have caused the {1} extension to not load.'; export class InvalidEnvironmentPathVariableDiagnostic extends BaseDiagnostic { constructor(message) { diff --git a/src/client/debugger/banner.ts b/src/client/debugger/banner.ts index 3062ed83cf87..5d45677749c6 100644 --- a/src/client/debugger/banner.ts +++ b/src/client/debugger/banner.ts @@ -22,7 +22,7 @@ export enum PersistentStateKeys { @injectable() export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { private initialized?: boolean; - private disabled?: boolean; + private disabledInCurrentSession?: boolean; public get enabled(): boolean { const factory = this.serviceContainer.get(IPersistentStateFactory); return factory.createGlobalPersistentState(PersistentStateKeys.ShowBanner, true).value; @@ -39,10 +39,10 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { return; } const debuggerService = this.serviceContainer.get(IDebugService); - const disposable = debuggerService.onDidStartDebugSession(e => { + const disposable = debuggerService.onDidStartDebugSession(async e => { if (e.type === ExperimentalDebuggerType) { const logger = this.serviceContainer.get(ILogger); - this.onDebugSessionStarted() + await this.onDebugSessionStarted() .catch(ex => logger.logError('Error in debugger Banner', ex)); } }); @@ -51,9 +51,9 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { } public async showBanner(): Promise { const appShell = this.serviceContainer.get(IApplicationShell); - const yes = 'Take Survey'; + const yes = 'Yes, take survey now'; const no = 'No thanks'; - const response = await appShell.showInformationMessage('Can you take 2 minutes to tell us how the Experimental Debugger is working for you?', yes, no); + const response = await appShell.showInformationMessage('Can you please take 2 minutes to tell us how the Experimental Debugger is working for you?', yes, no); switch (response) { case yes: { @@ -66,12 +66,13 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { break; } default: { - return; + // Disable for the current session. + this.disabledInCurrentSession = true; } } } public async shouldShowBanner(): Promise { - if (!this.enabled) { + if (!this.enabled || this.disabledInCurrentSession) { return false; } const [threshold, debuggerCounter] = await Promise.all([this.getDebuggerLaunchThresholdCounter(), this.getGetDebuggerLaunchCounter()]); @@ -81,7 +82,6 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { public async disable(): Promise { const factory = this.serviceContainer.get(IPersistentStateFactory); await factory.createGlobalPersistentState(PersistentStateKeys.ShowBanner, false).updateValue(false); - this.disabled = true; } public async launchSurvey(): Promise { const debuggerLaunchCounter = await this.getGetDebuggerLaunchCounter(); @@ -115,7 +115,7 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { return isNaN(num) ? crypto.randomBytes(1).toString('hex').slice(-1) : lastHexValue; } private async onDebugSessionStarted(): Promise { - if (this.disabled) { + if (!this.enabled) { return; } await this.incrementDebuggerLaunchCounter(); diff --git a/src/test/debugger/banner.unit.test.ts b/src/test/debugger/banner.unit.test.ts index f2c13cf21bd5..886d2ff16eba 100644 --- a/src/test/debugger/banner.unit.test.ts +++ b/src/test/debugger/banner.unit.test.ts @@ -8,7 +8,7 @@ import { expect } from 'chai'; import * as typemoq from 'typemoq'; import { DebugSession } from 'vscode'; -import { IDebugService } from '../../client/common/application/types'; +import { IApplicationShell, IDebugService } from '../../client/common/application/types'; import { IBrowserService, IDisposableRegistry, ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; import { ExperimentalDebuggerBanner, PersistentStateKeys } from '../../client/debugger/banner'; import { ExperimentalDebuggerType } from '../../client/debugger/Common/constants'; @@ -22,7 +22,12 @@ suite('Debugging - Banner', () => { let launchThresholdCounterState: typemoq.IMock>; let showBannerState: typemoq.IMock>; let debugService: typemoq.IMock; + let appShell: typemoq.IMock; let banner: IExperimentalDebuggerBanner; + const message = 'Can you please take 2 minutes to tell us how the Experimental Debugger is working for you?'; + const yes = 'Yes, take survey now'; + const no = 'No thanks'; + setup(() => { serviceContainer = typemoq.Mock.ofType(); browser = typemoq.Mock.ofType(); @@ -31,6 +36,7 @@ suite('Debugging - Banner', () => { launchCounterState = typemoq.Mock.ofType>(); showBannerState = typemoq.Mock.ofType>(); + appShell = typemoq.Mock.ofType(); launchThresholdCounterState = typemoq.Mock.ofType>(); const factory = typemoq.Mock.ofType(); factory @@ -48,6 +54,7 @@ suite('Debugging - Banner', () => { serviceContainer.setup(s => s.get(typemoq.It.isValue(IDebugService))).returns(() => debugService.object); serviceContainer.setup(s => s.get(typemoq.It.isValue(ILogger))).returns(() => logger.object); serviceContainer.setup(s => s.get(typemoq.It.isValue(IDisposableRegistry))).returns(() => []); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IApplicationShell))).returns(() => appShell.object); banner = new ExperimentalDebuggerBanner(serviceContainer.object); }); @@ -63,28 +70,28 @@ suite('Debugging - Banner', () => { browser.verifyAll(); }); test('Increment Debugger Launch Counter when debug session starts', async () => { - let onDidStartDebugSessionCb: (e: DebugSession) => void; + let onDidStartDebugSessionCb: (e: DebugSession) => Promise; debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) .callback(cb => onDidStartDebugSessionCb = cb) .verifiable(typemoq.Times.once()); const debuggerLaunchCounter = 1234; launchCounterState.setup(l => l.value).returns(() => debuggerLaunchCounter) - .verifiable(typemoq.Times.once()); + .verifiable(typemoq.Times.atLeastOnce()); launchCounterState.setup(l => l.updateValue(typemoq.It.isValue(debuggerLaunchCounter + 1))) .verifiable(typemoq.Times.once()); showBannerState.setup(s => s.value).returns(() => true) - .verifiable(typemoq.Times.once()); + .verifiable(typemoq.Times.atLeastOnce()); banner.initialize(); - onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); launchCounterState.verifyAll(); browser.verifyAll(); debugService.verifyAll(); showBannerState.verifyAll(); }); - test('Do not Increment Debugger Launch Counter when debug session starts when Banner is disabled', async () => { + test('Do not Increment Debugger Launch Counter when debug session starts and Banner is disabled', async () => { debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) .verifiable(typemoq.Times.never()); @@ -103,7 +110,7 @@ suite('Debugging - Banner', () => { debugService.verifyAll(); showBannerState.verifyAll(); }); - test('shouldShowBanner returnes false when Banner is disabled', async () => { + test('shouldShowBanner must return false when Banner is disabled', async () => { showBannerState.setup(s => s.value).returns(() => false) .verifiable(typemoq.Times.once()); @@ -111,7 +118,7 @@ suite('Debugging - Banner', () => { showBannerState.verifyAll(); }); - test('shouldShowBanner returnes false when Banner is enabled and debug counter is not same as threshold', async () => { + test('shouldShowBanner must return false when Banner is enabled and debug counter is not same as threshold', async () => { showBannerState.setup(s => s.value).returns(() => true) .verifiable(typemoq.Times.once()); launchCounterState.setup(l => l.value).returns(() => 1) @@ -125,7 +132,7 @@ suite('Debugging - Banner', () => { launchCounterState.verifyAll(); launchThresholdCounterState.verifyAll(); }); - test('shouldShowBanner returnes true when Banner is enabled and debug counter is same as threshold', async () => { + test('shouldShowBanner must return true when Banner is enabled and debug counter is same as threshold', async () => { showBannerState.setup(s => s.value).returns(() => true) .verifiable(typemoq.Times.once()); launchCounterState.setup(l => l.value).returns(() => 10) @@ -139,7 +146,65 @@ suite('Debugging - Banner', () => { launchCounterState.verifyAll(); launchThresholdCounterState.verifyAll(); }); - test('Disabling banner should store value of \'false\' in global store', async () => { + test('showBanner must be invoked when shouldShowBanner returns true', async () => { + let onDidStartDebugSessionCb: (e: DebugSession) => Promise; + const currentLaunchCounter = 50; + + debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) + .callback(cb => onDidStartDebugSessionCb = cb) + .verifiable(typemoq.Times.atLeastOnce()); + showBannerState.setup(s => s.value).returns(() => true) + .verifiable(typemoq.Times.atLeastOnce()); + launchCounterState.setup(l => l.value).returns(() => currentLaunchCounter) + .verifiable(typemoq.Times.atLeastOnce()); + launchThresholdCounterState.setup(t => t.value).returns(() => 10) + .verifiable(typemoq.Times.atLeastOnce()); + launchCounterState.setup(l => l.updateValue(typemoq.It.isValue(currentLaunchCounter + 1))) + .returns(() => Promise.resolve()) + .verifiable(typemoq.Times.atLeastOnce()); + + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(message), typemoq.It.isValue(yes), typemoq.It.isValue(no))) + .verifiable(typemoq.Times.once()); + banner.initialize(); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + + appShell.verifyAll(); + showBannerState.verifyAll(); + launchCounterState.verifyAll(); + launchThresholdCounterState.verifyAll(); + }); + test('showBanner must not be invoked the second time after dismissing the message', async () => { + let onDidStartDebugSessionCb: (e: DebugSession) => Promise; + let currentLaunchCounter = 50; + + debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) + .callback(cb => onDidStartDebugSessionCb = cb) + .verifiable(typemoq.Times.atLeastOnce()); + showBannerState.setup(s => s.value).returns(() => true) + .verifiable(typemoq.Times.atLeastOnce()); + launchCounterState.setup(l => l.value).returns(() => currentLaunchCounter) + .verifiable(typemoq.Times.atLeastOnce()); + launchThresholdCounterState.setup(t => t.value).returns(() => 10) + .verifiable(typemoq.Times.atLeastOnce()); + launchCounterState.setup(l => l.updateValue(typemoq.It.isAny())) + .callback(() => currentLaunchCounter = currentLaunchCounter + 1); + + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(message), typemoq.It.isValue(yes), typemoq.It.isValue(no))) + .returns(() => Promise.resolve(undefined)) + .verifiable(typemoq.Times.once()); + banner.initialize(); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + + appShell.verifyAll(); + showBannerState.verifyAll(); + launchCounterState.verifyAll(); + launchThresholdCounterState.verifyAll(); + expect(currentLaunchCounter).to.be.equal(54); + }); + test('Disabling banner must store value of \'false\' in global store', async () => { showBannerState.setup(s => s.updateValue(typemoq.It.isValue(false))) .verifiable(typemoq.Times.once());