From f35d1a2f9d214e0fa2d80e351befa8231401b76b Mon Sep 17 00:00:00 2001 From: Michael Jolley Date: Wed, 31 Mar 2021 15:20:03 -0500 Subject: [PATCH] Improved telemetry & testing setup (#15) * Improved telemetry & testing setup * Tests added for all commands * Fixing existing tests * Adding additional tests * Starting to add tests for numbers view * Adding unit tests * Fixing survey prompt test --- CHANGELOG.md | 10 +- package-lock.json | 15 ++ package.json | 7 +- src/commands/accountCommands.ts | 6 +- src/commands/applicationCommands.ts | 32 ++-- src/commands/authCommands.ts | 21 ++- src/commands/helpCommands.ts | 9 +- src/commands/numbersCommands.ts | 12 +- src/extension.ts | 4 +- src/prompts/survey.ts | 5 +- src/telemetry.ts | 32 ++-- src/views/account.ts | 4 +- test/suite/prompts/survey.test.ts | 78 -------- test/suite/prompts/telemetry.test.ts | 49 ----- test/suite/telemetry.test.ts | 36 ---- test/suite/utils.test.ts | 26 --- tests/mocks/index.ts | 2 + tests/mocks/vonage.ts | 78 ++++++++ {test => tests}/mocks/vscode.ts | 0 {test => tests}/runTest.ts | 2 +- tests/suite/commands/accountCommands.test.ts | 46 +++++ .../commands/applicationCommands.test.ts | 174 ++++++++++++++++++ tests/suite/commands/authCommands.test.ts | 70 +++++++ tests/suite/commands/helpCommands.test.ts | 69 +++++++ tests/suite/commands/numberCommands.test.ts | 83 +++++++++ {test => tests}/suite/extension.test.ts | 4 +- {test => tests}/suite/index.ts | 0 tests/suite/prompts/survey.test.ts | 63 +++++++ tests/suite/prompts/telemetry.test.ts | 40 ++++ tests/suite/telemetry.test.ts | 34 ++++ tests/suite/utils/getCountries.test.ts | 31 ++++ tests/suite/utils/getExtensionInfo.test.ts | 12 ++ tests/suite/views/account.test.ts | 53 ++++++ tests/suite/views/help.test.ts | 16 ++ tests/suite/views/number.test.ts | 48 +++++ tests/workspace/.vscode/settings.json | 4 + {test => tests}/workspace/emptyFile.ts | 0 tsconfig.json | 4 +- 38 files changed, 918 insertions(+), 261 deletions(-) delete mode 100644 test/suite/prompts/survey.test.ts delete mode 100644 test/suite/prompts/telemetry.test.ts delete mode 100644 test/suite/telemetry.test.ts delete mode 100644 test/suite/utils.test.ts create mode 100644 tests/mocks/index.ts create mode 100644 tests/mocks/vonage.ts rename {test => tests}/mocks/vscode.ts (100%) rename {test => tests}/runTest.ts (93%) create mode 100644 tests/suite/commands/accountCommands.test.ts create mode 100644 tests/suite/commands/applicationCommands.test.ts create mode 100644 tests/suite/commands/authCommands.test.ts create mode 100644 tests/suite/commands/helpCommands.test.ts create mode 100644 tests/suite/commands/numberCommands.test.ts rename {test => tests}/suite/extension.test.ts (75%) rename {test => tests}/suite/index.ts (100%) create mode 100644 tests/suite/prompts/survey.test.ts create mode 100644 tests/suite/prompts/telemetry.test.ts create mode 100644 tests/suite/telemetry.test.ts create mode 100644 tests/suite/utils/getCountries.test.ts create mode 100644 tests/suite/utils/getExtensionInfo.test.ts create mode 100644 tests/suite/views/account.test.ts create mode 100644 tests/suite/views/help.test.ts create mode 100644 tests/suite/views/number.test.ts create mode 100644 tests/workspace/.vscode/settings.json rename {test => tests}/workspace/emptyFile.ts (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d0fac..eacec1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [1.0.2] - 2021-03-31 + +### Updated + +- Added additional unit tests +- Improved gathering of telemetry + ## [1.0.0] - 2021-03-13 ### Added @@ -57,7 +64,8 @@ from using them if they weren't authenticated - Quick access to Vonage API dashboard - Initial README, CONTRIBUTING, etc. -[unreleased]: https://github.com/vonage/vscode/compare/1.0.0...HEAD +[unreleased]: https://github.com/vonage/vscode/compare/1.0.2...HEAD +[1.0.2]: https://github.com/vonage/vscode/compare/1.0.0...1.0.2 [1.0.0]: https://github.com/vonage/vscode/compare/0.0.10...1.0.0 [0.0.10]: https://github.com/vonage/vscode/compare/0.0.8...0.0.10 [0.0.8]: https://github.com/vonage/vscode/compare/edc07b4...0.0.8 diff --git a/package-lock.json b/package-lock.json index 31ade6e..3bcece1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,6 +80,21 @@ } } }, + "@istanbuljs/nyc-config-typescript": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.1.tgz", + "integrity": "sha512-/gz6LgVpky205LuoOfwEZmnUtaSmdk0QIMcNFj9OvxhiMhPpKftMgZmGN7jNj7jR+lr8IB1Yks3QSSSNSxfoaQ==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", diff --git a/package.json b/package.json index b3cc605..1d5163c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vscode", "displayName": "Vonage", - "version": "1.0.1", + "version": "1.0.2", "description": "A Visual Studio Code extension for accessing the Vonage communication APIs.", "main": "./dist/extension", "repository": { @@ -494,7 +494,7 @@ "watch": "tsc -watch -p ./", "pretest": "npm run compile", "webpack": "webpack --mode production", - "test": "node ./out/test/runTest.js" + "test": "node ./out/tests/runTest.js" }, "dependencies": { "@vonage/server-sdk": "^2.10.7", @@ -526,7 +526,6 @@ "glob": "^7.1.4", "mocha": "^8.2.1", "sinon": "^9.2.1", - "source-map-support": "^0.5.12", "ts-loader": "^8.0.14", "typescript": "^4.2.2", "vscode-codicons": "0.0.14", @@ -535,4 +534,4 @@ "webpack": "^5.19.0", "webpack-cli": "^4.4.0" } -} +} \ No newline at end of file diff --git a/src/commands/accountCommands.ts b/src/commands/accountCommands.ts index b1c902b..049b4d3 100644 --- a/src/commands/accountCommands.ts +++ b/src/commands/accountCommands.ts @@ -14,10 +14,12 @@ export class AccountCommands { } refresh = async (): Promise => { + this.telemetry.sendEvent('Account', 'account.refresh'); this.accountViewDataProvider.refresh(); } - toggleBalanceView = (): void => { - this.accountViewDataProvider.toggleBalanceView(); + toggleBalanceView = async (): Promise => { + this.telemetry.sendEvent('Account', 'account.toggleBalance'); + await this.accountViewDataProvider.toggleBalanceView(); } } \ No newline at end of file diff --git a/src/commands/applicationCommands.ts b/src/commands/applicationCommands.ts index b250cd0..c085c26 100644 --- a/src/commands/applicationCommands.ts +++ b/src/commands/applicationCommands.ts @@ -34,31 +34,31 @@ export class ApplicationCommands { /** Application commands */ refreshAppsList = () => { - this.telemetry.sendEvent('app.refreshAppsList'); + this.telemetry.sendEvent('Applications', 'app.refreshAppsList'); this.vonageApplicationViewDataProvider.refresh(); }; addApp = () => { - this.telemetry.sendEvent('app.addApp'); + this.telemetry.sendEvent('Applications', 'app.addApp'); this.vonageApplicationViewDataProvider.createApplication(); } updateApp = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.updateApp'); + this.telemetry.sendEvent('Applications', 'app.updateApp'); if (node) { this.vonageApplicationViewDataProvider.updateApplication(node); } } deleteApp = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.deleteApp'); + this.telemetry.sendEvent('Applications', 'app.deleteApp'); if (node) { this.vonageApplicationViewDataProvider.deleteApplication(node); } } linkApp = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.link'); + this.telemetry.sendEvent('Applications', 'app.link'); if (node) { this.vonageApplicationViewDataProvider.linkApplication(node); } @@ -67,21 +67,21 @@ export class ApplicationCommands { /** Voice commands */ voiceAdd = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.voice.add'); + this.telemetry.sendEvent('Applications', 'app.voice.add'); if (node) { this.vonageApplicationViewDataProvider.addVoice(node); } } voiceUpdate = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.voice.update'); + this.telemetry.sendEvent('Applications', 'app.voice.update'); if (node) { this.vonageApplicationViewDataProvider.updateVoice(node); } } voiceDelete = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.voice.delete'); + this.telemetry.sendEvent('Applications', 'app.voice.delete'); if (node) { this.vonageApplicationViewDataProvider.deleteVoice(node); } @@ -90,21 +90,21 @@ export class ApplicationCommands { /** RTC commands */ rtcAdd = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.rtc.add'); + this.telemetry.sendEvent('Applications', 'app.rtc.add'); if (node) { this.vonageApplicationViewDataProvider.addRTC(node); } } rtcUpdate = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.rtc.update'); + this.telemetry.sendEvent('Applications', 'app.rtc.update'); if (node) { this.vonageApplicationViewDataProvider.updateRTC(node); } } rtcDelete = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.rtc.delete'); + this.telemetry.sendEvent('Applications', 'app.rtc.delete'); if (node) { this.vonageApplicationViewDataProvider.deleteRTC(node); } @@ -113,21 +113,21 @@ export class ApplicationCommands { /** Messages commands */ messagesAdd = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.messages.add'); + this.telemetry.sendEvent('Applications', 'app.messages.add'); if (node) { this.vonageApplicationViewDataProvider.addMessages(node); } } messagesUpdate = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.messages.update'); + this.telemetry.sendEvent('Applications', 'app.messages.update'); if (node) { this.vonageApplicationViewDataProvider.updateMessages(node); } } messagesDelete = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.messages.delete'); + this.telemetry.sendEvent('Applications', 'app.messages.delete'); if (node) { this.vonageApplicationViewDataProvider.deleteMessages(node); } @@ -136,14 +136,14 @@ export class ApplicationCommands { /** VBC commands */ vbcAdd = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.vbc.add'); + this.telemetry.sendEvent('Applications', 'app.vbc.add'); if (node) { this.vonageApplicationViewDataProvider.addVBC(node); } } vbcDelete = (node?: ApplicationTreeItem) => { - this.telemetry.sendEvent('app.vbc.delete'); + this.telemetry.sendEvent('Applications', 'app.vbc.delete'); if (node) { this.vonageApplicationViewDataProvider.deleteVBC(node); } diff --git a/src/commands/authCommands.ts b/src/commands/authCommands.ts index 3bbcaf2..6dcf6fe 100644 --- a/src/commands/authCommands.ts +++ b/src/commands/authCommands.ts @@ -18,21 +18,24 @@ export class AuthCommands { * in order to use extension. */ login = async (): Promise => { - this.telemetry.sendEvent('login'); + this.telemetry.sendEvent('Auth', 'login'); const state = await LoginFlow.collectInputs(); - await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: `Configuring extension"...` - }, async () => { - await Auth.login(state.api_key, state.api_secret); - }); - + if (state.api_key?.length > 0 && state.api_secret?.length > 0) { + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Configuring extension"...` + }, async () => { + await Auth.login(state.api_key, state.api_secret); + }); + } else { + vscode.window.showErrorMessage('Your Vonage API key & secret are required to use the Vonage for VS Code extension.'); + } } logout = async (): Promise => { - this.telemetry.sendEvent('logout'); + this.telemetry.sendEvent('Auth', 'logout'); await Auth.logout(); } } \ No newline at end of file diff --git a/src/commands/helpCommands.ts b/src/commands/helpCommands.ts index 0d2fd7f..84e3360 100644 --- a/src/commands/helpCommands.ts +++ b/src/commands/helpCommands.ts @@ -19,6 +19,7 @@ export class HelpCommands { } refresh = async () => { + this.telemetry.sendEvent('Help', 'help.refresh'); this.vonageHelpViewDataProvider.refresh(); } @@ -27,7 +28,7 @@ export class HelpCommands { * Ideally will open documentation for the extension. */ openDocs = () => { - this.telemetry.sendEvent('help.openDocs'); + this.telemetry.sendEvent('Help', 'help.openDocs'); vscode.env.openExternal(vscode.Uri.parse('https://developer.nexmo.com')); }; @@ -36,7 +37,7 @@ export class HelpCommands { * to provide feedback on the extension. */ openReportIssue = () => { - this.telemetry.sendEvent('help.openReportIssue'); + this.telemetry.sendEvent('Help', 'help.openReportIssue'); const { name, publisher } = getExtensionInfo(); vscode.commands.executeCommand('vscode.openIssueReporter', { @@ -49,7 +50,7 @@ export class HelpCommands { * users to provide feedback on the extension. */ openSurvey = () => { - this.telemetry.sendEvent('help.openSurvey'); + this.telemetry.sendEvent('Help', 'help.openSurvey'); const extensionInfo = getExtensionInfo(); const query = querystring.stringify({ @@ -68,7 +69,7 @@ export class HelpCommands { * the user can learn more. */ openTelemetryInfo = () => { - this.telemetry.sendEvent('help.openTelemetryInfo'); + this.telemetry.sendEvent('Help', 'help.openTelemetryInfo'); vscode.env.openExternal( vscode.Uri.parse('https://code.visualstudio.com/docs/getstarted/telemetry'), ); diff --git a/src/commands/numbersCommands.ts b/src/commands/numbersCommands.ts index 58e237d..5fb6f0a 100644 --- a/src/commands/numbersCommands.ts +++ b/src/commands/numbersCommands.ts @@ -19,38 +19,38 @@ export class NumbersCommands { /** Number commands */ refreshNumbersList = () => { - this.telemetry.sendEvent('number.refreshList'); + this.telemetry.sendEvent('Numbers', 'number.refreshList'); this.vonageNumbersViewDataProvider.refresh(); }; buyNumber = () => { - this.telemetry.sendEvent('number.buy'); + this.telemetry.sendEvent('Numbers', 'number.buy'); this.vonageNumbersViewDataProvider.buyNumber(); } cancelNumber = (node?: NumberTreeItem) => { - this.telemetry.sendEvent('number.cancel'); + this.telemetry.sendEvent('Numbers', 'number.cancel'); if (node) { this.vonageNumbersViewDataProvider.cancelNumber(node); } } assignNumber = (node?: NumberTreeItem) => { - this.telemetry.sendEvent('number.assign'); + this.telemetry.sendEvent('Numbers', 'number.assign'); if (node) { this.vonageNumbersViewDataProvider.assignNumber(node); } } unassignNumber = (node?: NumberTreeItem) => { - this.telemetry.sendEvent('number.unassign'); + this.telemetry.sendEvent('Numbers', 'number.unassign'); if (node) { this.vonageNumbersViewDataProvider.unassignNumber(node); } } copyNumber = (node?: NumberTreeItem) => { - this.telemetry.sendEvent('number.copy'); + this.telemetry.sendEvent('Numbers', 'number.copy'); if (node) { this.vonageNumbersViewDataProvider.copyNumber(node); } diff --git a/src/extension.ts b/src/extension.ts index bc8d258..45a0dab 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,7 +19,6 @@ import { } from './commands'; import { Auth } from './auth'; import { Credentials } from './models'; -import { StorageKeys } from './enums'; let activeExtension: Extension; let _context: vscode.ExtensionContext; @@ -53,7 +52,7 @@ export class Extension { * and Vonage specific telemetry settings are allowed. */ const telemetry = getTelemetry(); - telemetry.sendEvent('activate'); + telemetry.sendEvent('Activation', 'activate'); /** @@ -155,7 +154,6 @@ export async function deactivate() { */ function getTelemetry() { if (process.env.EXTENSION_MODE === 'development' || vscode.env.sessionId === 'someValue.sessionId') { - console.log('Extension is running in development mode. Using local telemetry instance'); return new LocalTelemetry(); } else { return GoogleAnalyticsTelemetry.getInstance(); diff --git a/src/prompts/survey.ts b/src/prompts/survey.ts index de2258f..c1a053d 100644 --- a/src/prompts/survey.ts +++ b/src/prompts/survey.ts @@ -7,11 +7,8 @@ import { StorageKeys } from '../enums'; * form of a survey on the learn.vonage.com site */ export class SurveyPrompt { - private storage: vscode.Memento; - constructor(state: vscode.Memento) { - this.storage = state; - } + constructor(private storage: vscode.Memento) {} /** * Prompt user concerning the survey diff --git a/src/telemetry.ts b/src/telemetry.ts index 20142a9..7ab8209 100644 --- a/src/telemetry.ts +++ b/src/telemetry.ts @@ -5,7 +5,7 @@ import publicIp from 'public-ip'; import { getExtensionInfo } from './utils'; export interface Telemetry { - sendEvent(eventName: string, eventValue?: any): void; + sendEvent(eventCategory: string, eventName: string, eventValue?: any): void; isTelemetryEnabled(): boolean; } @@ -15,10 +15,11 @@ export interface Telemetry { export class NoOpTelemetry implements Telemetry { /** * NoOp implementation of sending a telemetry event + * @param eventCategory Area of the extension the event lives * @param eventName Name of the event that occurred * @param eventValue Optional value of the event */ - sendEvent(eventName: string, eventValue?: any) { + sendEvent(eventCategory: string, eventName: string, eventValue?: any) { return; } @@ -38,11 +39,12 @@ export class LocalTelemetry implements Telemetry { /** * Records telemetry to the debug console + * @param eventCategory Area of the extension the event lives * @param eventName Name of the event that occurred * @param eventValue Optional value of the event */ - sendEvent(eventName: string, eventValue?: any) { - console.log('[TelemetryEvent] %s: %s', eventName, eventValue); + sendEvent(eventCategory: string, eventName: string, eventValue?: any) { + console.log(`[TelemetryEvent] ${eventCategory}: ${eventName}: ${eventValue}`); } /** @@ -59,7 +61,7 @@ export class LocalTelemetry implements Telemetry { export class GoogleAnalyticsTelemetry implements Telemetry { private static INSTANCE: GoogleAnalyticsTelemetry; - client: any; + visitor: any; userId: string; ip: string; private _isTelemetryEnabled: boolean; @@ -104,11 +106,11 @@ export class GoogleAnalyticsTelemetry implements Telemetry { return; } - if (this.client) { + if (this.visitor) { return; } - this.client = ua('UA-190207805-1'); + this.visitor = ua('UA-190207805-1'); const extensionInfo = getExtensionInfo(); @@ -120,24 +122,24 @@ export class GoogleAnalyticsTelemetry implements Telemetry { /** * User custom dimensions to store user metadata */ - this.client.set('cd1', vscode.env.sessionId); - this.client.set('cd2', vscode.env.language); - this.client.set('cd3', vscode.version); - this.client.set('cd4', osName()); - this.client.set('cd5', extensionInfo.version); + this.visitor.set('applicationVersion ', vscode.version); + this.visitor.set('userLanguage', vscode.env.language); + this.visitor.set('cd1', osName()); + this.visitor.set('cd2', extensionInfo.version); /** * Set userID */ - this.client.set('uid', this.userId); + this.visitor.set('uid', this.userId); } /** * Records event to telemetry + * @param eventCategory Area of the extension the event lives * @param eventName Name of the event that occurred * @param eventValue Optional value of the event */ - sendEvent(eventName: string, eventValue?: any): void { + sendEvent(eventCategory: string, eventName: string, eventValue?: any): void { if (!this.isTelemetryEnabled) { return; } @@ -150,7 +152,7 @@ export class GoogleAnalyticsTelemetry implements Telemetry { uid: this.userId, }; - this.client.event(requestParams).send(); + this.visitor.event(requestParams).send(); } /** diff --git a/src/views/account.ts b/src/views/account.ts index 55e53f5..428b40e 100644 --- a/src/views/account.ts +++ b/src/views/account.ts @@ -39,10 +39,10 @@ export class AccountViewDataProvider extends BaseTreeViewDataProvider { ]; } - toggleBalanceView(): void { + async toggleBalanceView(): Promise { const currentHideState = this.storage.get(StorageKeys.hideAccountBalance) as boolean || false; - this.storage.update(StorageKeys.hideAccountBalance, !currentHideState); + await this.storage.update(StorageKeys.hideAccountBalance, !currentHideState); this.refresh(); } } \ No newline at end of file diff --git a/test/suite/prompts/survey.test.ts b/test/suite/prompts/survey.test.ts deleted file mode 100644 index 2c200d0..0000000 --- a/test/suite/prompts/survey.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -// // tslint:disable: no-unused-expression -// import * as vscode from 'vscode'; -// // import * as sinon from 'sinon'; -// // import * as chai from 'chai'; -// import moment from 'moment'; -// // import { fakeMemento, fakeWorkspaceConfiguration } from '../../../../.github/fakes'; -// import { StorageKeys } from '../../../enums'; -// import { SurveyPrompt } from '../../../prompts'; - -// // chai.should(); - -// suite('Survey Prompt Tests', function () { -// // let fakeState: vscode.Memento; -// // let fakeWorkspaceConfig: vscode.WorkspaceConfiguration; -// // let fakeSurveyPrompt: SurveyPrompt; -// // let getConfigurationStub: sinon.SinonStub<[(string | undefined)?, (vscode.Uri | null | undefined)?], vscode.WorkspaceConfiguration>; - -// // suiteSetup(function () { -// // fakeState = fakeMemento; -// // fakeWorkspaceConfig = fakeWorkspaceConfiguration; -// // }); - -// // suiteTeardown(function () { -// // getConfigurationStub.restore(); -// // }); - -// // setup(async function () { -// // getConfigurationStub.resetHistory(); -// // fakeState.update(StorageKeys.doNotShowSurveyPromptAgain, false); -// // fakeState.update(StorageKeys.lastSurveyDate, undefined); -// // fakeSurveyPrompt = new SurveyPrompt(fakeState); -// // }); - -// // test(`Should not show if user selected to never show again`, async function () { -// // fakeState.update(StorageKeys.doNotShowSurveyPromptAgain, true); - -// // const getStorageStub = sinon.stub(fakeState, 'get'); - -// // const shouldShow = fakeSurveyPrompt.shouldShowBanner(); -// // sinon.assert.match(shouldShow, false); -// // getStorageStub.notCalled.should.be.true; -// // }); - -// // test(`Should not show if user has seen message within past 12 weeks`, async function () { -// // const currentEpoch = moment().valueOf(); -// // this.storage.update(StorageKeys.lastSurveyDate, currentEpoch); - -// // const getStorageStub = sinon.stub(fakeState, 'get'); - -// // const shouldShow = fakeSurveyPrompt.shouldShowBanner(); -// // sinon.assert.match(shouldShow, false); -// // getStorageStub.called.should.be.true; -// // }); - -// // test(`Should not show if not in 20% sampling`, async function () { -// // const getRandomIntStub = sinon -// // .stub(fakeSurveyPrompt, 'getRandomInt') -// // .callsFake((max: number): number => { -// // return 80; -// // }); - -// // const shouldShow = fakeSurveyPrompt.shouldShowBanner(); -// // sinon.assert.match(shouldShow, false); -// // getRandomIntStub.calledOnce.should.be.true; -// // }); - -// // test(`Should show if in 20% sampling`, async function () { -// // const getRandomIntStub = sinon -// // .stub(fakeSurveyPrompt, 'getRandomInt') -// // .callsFake((max: number): number => { -// // return 10; -// // }); - -// // const shouldShow = fakeSurveyPrompt.shouldShowBanner(); -// // sinon.assert.match(shouldShow, true); -// // getRandomIntStub.calledOnce.should.be.true; -// // }); -// }); \ No newline at end of file diff --git a/test/suite/prompts/telemetry.test.ts b/test/suite/prompts/telemetry.test.ts deleted file mode 100644 index 4326f83..0000000 --- a/test/suite/prompts/telemetry.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -// import vscode from 'vscode'; -// import sinon from 'sinon'; -// import chai from 'chai'; -// import { TelemetryPrompt } from '../../../src/prompts'; -// import { StorageKeys } from '../../../src/enums'; - -// chai.should(); - -// suite('Telemetry Prompt Tests', () => { - -// let fakeState: vscode.Memento; -// let fakeWorkspaceConfig: vscode.WorkspaceConfiguration; -// let fakeTelemetryPrompt: TelemetryPrompt; -// let getConfigurationStub: sinon.SinonStub<[(string | undefined)?, (vscode.Uri | null | undefined)?], vscode.WorkspaceConfiguration>; -// let windowShowInformationMessageStub: sinon.SinonStub; - -// suiteSetup(function () { -// fakeState = fakeMemento; -// fakeWorkspaceConfig = fakeWorkspaceConfiguration; -// windowShowInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage'); -// }); - -// suiteTeardown(function () { -// windowShowInformationMessageStub.restore(); -// }); - -// setup(async function () { -// windowShowInformationMessageStub.resetHistory(); -// fakeState.update(StorageKeys.doNotShowTelemetryPromptAgain, false); -// fakeTelemetryPrompt = new TelemetryPrompt(fakeState); -// }); - -// test(`Should show if never shown`, async function () { -// fakeTelemetryPrompt.activate(); -// windowShowInformationMessageStub.calledOnce.should.be.true; -// }); - -// test(`Should not show if shown before`, async function () { - -// console.log(windowShowInformationMessageStub.callCount); -// fakeState.update(StorageKeys.doNotShowSurveyPromptAgain, true); - -// fakeTelemetryPrompt.activate(); - - -// windowShowInformationMessageStub.called.should.be.false; -// }); - -// }); \ No newline at end of file diff --git a/test/suite/telemetry.test.ts b/test/suite/telemetry.test.ts deleted file mode 100644 index e029c62..0000000 --- a/test/suite/telemetry.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import { GoogleAnalyticsTelemetry } from '../../src/telemetry'; - -suite('GoogleAnalyticsTelemetry', function () { - this.timeout(20000); - const telemetry = GoogleAnalyticsTelemetry.getInstance(); - - suite('Telemetry configs', () => { - test('Respects overall and Vonage-specific telemetry configs', async () => { - const workspaceFolder = - vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]; - const telemetryConfig = vscode.workspace.getConfiguration('telemetry', workspaceFolder); - const stripeTelemetryConfig = vscode.workspace.getConfiguration( - 'vonage.telemetry', - workspaceFolder, - ); - - await telemetryConfig.update('enableTelemetry', false); - await stripeTelemetryConfig.update('enabled', false); - assert.strictEqual(telemetry.isTelemetryEnabled(), false); - - await telemetryConfig.update('enableTelemetry', false); - await stripeTelemetryConfig.update('enabled', true); - assert.strictEqual(telemetry.isTelemetryEnabled(), false); - - await telemetryConfig.update('enableTelemetry', true); - await stripeTelemetryConfig.update('enabled', false); - assert.strictEqual(telemetry.isTelemetryEnabled(), false); - - await telemetryConfig.update('enableTelemetry', true); - await stripeTelemetryConfig.update('enabled', true); - assert.strictEqual(telemetry.isTelemetryEnabled(), true); - }); - }); -}); \ No newline at end of file diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts deleted file mode 100644 index a73f77b..0000000 --- a/test/suite/utils.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as sinon from 'sinon'; -import * as vscode from 'vscode'; -import * as assert from 'assert'; -import * as utils from '../../src/utils'; - -suite('Utils', () => { - const arr = [1, 2, 3]; - let sandbox: sinon.SinonSandbox; - - setup(() => { - sandbox = sinon.createSandbox(); - }); - - teardown(() => { - sandbox.restore(); - }); - - suite('getExtensionInfo', () => { - test('getExtensionInfo returns vonage.vscode extension', async () => { - const extensionInfo = utils.getExtensionInfo(); - - assert.notDeepStrictEqual(extensionInfo, {}); - assert.strictEqual(>extensionInfo.id, 'vonage.vscode'); - }); - }); -}); \ No newline at end of file diff --git a/tests/mocks/index.ts b/tests/mocks/index.ts new file mode 100644 index 0000000..89af7f3 --- /dev/null +++ b/tests/mocks/index.ts @@ -0,0 +1,2 @@ +export * from './vonage'; +export * from './vscode'; \ No newline at end of file diff --git a/tests/mocks/vonage.ts b/tests/mocks/vonage.ts new file mode 100644 index 0000000..33e46c2 --- /dev/null +++ b/tests/mocks/vonage.ts @@ -0,0 +1,78 @@ + +export const vonage = { + loginStateValidMock: { + title: '', + step: 2, + totalSteps: 2, + api_key: 'fakeApiKey', + api_secret: 'fakeApiSecret' + }, + loginStateInvalidMock: { + title: '', + step: 2, + totalSteps: 2, + api_key: '', + api_secret: 'fakeApiSecret' + }, + numberMock: { + msisdn: '15555555555', + type: 'voice', + country: 'US', + cost: '0', + features: ['voice', 'sms'], + moHttpUrl: '', + messagesCallbackType: '', + messagesCallbackValue: '', + voiceCallbackType: '', + voiceCallbackValue: '' + }, + applicationMock: { + "id": "78d335fa-323d-0114-9c3d-d6f0d48968cf", + "name": "My Application", + "capabilities": { + "voice": { + "webhooks": { + "answer_url": { + "address": "https://example.com/webhooks/answer", + "http_method": "POST", + "connection_timeout": 500, + "socket_timeout": 3000 + }, + "fallback_answer_url": { + "address": "https://fallback.example.com/webhooks/answer", + "http_method": "POST", + "connection_timeout": 500, + "socket_timeout": 3000 + }, + "event_url": { + "address": "https://example.com/webhooks/event", + "http_method": "POST", + "connection_timeout": 500, + "socket_timeout": 3000 + } + } + }, + "messages": { + "webhooks": { + "inbound_url": { + "address": "https://example.com/webhooks/inbound", + "http_method": "POST" + }, + "status_url": { + "address": "https://example.com/webhooks/status", + "http_method": "POST" + } + } + }, + "rtc": { + "webhooks": { + "event_url": { + "address": "https://example.com/webhooks/event", + "http_method": "POST" + } + } + }, + "vbc": { } + } + } +}; diff --git a/test/mocks/vscode.ts b/tests/mocks/vscode.ts similarity index 100% rename from test/mocks/vscode.ts rename to tests/mocks/vscode.ts diff --git a/test/runTest.ts b/tests/runTest.ts similarity index 93% rename from test/runTest.ts rename to tests/runTest.ts index 0bb7a6d..58602e9 100644 --- a/test/runTest.ts +++ b/tests/runTest.ts @@ -10,7 +10,7 @@ async function main() { // The path to the extension test workspace const testWorkspace = path.resolve( __dirname, - extensionDevelopmentPath + '/test/workspace/', + extensionDevelopmentPath + '/tests/workspace/', ); const launchArgs = [ diff --git a/tests/suite/commands/accountCommands.test.ts b/tests/suite/commands/accountCommands.test.ts new file mode 100644 index 0000000..253ab55 --- /dev/null +++ b/tests/suite/commands/accountCommands.test.ts @@ -0,0 +1,46 @@ +import chai from 'chai'; +import Sinon from 'sinon'; +import { AccountCommands } from '../../../src/commands'; +import { LocalTelemetry } from '../../../src/telemetry'; +import { AccountViewDataProvider } from '../../../src/views'; +import { mocks } from '../../mocks'; + +chai.should(); + +suite('Commands:Account', function() { + + const telemetry = new LocalTelemetry(); + const telemetrySendEvent = Sinon.stub(telemetry, 'sendEvent'); + const viewProvider = new AccountViewDataProvider(mocks.extensionContextMock.globalState); + + const accountCommands = new AccountCommands( + mocks.extensionContextMock.subscriptions, + telemetry, + viewProvider); + + this.beforeEach(function() { + telemetrySendEvent.resetHistory(); + }); + + this.afterAll(function() { + telemetrySendEvent.restore(); + }); + + test('refresh calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'refresh'); + + accountCommands.refresh(); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('toggleBalanceView calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'toggleBalanceView'); + + accountCommands.toggleBalanceView(); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); +}); \ No newline at end of file diff --git a/tests/suite/commands/applicationCommands.test.ts b/tests/suite/commands/applicationCommands.test.ts new file mode 100644 index 0000000..5e12ae0 --- /dev/null +++ b/tests/suite/commands/applicationCommands.test.ts @@ -0,0 +1,174 @@ +import chai from 'chai'; +import Sinon from 'sinon'; +import { ApplicationCommands } from '../../../src/commands'; +import { LocalTelemetry } from '../../../src/telemetry'; +import { mocks, vonage } from '../../mocks'; +import { ApplicationViewDataProvider, ApplicationTreeItem } from '../../../src/views'; + +chai.should(); + +suite('Commands:Applications', function() { + + const telemetry = new LocalTelemetry(); + const telemetrySendEvent = Sinon.stub(telemetry, 'sendEvent'); + const node = new ApplicationTreeItem(vonage.applicationMock); + + const viewProvider = new ApplicationViewDataProvider(); + + const applicationsCommands = new ApplicationCommands( + mocks.extensionContextMock.subscriptions, + telemetry, + viewProvider); + + this.beforeEach(() => { + telemetrySendEvent.resetHistory(); + }); + + this.afterAll(() => { + telemetrySendEvent.restore(); + }); + + test('refreshAppsList refreshes appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'refresh'); + + applicationsCommands.refreshAppsList(); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('addApp calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'createApplication'); + + applicationsCommands.addApp(); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('updateApp calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'updateApplication'); + + applicationsCommands.updateApp(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('deleteApp calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'deleteApplication'); + + applicationsCommands.deleteApp(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('linkApp calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'linkApplication'); + + applicationsCommands.linkApp(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('voiceAdd calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'addVoice'); + + applicationsCommands.voiceAdd(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('voiceUpdate calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'updateVoice'); + + applicationsCommands.voiceUpdate(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('voiceDelete calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'deleteVoice'); + + applicationsCommands.voiceDelete(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('rtcAdd calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'addRTC'); + + applicationsCommands.rtcAdd(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('rtcUpdate calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'updateRTC'); + + applicationsCommands.rtcUpdate(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('rtcDelete calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'deleteRTC'); + + applicationsCommands.rtcDelete(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('messagesAdd calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'addMessages'); + + applicationsCommands.messagesAdd(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('messagesUpdate calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'updateMessages'); + + applicationsCommands.messagesUpdate(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('messagesDelete calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'deleteMessages'); + + applicationsCommands.messagesDelete(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('vbcAdd calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'addVBC'); + + applicationsCommands.vbcAdd(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('vbcDelete calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'deleteVBC'); + + applicationsCommands.vbcDelete(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); +}); \ No newline at end of file diff --git a/tests/suite/commands/authCommands.test.ts b/tests/suite/commands/authCommands.test.ts new file mode 100644 index 0000000..57e7f97 --- /dev/null +++ b/tests/suite/commands/authCommands.test.ts @@ -0,0 +1,70 @@ +import chai from 'chai'; +import Sinon from 'sinon'; +import * as vscode from 'vscode'; +import { AuthCommands } from '../../../src/commands'; +import { LocalTelemetry } from '../../../src/telemetry'; +import { mocks, vonage } from '../../mocks'; +import { Auth } from '../../../src/auth'; +import { LoginFlow } from '../../../src/steps'; + +chai.should(); + +suite('Commands:Auth', function() { + + const telemetry: LocalTelemetry = new LocalTelemetry(); + const telemetrySendEvent = Sinon.stub(telemetry, 'sendEvent'); + + const authCommands: AuthCommands = new AuthCommands( + mocks.extensionContextMock.subscriptions, + telemetry); + + this.afterEach(() => { + telemetrySendEvent.reset(); + }); + + test('login renders correct user flow', async () => { + + const loginFlowStub = Sinon.stub(LoginFlow, 'collectInputs').returns(Promise.resolve(vonage.loginStateInvalidMock)); + + await authCommands.login(); + + telemetrySendEvent.calledOnce.should.eq(true); + loginFlowStub.calledOnce.should.eq(true); + + loginFlowStub.restore(); + }); + + test('error message displays when missing api key or secret', async () => { + const windowShowErrorMessageStub = Sinon.stub(vscode.window, 'showErrorMessage'); + const loginFlowStub = Sinon.stub(LoginFlow, 'collectInputs').returns(Promise.resolve(vonage.loginStateInvalidMock)); + + await authCommands.login(); + + windowShowErrorMessageStub.calledOnce.should.eq(true); + + windowShowErrorMessageStub.restore(); + loginFlowStub.restore(); + }); + + test('calls Auth.login when api key and secret are provided', async () => { + const loginFlowStub = Sinon.stub(LoginFlow, 'collectInputs').returns(Promise.resolve(vonage.loginStateValidMock)); + const authLoginStub = Sinon.stub(Auth, 'login'); + + await authCommands.login(); + + authLoginStub.calledOnce.should.eq(true); + + authLoginStub.restore(); + loginFlowStub.restore(); + }); + + test('logout calls auth logout', async () => { + const stub = Sinon.stub(Auth, 'logout'); + + await authCommands.logout(); + + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); +}); \ No newline at end of file diff --git a/tests/suite/commands/helpCommands.test.ts b/tests/suite/commands/helpCommands.test.ts new file mode 100644 index 0000000..5b41449 --- /dev/null +++ b/tests/suite/commands/helpCommands.test.ts @@ -0,0 +1,69 @@ +import chai from 'chai'; +import Sinon from 'sinon'; +import vscode from 'vscode'; +import { HelpCommands } from '../../../src/commands'; +import { LocalTelemetry } from '../../../src/telemetry'; +import { HelpViewDataProvider } from '../../../src/views'; +import { mocks } from '../../mocks'; + +chai.should(); + +suite('Commands:Help', function() { + + const telemetry = new LocalTelemetry(); + const telemetrySendEvent = Sinon.stub(telemetry, 'sendEvent'); + const openExternalStub = Sinon.stub(vscode.env, 'openExternal'); + + const viewProvider = new HelpViewDataProvider(); + + const helpCommands = new HelpCommands( + mocks.extensionContextMock.subscriptions, + telemetry, + viewProvider); + + this.beforeEach(() => { + telemetrySendEvent.resetHistory(); + openExternalStub.resetHistory(); + }); + + this.afterAll(() => { + telemetrySendEvent.restore(); + openExternalStub.restore(); + }); + + test('refresh refreshes appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'refresh'); + + helpCommands.refresh(); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('openDocs opens external url', async () => { + helpCommands.openDocs(); + telemetrySendEvent.calledOnce.should.eq(true); + openExternalStub.calledOnce.should.eq(true); + }); + + test('openReportIssue fires an extension command', async () => { + const stub = Sinon.stub(vscode.commands, 'executeCommand'); + + helpCommands.openReportIssue(); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('openSurvey opens external url', async () => { + helpCommands.openSurvey(); + telemetrySendEvent.calledOnce.should.eq(true); + openExternalStub.calledOnce.should.eq(true); + }); + + test('openTelemetryInfo opens external url', async () => { + helpCommands.openTelemetryInfo(); + telemetrySendEvent.calledOnce.should.eq(true); + openExternalStub.calledOnce.should.eq(true); + }); +}); \ No newline at end of file diff --git a/tests/suite/commands/numberCommands.test.ts b/tests/suite/commands/numberCommands.test.ts new file mode 100644 index 0000000..e40a1fa --- /dev/null +++ b/tests/suite/commands/numberCommands.test.ts @@ -0,0 +1,83 @@ +import chai from 'chai'; +import Sinon from 'sinon'; +import { NumbersCommands } from '../../../src/commands'; +import { LocalTelemetry } from '../../../src/telemetry'; +import { mocks, vonage } from '../../mocks'; +import { NumbersViewDataProvider, NumberTreeItem } from '../../../src/views'; + +chai.should(); + +suite('Commands:Numbers', function() { + + const telemetry = new LocalTelemetry(); + const telemetrySendEvent = Sinon.stub(telemetry, 'sendEvent'); + + const viewProvider = new NumbersViewDataProvider(mocks.extensionContextMock.globalState); + + const numbersCommands = new NumbersCommands( + mocks.extensionContextMock.subscriptions, + telemetry, + viewProvider); + + this.beforeEach(() => { + telemetrySendEvent.resetHistory(); + }); + + this.afterAll(() => { + telemetrySendEvent.restore(); + }); + + test('refreshNumbersList refreshes appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'refresh'); + + numbersCommands.refreshNumbersList(); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('buyNumber calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'buyNumber'); + + numbersCommands.buyNumber(); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('cancelNumber calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'cancelNumber'); + const node = new NumberTreeItem(vonage.numberMock); + numbersCommands.cancelNumber(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('assignNumber calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'assignNumber'); + const node = new NumberTreeItem(vonage.numberMock); + numbersCommands.assignNumber(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('unassignNumber calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'unassignNumber'); + const node = new NumberTreeItem(vonage.numberMock); + numbersCommands.unassignNumber(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); + + test('copyNumber calls appropriate view', async () => { + const stub = Sinon.stub(viewProvider, 'copyNumber'); + const node = new NumberTreeItem(vonage.numberMock); + numbersCommands.copyNumber(node); + telemetrySendEvent.calledOnce.should.eq(true); + stub.calledOnce.should.eq(true); + stub.restore(); + }); +}); \ No newline at end of file diff --git a/test/suite/extension.test.ts b/tests/suite/extension.test.ts similarity index 75% rename from test/suite/extension.test.ts rename to tests/suite/extension.test.ts index b179998..f24599a 100644 --- a/test/suite/extension.test.ts +++ b/tests/suite/extension.test.ts @@ -1,8 +1,8 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -suite('Extension Test Suite', () => { - test('Should start extension vonage.vscode', async () => { +suite('Extension', () => { + test('should start extension vonage.vscode', async () => { const started = vscode.extensions.getExtension('vonage.vscode'); assert.notStrictEqual(started, undefined); if (started) { diff --git a/test/suite/index.ts b/tests/suite/index.ts similarity index 100% rename from test/suite/index.ts rename to tests/suite/index.ts diff --git a/tests/suite/prompts/survey.test.ts b/tests/suite/prompts/survey.test.ts new file mode 100644 index 0000000..1883e53 --- /dev/null +++ b/tests/suite/prompts/survey.test.ts @@ -0,0 +1,63 @@ +import Sinon from 'sinon'; +import chai from 'chai'; +import moment from 'moment'; +import { SurveyPrompt } from '../../../src/prompts'; +import { StorageKeys } from '../../../src/enums'; +import { TestMemento } from '../../mocks'; + +chai.should(); + +suite('Prompt:Survey', function () { + + let storage: TestMemento; + let surveyPrompt: SurveyPrompt; + + this.beforeEach(function() { + storage = new TestMemento(); + storage.storage = new Map(); + surveyPrompt = new SurveyPrompt(storage); + }); + + test(`should not show if user selected to never show again`, async function () { + await storage.update(StorageKeys.doNotShowSurveyPromptAgain, true); + + const shouldShow = surveyPrompt.shouldShowBanner(); + + shouldShow.should.eq(false); + }); + + test(`should not show if user has seen message within past 12 weeks`, async function () { + const currentEpoch = moment().valueOf(); + await storage.update(StorageKeys.lastSurveyDate, currentEpoch); + + const shouldShow = surveyPrompt.shouldShowBanner(); + shouldShow.should.eq(false); + + }); + + test(`should not show if not in 20% sampling`, function () { + const getRandomIntStub = Sinon + .stub(surveyPrompt, 'getRandomInt') + .callsFake((max: number): number => { + return 80; + }); + + const shouldShow = surveyPrompt.shouldShowBanner(); + shouldShow.should.eq(false); + getRandomIntStub.calledOnce.should.be.true; + getRandomIntStub.restore(); + }); + + test(`should show if in 20% sampling`, function () { + const getRandomIntStub = Sinon + .stub(surveyPrompt, 'getRandomInt') + .callsFake((max: number): number => { + return 10; + }); + + const shouldShow = surveyPrompt.shouldShowBanner(); + shouldShow.should.eq(true); + getRandomIntStub.calledOnce.should.be.true; + getRandomIntStub.restore(); + }); +}); \ No newline at end of file diff --git a/tests/suite/prompts/telemetry.test.ts b/tests/suite/prompts/telemetry.test.ts new file mode 100644 index 0000000..9aad465 --- /dev/null +++ b/tests/suite/prompts/telemetry.test.ts @@ -0,0 +1,40 @@ +import vscode from 'vscode'; +import Sinon from 'sinon'; +import chai from 'chai'; +import { TelemetryPrompt } from '../../../src/prompts'; +import { StorageKeys } from '../../../src/enums'; +import { TestMemento } from '../../mocks'; + +chai.should(); + +suite('Prompt:Telemetry', function() { + + const storage = new TestMemento(); + let telemetryPrompt: TelemetryPrompt; + const windowShowInformationMessageStub = Sinon.stub(vscode.window, 'showInformationMessage'); + + this.beforeEach(function() { + storage.storage = new Map(); + telemetryPrompt = new TelemetryPrompt(storage); + storage.update(StorageKeys.doNotShowTelemetryPromptAgain, false); + windowShowInformationMessageStub.resetHistory(); + }); + + this.afterAll(function() { + windowShowInformationMessageStub.restore(); + }); + + test(`should show if never shown`, function () { + telemetryPrompt.activate(); + + windowShowInformationMessageStub.calledOnce.should.be.true; + }); + + test(`should not show if shown before`, function () { + storage.update(StorageKeys.doNotShowTelemetryPromptAgain, true); + + telemetryPrompt.activate(); + + windowShowInformationMessageStub.called.should.be.false; + }); +}); \ No newline at end of file diff --git a/tests/suite/telemetry.test.ts b/tests/suite/telemetry.test.ts new file mode 100644 index 0000000..fa0089a --- /dev/null +++ b/tests/suite/telemetry.test.ts @@ -0,0 +1,34 @@ +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { GoogleAnalyticsTelemetry } from '../../src/telemetry'; + +suite('Telemetry', function () { + this.timeout(20000); + const telemetry = GoogleAnalyticsTelemetry.getInstance(); + + test('respects overall and Vonage-specific telemetry configs', async () => { + const workspaceFolder = + vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]; + const telemetryConfig = vscode.workspace.getConfiguration('telemetry', workspaceFolder); + const vonageTelemetryConfig = vscode.workspace.getConfiguration( + 'vonage.telemetry', + workspaceFolder, + ); + + await telemetryConfig.update('enableTelemetry', false); + await vonageTelemetryConfig.update('enabled', false); + assert.strictEqual(telemetry.isTelemetryEnabled(), false); + + await telemetryConfig.update('enableTelemetry', false); + await vonageTelemetryConfig.update('enabled', true); + assert.strictEqual(telemetry.isTelemetryEnabled(), false); + + await telemetryConfig.update('enableTelemetry', true); + await vonageTelemetryConfig.update('enabled', false); + assert.strictEqual(telemetry.isTelemetryEnabled(), false); + + await telemetryConfig.update('enableTelemetry', true); + await vonageTelemetryConfig.update('enabled', true); + assert.strictEqual(telemetry.isTelemetryEnabled(), true); + }); +}); \ No newline at end of file diff --git a/tests/suite/utils/getCountries.test.ts b/tests/suite/utils/getCountries.test.ts new file mode 100644 index 0000000..020735f --- /dev/null +++ b/tests/suite/utils/getCountries.test.ts @@ -0,0 +1,31 @@ +import chai, { expect } from 'chai'; +import * as utils from '../../../src/utils'; +import { StorageKeys } from '../../../src/enums'; +import { TestMemento } from '../../mocks/vscode'; + +chai.should(); + +suite('Utils:getCountries', function() { + + const storage = new TestMemento(); + + this.beforeEach(() => { + storage.storage = new Map(); + }); + + test('returns the most recently picked country selected', async () => { + storage.update(StorageKeys.lastCountrySelected, 'US'); + const countryList = utils.getCountries(storage); + + const selectedItem = countryList.find(f => f.picked); + + expect(selectedItem).to.exist; + expect(selectedItem?.description).to.eq('US'); + }); + + test('returns list of countries with no picked items when never used', async () => { + const countryList = utils.getCountries(storage); + + expect(countryList.find(f => f.picked)).not.to.exist; + }); +}); \ No newline at end of file diff --git a/tests/suite/utils/getExtensionInfo.test.ts b/tests/suite/utils/getExtensionInfo.test.ts new file mode 100644 index 0000000..de6c596 --- /dev/null +++ b/tests/suite/utils/getExtensionInfo.test.ts @@ -0,0 +1,12 @@ +import * as vscode from 'vscode'; +import * as assert from 'assert'; +import * as utils from '../../../src/utils'; + +suite('Utils:getExtensionInfo', () => { + test('returns vonage.vscode extension', async () => { + const extensionInfo = utils.getExtensionInfo(); + + assert.notDeepStrictEqual(extensionInfo, {}); + assert.strictEqual(>extensionInfo.id, 'vonage.vscode'); + }); +}); \ No newline at end of file diff --git a/tests/suite/views/account.test.ts b/tests/suite/views/account.test.ts new file mode 100644 index 0000000..6b893ea --- /dev/null +++ b/tests/suite/views/account.test.ts @@ -0,0 +1,53 @@ +import chai from 'chai'; +import assert from 'assert'; +import Sinon from 'sinon'; +import { VonageClient } from '../../../src/client/vonageClient'; +import { AccountViewDataProvider } from '../../../src/views'; +import { mocks, TestMemento } from '../../mocks'; +import { StorageKeys } from '../../../src/enums'; + +chai.should(); + +suite('Views:Account', function () { + + const storage = new TestMemento(); + let viewProvider: AccountViewDataProvider; + const fakeGetBalance = Sinon.fake.returns(1.784); + Sinon.replace(VonageClient.account, 'getBalance', fakeGetBalance); + + this.beforeEach(function () { + storage.storage = new Map(); + viewProvider = new AccountViewDataProvider(storage); + }); + + test('buildTree hides balance when appropriate', async () => { + storage.update(StorageKeys.hideAccountBalance, true); + + const treeItems = await viewProvider.getChildren(); + + const balanceTreeItem = treeItems[0]; + balanceTreeItem.should.exist; + assert.notDeepStrictEqual(balanceTreeItem.label, undefined); + if (balanceTreeItem && balanceTreeItem.label) { + balanceTreeItem.label.should.eq(`Balance: € ----`); + } + }); + + test('buildTree shows balance when appropriate', async () => { + const treeItems = await viewProvider.getChildren(); + + const balanceTreeItem = treeItems[0]; + balanceTreeItem.should.exist; + assert.notDeepStrictEqual(balanceTreeItem.label, undefined); + if (balanceTreeItem && balanceTreeItem.label) { + balanceTreeItem.label.should.eq(`Balance: € 1.78`); + } + }); + + test('toggleBalanceView toggles hideAccountBalance setting', async () => { + await viewProvider.toggleBalanceView(); + + const shouldHide = storage.get(StorageKeys.hideAccountBalance); + shouldHide.should.eq(true); + }); +}); \ No newline at end of file diff --git a/tests/suite/views/help.test.ts b/tests/suite/views/help.test.ts new file mode 100644 index 0000000..163150a --- /dev/null +++ b/tests/suite/views/help.test.ts @@ -0,0 +1,16 @@ +import chai from 'chai'; +import assert from 'assert'; +import { HelpViewDataProvider } from '../../../src/views'; + +chai.should(); + +suite('Views:Help', function () { + + const viewProvider = new HelpViewDataProvider(); + + test('buildTree provides the correct items', async () => { + const treeItems = await viewProvider.getChildren(); + + treeItems.length.should.eq(4); + }); +}); \ No newline at end of file diff --git a/tests/suite/views/number.test.ts b/tests/suite/views/number.test.ts new file mode 100644 index 0000000..9f4c5b5 --- /dev/null +++ b/tests/suite/views/number.test.ts @@ -0,0 +1,48 @@ +import * as vscode from 'vscode'; +import chai from 'chai'; +import Sinon from 'sinon'; +import assert from 'assert'; +import { NumbersViewDataProvider, NumberTreeItem } from '../../../src/views'; +import { TestMemento, vonage } from '../../mocks'; +import { VonageClient } from '../../../src/client/vonageClient'; + +chai.should(); + +suite('Views:Number', function () { + + const storage = new TestMemento(); + const viewProvider = new NumbersViewDataProvider(storage); + const fakeGetNumbers = Sinon.fake.returns([vonage.numberMock]); + Sinon.replace(VonageClient.numbers, 'getNumbers', fakeGetNumbers); + + this.beforeEach(function () { + storage.storage = new Map(); + }); + + test('buildTree displays numbers correctly', async () => { + const treeItems = await viewProvider.getChildren(); + + treeItems.length.should.eq(1); + + const treeItem = treeItems[0]; + treeItem.should.exist; + assert.notDeepStrictEqual(treeItem.label, undefined); + if (treeItem && treeItem.label) { + treeItem.label.should.eq(`+${vonage.numberMock.msisdn}`); + } + }); + + test('copyNumber adds the msisdn to the clipboard', async () => { + const windowShowInformationMessageStub = Sinon.stub(vscode.window, 'showInformationMessage'); + const node = new NumberTreeItem(vonage.numberMock); + + await viewProvider.copyNumber(node); + + const clipboard = await vscode.env.clipboard.readText(); + + clipboard.should.eq(vonage.numberMock.msisdn); + windowShowInformationMessageStub.calledOnce.should.eq(true); + + windowShowInformationMessageStub.restore(); + }); +}); \ No newline at end of file diff --git a/tests/workspace/.vscode/settings.json b/tests/workspace/.vscode/settings.json new file mode 100644 index 0000000..b6f5106 --- /dev/null +++ b/tests/workspace/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "telemetry.enableTelemetry": true, + "vonage.telemetry.enabled": true +} \ No newline at end of file diff --git a/test/workspace/emptyFile.ts b/tests/workspace/emptyFile.ts similarity index 100% rename from test/workspace/emptyFile.ts rename to tests/workspace/emptyFile.ts diff --git a/tsconfig.json b/tsconfig.json index 034917a..761a5a8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,9 +9,7 @@ }, "include": [ "src", - "test", - "test/suite/fakes", - ".github/prompts" + "tests" ], "exclude": [ "node_modules",