Skip to content

Commit

Permalink
added new command to create new SQL query document
Browse files Browse the repository at this point in the history
  • Loading branch information
llali committed Dec 29, 2016
1 parent acafa52 commit f3bed39
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 3 deletions.
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"onCommand:extension.manageProfiles",
"onCommand:extension.chooseDatabase",
"onCommand:extension.cancelQuery",
"onCommand:extension.showGettingStarted"
"onCommand:extension.showGettingStarted",
"onCommand:extension.newQuery"
],
"main": "./out/src/extension",
"extensionDependencies": [
Expand Down Expand Up @@ -179,6 +180,11 @@
"command": "extension.showGettingStarted",
"title": "Getting Started Guide",
"category": "MS SQL"
},
{
"command": "extension.newQuery",
"title": "New query",
"category": "MS SQL"
}
],
"keybindings": [
Expand All @@ -199,6 +205,11 @@
"key": "ctrl+shift+d",
"mac": "cmd+shift+d",
"when": "editorTextFocus && editorLangId == 'sql'"
},
{
"command": "extension.newQuery",
"key": "ctrl+shift+n",
"mac": "cmd+shift+n+q"
}
],
"configuration": {
Expand Down
29 changes: 27 additions & 2 deletions src/controllers/mainController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { IPrompter } from '../prompts/question';
import CodeAdapter from '../prompts/adapter';
import Telemetry from '../models/telemetry';
import VscodeWrapper from './vscodeWrapper';
import UntitledSqlDocumentService from './untitledSqlDocumentService';
import { ISelectionData } from './../models/interfaces';
import * as path from 'path';
import fs = require('fs');
Expand All @@ -38,20 +39,29 @@ export default class MainController implements vscode.Disposable {
private _lastSavedTimer: Utils.Timer;
private _lastOpenedUri: string;
private _lastOpenedTimer: Utils.Timer;
private _untitledSqlDocumentService: UntitledSqlDocumentService;

/**
* The main controller constructor
* @constructor
*/
constructor(context: vscode.ExtensionContext,
connectionManager?: ConnectionManager,
vscodeWrapper?: VscodeWrapper) {
vscodeWrapper?: VscodeWrapper,
untitledSqlDocumentService?: UntitledSqlDocumentService) {
this._context = context;
if (connectionManager) {
this._connectionMgr = connectionManager;
}
if (vscodeWrapper) {
this._vscodeWrapper = vscodeWrapper;
} else {
this._vscodeWrapper = new VscodeWrapper();
}
if (untitledSqlDocumentService) {
this._untitledSqlDocumentService = untitledSqlDocumentService;
} else {
this._untitledSqlDocumentService = new UntitledSqlDocumentService(this._vscodeWrapper);
}
}

Expand Down Expand Up @@ -104,8 +114,10 @@ export default class MainController implements vscode.Disposable {
this._event.on(Constants.cmdCancelQuery, () => { self.onCancelQuery(); });
this.registerCommand(Constants.cmdShowGettingStarted);
this._event.on(Constants.cmdShowGettingStarted, () => { self.launchGettingStartedPage(); });
this.registerCommand(Constants.cmdNewQuery);
this._event.on(Constants.cmdNewQuery, () => { self.runAndLogErrors(self.onNewQuery(), 'onNewQuery'); });

this._vscodeWrapper = new VscodeWrapper();
// this._vscodeWrapper = new VscodeWrapper();

// Add handlers for VS Code generated commands
this._vscodeWrapper.onDidCloseTextDocument(params => this.onDidCloseTextDocument(params));
Expand Down Expand Up @@ -306,6 +318,10 @@ export default class MainController implements vscode.Disposable {
this._connectionMgr = connectionManager;
}

public set untitledSqlDocumentService(untitledSqlDocumentService: UntitledSqlDocumentService) {
this._untitledSqlDocumentService = untitledSqlDocumentService;
}


/**
* Verifies the extension is initilized and if not shows an error message
Expand Down Expand Up @@ -360,6 +376,15 @@ export default class MainController implements vscode.Disposable {
opener(Constants.gettingStartedGuideLink);
}

/**
* Opens a new query and creates new connection
*/
public onNewQuery(): Promise<boolean> {
return this._untitledSqlDocumentService.newQuery().then(x => {
return this._connectionMgr.onNewConnection();
});
}

/**
* Check if the extension launched file exists.
* This is to detect when we are running in a clean install scenario.
Expand Down
62 changes: 62 additions & 0 deletions src/controllers/untitledSqlDocumentService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import VscodeWrapper from './vscodeWrapper';
import vscode = require('vscode');
import path = require('path');
import os = require('os');
const fs = require('fs');

/**
* Service for creating untitled documents for SQL query
*/
export default class UntitledSqlDocumentService {
private _counter: number = 1;

constructor(private vscodeWrapper: VscodeWrapper) {
}

/**
* Creates new untitled document for SQL query and opens in new editor tab
*/
public newQuery(): Promise<boolean> {

return new Promise<boolean>((resolve, reject) => {
try {
let filePath = this.createUntitledFilePath();
let docUri: vscode.Uri = vscode.Uri.parse('untitled:' + filePath);

// Open an untitled document. So the file doesn't have to exist in disk
this.vscodeWrapper.openTextDocument(docUri).then(doc => {
// Show the new untitled document in the editor's first tab and change the focus to it.
this.vscodeWrapper.showTextDocument(doc, 1, false).then(textDoc => {
this._counter++;
resolve(true);
});
});
} catch (error) {
reject(error);
}
});
}

private createUntitledFilePath(): string {
let filePath = UntitledSqlDocumentService.createFilePath(this._counter);
while (fs.existsSync(filePath)) {
this._counter++;
filePath = UntitledSqlDocumentService.createFilePath(this._counter);
}
while (this.vscodeWrapper.textDocuments.find(x => x.fileName.toUpperCase() === filePath.toUpperCase())) {
this._counter++;
filePath = UntitledSqlDocumentService.createFilePath(this._counter);
}
return filePath;
}

public static createFilePath(counter: number): string {
return path.join(os.tmpdir(), `SQLQuery${counter}.sql`);
}
}

1 change: 1 addition & 0 deletions src/models/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const cmdDisconnect = 'extension.disconnect';
export const cmdChooseDatabase = 'extension.chooseDatabase';
export const cmdShowReleaseNotes = 'extension.showReleaseNotes';
export const cmdShowGettingStarted = 'extension.showGettingStarted';
export const cmdNewQuery = 'extension.newQuery';
export const cmdManageConnectionProfiles = 'extension.manageProfiles';

export const sqlDbPrefix = '.database.windows.net';
Expand Down
27 changes: 27 additions & 0 deletions test/mainController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as TypeMoq from 'typemoq';
import vscode = require('vscode');
import MainController from '../src/controllers/mainController';
import ConnectionManager from '../src/controllers/connectionManager';
import UntitledSqlDocumentService from '../src/controllers/untitledSqlDocumentService';
import * as Extension from '../src/extension';
import Constants = require('../src/models/constants');
import assert = require('assert');
Expand All @@ -13,6 +14,7 @@ suite('MainController Tests', () => {
let newDocument: vscode.TextDocument;
let mainController: MainController;
let connectionManager: TypeMoq.Mock<ConnectionManager>;
let untitledSqlDocumentService: TypeMoq.Mock<UntitledSqlDocumentService>;
let docUri: string;
let newDocUri: string;
let docUriCallback: string;
Expand Down Expand Up @@ -53,6 +55,9 @@ suite('MainController Tests', () => {
connectionManager = TypeMoq.Mock.ofType(ConnectionManager);
mainController.connectionManager = connectionManager.object;

untitledSqlDocumentService = TypeMoq.Mock.ofType(UntitledSqlDocumentService);
mainController.untitledSqlDocumentService = untitledSqlDocumentService.object;

// Watching these functions and input paramters
connectionManager.setup(x => x.onDidOpenTextDocument(TypeMoq.It.isAny())).callback((doc) => {
docUriCallback = doc.uri.toString();
Expand Down Expand Up @@ -173,4 +178,26 @@ suite('MainController Tests', () => {
}
});

test('onNewQuery should call the new query and new connection' , () => {

untitledSqlDocumentService.setup(x => x.newQuery()).returns(() => Promise.resolve(true));
connectionManager.setup(x => x.onNewConnection()).returns(() => Promise.resolve(true));

return mainController.onNewQuery().then(result => {
untitledSqlDocumentService.verify(x => x.newQuery(), TypeMoq.Times.once());
connectionManager.verify(x => x.onNewConnection(), TypeMoq.Times.once());
});
});

test('onNewQuery should not call the new connection if new query fails' , done => {

untitledSqlDocumentService.setup(x => x.newQuery()).returns(() => { return Promise.reject<boolean>('error'); } );
connectionManager.setup(x => x.onNewConnection()).returns(() => { return Promise.resolve(true); } );

mainController.onNewQuery().catch(error => {
untitledSqlDocumentService.verify(x => x.newQuery(), TypeMoq.Times.once());
connectionManager.verify(x => x.onNewConnection(), TypeMoq.Times.never());
done();
});
});
});
128 changes: 128 additions & 0 deletions test/untitledSqlDocumentService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import * as TypeMoq from 'typemoq';
import vscode = require('vscode');
import UntitledSqlDocumentService from '../src/controllers/untitledSqlDocumentService';
import VscodeWrapper from '../src/controllers/VscodeWrapper';
const fse = require('fs-extra');
const fs = require('fs');

interface IFixture {
openDocResult: Promise<vscode.TextDocument>;
showDocResult: Promise<vscode.TextEditor>;
vscodeWrapper: TypeMoq.Mock<VscodeWrapper>;
service: UntitledSqlDocumentService;
textDocuments: vscode.TextDocument[];
}

suite('UntitledSqlDocumentService Tests', () => {

function createTextDocumentObject(fileName: string = ''): vscode.TextDocument {
return {
uri: undefined,
fileName: fileName,
getText: undefined,
getWordRangeAtPosition: undefined,
isDirty: true,
isUntitled: true,
languageId: 'sql',
lineAt: undefined,
lineCount: undefined,
offsetAt: undefined,
positionAt: undefined,
save: undefined,
validatePosition: undefined,
validateRange: undefined,
version: undefined
};
}

function createUntitledSqlDocumentService(fixture: IFixture): IFixture {
let vscodeWrapper: TypeMoq.Mock<VscodeWrapper>;
vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper);

vscodeWrapper.setup(x => x.textDocuments).returns(() => { return fixture.textDocuments; });
vscodeWrapper.setup(x => x.openTextDocument(TypeMoq.It.isAny()))
.returns(() => { return Promise.resolve(createTextDocumentObject()); });
vscodeWrapper.setup(x => x.showTextDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => { return Promise.resolve(TypeMoq.It.isAny()); });
fixture.vscodeWrapper = vscodeWrapper;
fixture.service = new UntitledSqlDocumentService(vscodeWrapper.object);
return fixture;
}

test('newQuery should open a new untitled document and show in new tab' , () => {
let fixture: IFixture = {
openDocResult: Promise.resolve(createTextDocumentObject()),
showDocResult: Promise.resolve(TypeMoq.It.isAny()),
service: undefined,
vscodeWrapper: undefined,
textDocuments: []
};
fixture = createUntitledSqlDocumentService(fixture);

return fixture.service.newQuery().then(result => {
fixture.vscodeWrapper.verify(x => x.openTextDocument(
TypeMoq.It.is<vscode.Uri>(d => d.scheme === 'untitled')), TypeMoq.Times.once());
fixture.vscodeWrapper.verify(x => x.showTextDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
});
});

test('newQuery should increment the counter for untitled document if file exits' , () => {
let fixture: IFixture = {
openDocResult: Promise.resolve(createTextDocumentObject()),
showDocResult: Promise.resolve(TypeMoq.It.isAny()),
service: undefined,
vscodeWrapper: undefined,
textDocuments: []
};
let counter = getCounterForUntitledFile(1);
fixture = createUntitledSqlDocumentService(fixture);
let filePath = UntitledSqlDocumentService.createFilePath(counter);
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, 'test');
}
return fixture.service.newQuery().then(result => {
fixture.vscodeWrapper.verify(x => x.openTextDocument(
TypeMoq.It.is<vscode.Uri>(d => verifyDocumentUri(d, counter + 1))), TypeMoq.Times.once());
fixture.vscodeWrapper.verify(x => x.showTextDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
if (!fs.existsSync(filePath)) {
fse.remove(filePath, undefined);
}
});
});

function verifyDocumentUri(uri: vscode.Uri, expectedNumber: number): boolean {
return uri.scheme === 'untitled' && uri.path.endsWith(`${expectedNumber}.sql`);
}

function getCounterForUntitledFile(start: number): number {
let counter = start;
let filePath = UntitledSqlDocumentService.createFilePath(counter);
while (fs.existsSync(filePath)) {
counter++;
filePath = UntitledSqlDocumentService.createFilePath(counter);
}
return counter;
}

test('newQuery should increment the counter for untitled document given text documents already open with current counter' , () => {
let counter = getCounterForUntitledFile(1);
let fixture: IFixture = {
openDocResult: Promise.resolve(createTextDocumentObject()),
showDocResult: Promise.resolve(TypeMoq.It.isAny()),
service: undefined,
vscodeWrapper: undefined,
textDocuments: [
createTextDocumentObject(UntitledSqlDocumentService.createFilePath(counter + 1)),
createTextDocumentObject(UntitledSqlDocumentService.createFilePath(counter))]
};
fixture = createUntitledSqlDocumentService(fixture);
let service = fixture.service;

return service.newQuery().then(result => {
fixture.vscodeWrapper.verify(x => x.openTextDocument(
TypeMoq.It.is<vscode.Uri>(d => verifyDocumentUri(d, counter + 2))), TypeMoq.Times.once());
fixture.vscodeWrapper.verify(x => x.showTextDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
});
});
});

0 comments on commit f3bed39

Please sign in to comment.