Copy results supports copy with headers (#386)
Fixes #368. - Added a config option to support copy with column name header. Defaults to false to match SSMS behavior - Support this during the copy event - Context menu support for both Copy and Copy with Headers - Unit tests will be added once the main unit test PR for the ResultsView is merged, since this has the necessary hooks and files. - includes tests to cover all inputs to copyResults
This commit is contained in:
Родитель
c2bb0e5320
Коммит
f1dcce68d4
|
@ -6,7 +6,8 @@
|
|||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
"out": true, // set this to false to include "out" folder in search results
|
||||
"coverage": true
|
||||
},
|
||||
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
|
||||
"files.watcherExclude": {
|
||||
|
|
|
@ -381,6 +381,7 @@
|
|||
"event.prevGrid": "ctrl+up",
|
||||
"event.nextGrid": "ctrl+down",
|
||||
"event.copySelection": "ctrl+c",
|
||||
"event.copyWithHeaders": "",
|
||||
"event.maximizeGrid": "",
|
||||
"event.selectAll": "",
|
||||
"event.saveAsJSON": "",
|
||||
|
@ -400,6 +401,11 @@
|
|||
"default": true,
|
||||
"description": "[Optional] When true, column headers are included in CSV"
|
||||
}
|
||||
},
|
||||
"mssql.copyIncludeHeaders": {
|
||||
"type": "boolean",
|
||||
"description": "[Optional] Configuration options for copying results from the Results View",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,20 +233,44 @@ export default class QueryRunner {
|
|||
});
|
||||
}
|
||||
|
||||
private getColumnHeaders(batchId: number, resultId: number, range: ISlickRange): string[] {
|
||||
let headers: string[] = undefined;
|
||||
let batchSummary: BatchSummary = this.batchSets[batchId];
|
||||
if (batchSummary !== undefined) {
|
||||
let resultSetSummary = batchSummary.resultSetSummaries[resultId];
|
||||
headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
|
||||
return info.columnName;
|
||||
});
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the result range to the system clip-board
|
||||
* @param selection The selection range array to copy
|
||||
* @param batchId The id of the batch to copy from
|
||||
* @param resultId The id of the result to copy from
|
||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||
*/
|
||||
public copyResults(selection: ISlickRange[], batchId: number, resultId: number): Promise<void> {
|
||||
public copyResults(selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): Promise<void> {
|
||||
const self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let copyString = '';
|
||||
|
||||
// create a mapping of the ranges to get promises
|
||||
let tasks = selection.map((range, i) => {
|
||||
return () => {
|
||||
return self.getRows(range.fromRow, range.toRow - range.fromRow + 1, batchId, resultId).then((result) => {
|
||||
if (self.shouldIncludeHeaders(includeHeaders)) {
|
||||
let columnHeaders = self.getColumnHeaders(batchId, resultId, range);
|
||||
if (columnHeaders !== undefined) {
|
||||
for (let header of columnHeaders) {
|
||||
copyString += header + '\t';
|
||||
}
|
||||
copyString += '\r\n';
|
||||
}
|
||||
}
|
||||
|
||||
// iterate over the rows to paste into the copy string
|
||||
for (let row of result.resultSubset.rows) {
|
||||
// iterate over the cells we want from that row
|
||||
|
@ -271,6 +295,17 @@ export default class QueryRunner {
|
|||
});
|
||||
}
|
||||
|
||||
private shouldIncludeHeaders(includeHeaders: boolean): boolean {
|
||||
if (includeHeaders !== undefined) {
|
||||
// Respect the value explicity passed into the method
|
||||
return includeHeaders;
|
||||
}
|
||||
// else get config option from vscode config
|
||||
let config = this._vscodeWrapper.getConfiguration(Constants.extensionConfigSectionName);
|
||||
includeHeaders = config[Constants.copyIncludeHeaders];
|
||||
return !!includeHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a selection range in the editor for this query
|
||||
* @param selection The selection range to select
|
||||
|
|
|
@ -211,8 +211,9 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
|
|||
let uri = req.query.uri;
|
||||
let resultId = req.query.resultId;
|
||||
let batchId = req.query.batchId;
|
||||
let includeHeaders = req.query.includeHeaders;
|
||||
let selection: Interfaces.ISlickRange[] = req.body;
|
||||
self._queryResultsMap.get(uri).queryRunner.copyResults(selection, batchId, resultId).then(() => {
|
||||
self._queryResultsMap.get(uri).queryRunner.copyResults(selection, batchId, resultId, includeHeaders).then(() => {
|
||||
res.status = 200;
|
||||
res.send();
|
||||
});
|
||||
|
|
|
@ -45,6 +45,7 @@ export const outputServiceLocalhost = 'http://localhost:';
|
|||
export const msgContentProviderSqlOutputHtml = 'dist/html/sqlOutput.ejs';
|
||||
export const contentProviderMinFile = 'dist/js/app.min.js';
|
||||
|
||||
export const copyIncludeHeaders = 'copyIncludeHeaders';
|
||||
export const configLogDebugInfo = 'logDebugInfo';
|
||||
export const configMyConnections = 'connections';
|
||||
export const configSaveAsCsv = 'saveAsCsv';
|
||||
|
|
|
@ -5,4 +5,8 @@
|
|||
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.saveAsJSON']}}</span></li>
|
||||
<li id="selectall" (click)="handleContextActionClick('selectall')" [class.disabled]="isDisabled"> {{Constants.selectAll}}
|
||||
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.selectAll']}}</span></li>
|
||||
<li id="copy" (click)="handleContextActionClick('copySelection')" [class.disabled]="isDisabled"> {{Constants.copyLabel}}
|
||||
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.copySelection']}}</span></li>
|
||||
<li id="copyWithHeaders" (click)="handleContextActionClick('copyWithHeaders')" [class.disabled]="isDisabled"> {{Constants.copyWithHeadersLabel}}
|
||||
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.copyWithHeaders']}}</span></li>
|
||||
</ul>
|
|
@ -165,6 +165,12 @@ export class AppComponent implements OnInit, AfterViewChecked {
|
|||
let selection = this.slickgrids.toArray()[activeGrid].getSelectedRanges();
|
||||
this.dataService.copyResults(selection, this.renderedDataSets[activeGrid].batchId, this.renderedDataSets[activeGrid].resultId);
|
||||
},
|
||||
'event.copyWithHeaders': () => {
|
||||
let activeGrid = this.activeGrid;
|
||||
let selection = this.slickgrids.toArray()[activeGrid].getSelectedRanges();
|
||||
this.dataService.copyResults(selection, this.renderedDataSets[activeGrid].batchId,
|
||||
this.renderedDataSets[activeGrid].resultId, true);
|
||||
},
|
||||
'event.maximizeGrid': () => {
|
||||
this.magnify(this.activeGrid);
|
||||
},
|
||||
|
@ -441,6 +447,13 @@ export class AppComponent implements OnInit, AfterViewChecked {
|
|||
case 'selectall':
|
||||
this.activeGrid = event.index;
|
||||
this.shortcutfunc['event.selectAll']();
|
||||
break;
|
||||
case 'copySelection':
|
||||
this.dataService.copyResults(event.selection, event.batchId, event.resultId);
|
||||
break;
|
||||
case 'copyWithHeaders':
|
||||
this.dataService.copyResults(event.selection, event.batchId, event.resultId, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,10 @@ const template = `
|
|||
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.saveAsJSON']}}</span></li>
|
||||
<li id="selectall" (click)="handleContextActionClick('selectall')" [class.disabled]="isDisabled"> {{Constants.selectAll}}
|
||||
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.selectAll']}}</span></li>
|
||||
<li id="copy" (click)="handleContextActionClick('copySelection')" [class.disabled]="isDisabled"> {{Constants.copyLabel}}
|
||||
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.copySelection']}}</span></li>
|
||||
<li id="copyWithHeaders" (click)="handleContextActionClick('copyWithHeaders')" [class.disabled]="isDisabled"> {{Constants.copyWithHeadersLabel}}
|
||||
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.copyWithHeaders']}}</span></li>
|
||||
</ul>
|
||||
`;
|
||||
|
||||
|
@ -48,7 +52,9 @@ export class ContextMenu implements OnInit {
|
|||
private keys = {
|
||||
'event.saveAsCSV': '',
|
||||
'event.saveAsJSON': '',
|
||||
'event.selectAll': ''
|
||||
'event.selectAll': '',
|
||||
'event.copySelection': '',
|
||||
'event.copyWithHeaders': ''
|
||||
};
|
||||
|
||||
constructor(@Inject(forwardRef(() => ShortcutService)) private shortcuts: ShortcutService) {
|
||||
|
|
|
@ -5,6 +5,8 @@ export const saveCSVLabel = 'Save as CSV';
|
|||
export const saveJSONLabel = 'Save as JSON';
|
||||
export const resultPaneLabel = 'Results';
|
||||
export const selectAll = 'Select all';
|
||||
export const copyLabel = 'Copy';
|
||||
export const copyWithHeadersLabel = 'Copy with Headers';
|
||||
|
||||
/** Messages Pane Labels */
|
||||
export const executeQueryLabel = 'Executing query...';
|
||||
|
|
|
@ -119,11 +119,15 @@ export class DataService {
|
|||
* @param selection The selection range to copy
|
||||
* @param batchId The batch id of the result to copy from
|
||||
* @param resultId The result id of the result to copy from
|
||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||
*/
|
||||
copyResults(selection: ISlickRange[], batchId: number, resultId: number): void {
|
||||
copyResults(selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||
const self = this;
|
||||
let headers = new Headers();
|
||||
let url = '/copyResults?' + '&uri=' + self.uri + '&batchId=' + batchId + '&resultId=' + resultId;
|
||||
if (includeHeaders !== undefined) {
|
||||
url += '&includeHeaders=' + includeHeaders;
|
||||
}
|
||||
self.http.post(url, selection, { headers: headers }).subscribe();
|
||||
}
|
||||
|
||||
|
|
|
@ -481,6 +481,23 @@ describe('AppComponent', function (): void {
|
|||
}, 100);
|
||||
});
|
||||
|
||||
it('event copy with headers', (done) => {
|
||||
let dataService = <MockDataService> fixture.componentRef.injector.get(DataService);
|
||||
let shortcutService = <MockShortcutService> fixture.componentRef.injector.get(ShortcutService);
|
||||
spyOn(shortcutService, 'buildEventString').and.returnValue('');
|
||||
spyOn(shortcutService, 'getEvent').and.returnValue(Promise.resolve('event.copyWithHeaders'));
|
||||
spyOn(dataService, 'copyResults');
|
||||
dataService.sendWSEvent(batch1);
|
||||
dataService.sendWSEvent(completeEvent);
|
||||
fixture.detectChanges();
|
||||
triggerKeyEvent(40, ele);
|
||||
setTimeout(() => {
|
||||
fixture.detectChanges();
|
||||
expect(dataService.copyResults).toHaveBeenCalledWith([], 0, 0, true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('event maximize grid', (done) => {
|
||||
|
||||
let dataService = <MockDataService> fixture.componentRef.injector.get(DataService);
|
||||
|
|
|
@ -6,7 +6,9 @@ class MockShortCutService {
|
|||
private keyToString = {
|
||||
'event.saveAsCSV': 'ctrl+s',
|
||||
'event.saveAsJSON': 'ctrl+shift+s',
|
||||
'event.selectAll': 'ctrl+a'
|
||||
'event.selectAll': 'ctrl+a',
|
||||
'event.copySelection': 'ctrl+c',
|
||||
'event.copyWithHeaders': 'ctrl+shift+c'
|
||||
};
|
||||
public stringCodeFor(value: string): Promise<string> {
|
||||
return Promise.resolve(this.keyToString[value]);
|
||||
|
@ -63,7 +65,7 @@ describe('context Menu', () => {
|
|||
comp.show(0, 0, 0, 0, 0, []);
|
||||
fixture.detectChanges();
|
||||
expect(ele.firstElementChild.className.indexOf('hidden')).toEqual(-1);
|
||||
expect(ele.firstElementChild.childElementCount).toEqual(3);
|
||||
expect(ele.firstElementChild.childElementCount).toEqual(5, 'expect 5 menu items to be present');
|
||||
});
|
||||
|
||||
it('hides correctly', () => {
|
||||
|
|
|
@ -110,6 +110,7 @@ describe('data service', () => {
|
|||
let param = getParamsFromUrl(conn.request.url);
|
||||
expect(param['batchId']).toEqual('0');
|
||||
expect(param['resultId']).toEqual('0');
|
||||
expect(param['includeHeaders']).toEqual(undefined);
|
||||
let body = JSON.parse(conn.request.getBody());
|
||||
expect(body).toBeDefined();
|
||||
expect(body).toEqual([]);
|
||||
|
@ -120,6 +121,25 @@ describe('data service', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('copy with headers request', () => {
|
||||
it('correctly threads through the data', (done) => {
|
||||
mockbackend.connections.subscribe((conn: MockConnection) => {
|
||||
let isCopyRequest = urlMatch(conn.request, /\/copyResults/, RequestMethod.Post);
|
||||
expect(isCopyRequest).toBe(true);
|
||||
let param = getParamsFromUrl(conn.request.url);
|
||||
expect(param['batchId']).toEqual('0');
|
||||
expect(param['resultId']).toEqual('0');
|
||||
expect(param['includeHeaders']).toEqual('true');
|
||||
let body = JSON.parse(conn.request.getBody());
|
||||
expect(body).toBeDefined();
|
||||
expect(body).toEqual([]);
|
||||
done();
|
||||
});
|
||||
|
||||
dataservice.copyResults([], 0, 0, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('set selection request', () => {
|
||||
it('correctly threads through the data', (done) => {
|
||||
mockbackend.connections.subscribe((conn: MockConnection) => {
|
||||
|
|
|
@ -4,10 +4,12 @@ import QueryRunner from './../src/controllers/QueryRunner';
|
|||
import { QueryNotificationHandler } from './../src/controllers/QueryNotificationHandler';
|
||||
import { SqlOutputContentProvider } from './../src/models/SqlOutputContentProvider';
|
||||
import SqlToolsServerClient from './../src/languageservice/serviceclient';
|
||||
import { QueryExecuteParams, QueryExecuteCompleteNotificationResult } from './../src/models/contracts/queryExecute';
|
||||
import { QueryExecuteParams, QueryExecuteCompleteNotificationResult, ResultSetSummary } from './../src/models/contracts/queryExecute';
|
||||
import VscodeWrapper from './../src/controllers/vscodeWrapper';
|
||||
import StatusView from './../src/views/statusView';
|
||||
import * as Constants from '../src/models/constants';
|
||||
import { ISlickRange } from './../src/models/interfaces';
|
||||
import * as stubs from './stubs';
|
||||
|
||||
const ncp = require('copy-paste');
|
||||
|
||||
|
@ -195,16 +197,28 @@ suite('Query Runner tests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('Correctly copy pastes a selection', () => {
|
||||
function setupWorkspaceConfig(configResult: {[key: string]: any}): void {
|
||||
let config = stubs.createWorkspaceConfiguration(configResult);
|
||||
testVscodeWrapper.setup(x => x.getConfiguration(TypeMoq.It.isAny()))
|
||||
.returns(x => {
|
||||
return config;
|
||||
});
|
||||
}
|
||||
|
||||
suite('Copy Tests', () => {
|
||||
// ------ Common inputs and setup for copy tests -------
|
||||
const TAB = '\t';
|
||||
const CLRF = '\r\n';
|
||||
const finalString = '1' + TAB + '2' + TAB + CLRF +
|
||||
const finalStringNoHeader = '1' + TAB + '2' + TAB + CLRF +
|
||||
'3' + TAB + '4' + TAB + CLRF +
|
||||
'5' + TAB + '6' + TAB + CLRF +
|
||||
'7' + TAB + '8' + TAB + CLRF +
|
||||
'9' + TAB + '10' + TAB + CLRF;
|
||||
let testuri = 'test';
|
||||
let testresult = {
|
||||
|
||||
const finalStringWithHeader = 'Col1' + TAB + 'Col2' + TAB + CLRF + finalStringNoHeader;
|
||||
|
||||
const testuri = 'test';
|
||||
const testresult = {
|
||||
message: '',
|
||||
resultSubset: {
|
||||
rowCount: 5,
|
||||
|
@ -217,24 +231,142 @@ suite('Query Runner tests', () => {
|
|||
]
|
||||
}
|
||||
};
|
||||
|
||||
let testRange: ISlickRange[] = [{fromCell: 0, fromRow: 0, toCell: 1, toRow: 4}];
|
||||
testSqlToolsServerClient.setup(x => x.sendRequest(TypeMoq.It.isAny(),
|
||||
TypeMoq.It.isAny())).callback(() => {
|
||||
// testing
|
||||
}).returns(() => { return Promise.resolve(testresult); });
|
||||
testStatusView.setup(x => x.executingQuery(TypeMoq.It.isAnyString()));
|
||||
let queryRunner = new QueryRunner(
|
||||
testuri,
|
||||
testuri,
|
||||
testStatusView.object,
|
||||
testSqlToolsServerClient.object,
|
||||
testQueryNotificationHandler.object,
|
||||
testVscodeWrapper.object
|
||||
);
|
||||
queryRunner.uri = testuri;
|
||||
return queryRunner.copyResults(testRange, 0, 0).then(() => {
|
||||
let pasteContents = ncp.paste();
|
||||
assert.equal(pasteContents, finalString);
|
||||
|
||||
let result: QueryExecuteCompleteNotificationResult = {
|
||||
ownerUri: testuri,
|
||||
message: undefined,
|
||||
batchSummaries: [{
|
||||
hasError: false,
|
||||
id: 0,
|
||||
selection: {startLine: 0, endLine: 0, startColumn: 3, endColumn: 3},
|
||||
messages: [{time: '', message: '6 affects rows'}],
|
||||
resultSetSummaries: <ResultSetSummary[]> [{
|
||||
id: 0,
|
||||
rowCount: 5,
|
||||
columnInfo: [
|
||||
{ columnName: 'Col1' },
|
||||
{ columnName: 'Col2' }
|
||||
]
|
||||
}],
|
||||
executionElapsed: undefined,
|
||||
executionStart: new Date().toISOString(),
|
||||
executionEnd: new Date().toISOString()
|
||||
}]
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
testSqlToolsServerClient.setup(x => x.sendRequest(TypeMoq.It.isAny(),
|
||||
TypeMoq.It.isAny())).callback(() => {
|
||||
// testing
|
||||
}).returns(() => { return Promise.resolve(testresult); });
|
||||
testStatusView.setup(x => x.executingQuery(TypeMoq.It.isAnyString()));
|
||||
testStatusView.setup(x => x.executedQuery(TypeMoq.It.isAnyString()));
|
||||
testVscodeWrapper.setup( x => x.logToOutputChannel(TypeMoq.It.isAnyString()));
|
||||
});
|
||||
|
||||
// ------ Copy tests -------
|
||||
test('Correctly copy pastes a selection', () => {
|
||||
let configResult: {[key: string]: any} = {};
|
||||
configResult[Constants.copyIncludeHeaders] = false;
|
||||
setupWorkspaceConfig(configResult);
|
||||
|
||||
let queryRunner = new QueryRunner(
|
||||
testuri,
|
||||
testuri,
|
||||
testStatusView.object,
|
||||
testSqlToolsServerClient.object,
|
||||
testQueryNotificationHandler.object,
|
||||
testVscodeWrapper.object
|
||||
);
|
||||
queryRunner.uri = testuri;
|
||||
return queryRunner.copyResults(testRange, 0, 0).then(() => {
|
||||
let pasteContents = ncp.paste();
|
||||
assert.equal(pasteContents, finalStringNoHeader);
|
||||
});
|
||||
});
|
||||
|
||||
test('Copies selection with column headers set in user config', () => {
|
||||
// Set column headers in the user config settings
|
||||
let configResult: {[key: string]: any} = {};
|
||||
configResult[Constants.copyIncludeHeaders] = true;
|
||||
setupWorkspaceConfig(configResult);
|
||||
|
||||
let queryRunner = new QueryRunner(
|
||||
testuri,
|
||||
testuri,
|
||||
testStatusView.object,
|
||||
testSqlToolsServerClient.object,
|
||||
testQueryNotificationHandler.object,
|
||||
testVscodeWrapper.object
|
||||
);
|
||||
queryRunner.uri = testuri;
|
||||
queryRunner.dataResolveReject = {resolve: () => {
|
||||
// Needed to handle the result callback
|
||||
}};
|
||||
// Call handleResult to ensure column header info is seeded
|
||||
queryRunner.handleResult(result);
|
||||
return queryRunner.copyResults(testRange, 0, 0).then(() => {
|
||||
let pasteContents = ncp.paste();
|
||||
assert.equal(pasteContents, finalStringWithHeader);
|
||||
});
|
||||
});
|
||||
|
||||
test('Copies selection with headers when true passed as parameter', () => {
|
||||
// Do not set column config in user settings
|
||||
let configResult: {[key: string]: any} = {};
|
||||
configResult[Constants.copyIncludeHeaders] = false;
|
||||
setupWorkspaceConfig(configResult);
|
||||
|
||||
let queryRunner = new QueryRunner(
|
||||
testuri,
|
||||
testuri,
|
||||
testStatusView.object,
|
||||
testSqlToolsServerClient.object,
|
||||
testQueryNotificationHandler.object,
|
||||
testVscodeWrapper.object
|
||||
);
|
||||
queryRunner.uri = testuri;
|
||||
queryRunner.dataResolveReject = {resolve: () => {
|
||||
// Needed to handle the result callback
|
||||
}};
|
||||
// Call handleResult to ensure column header info is seeded
|
||||
queryRunner.handleResult(result);
|
||||
|
||||
// call copyResults with additional parameter indicating to include headers
|
||||
return queryRunner.copyResults(testRange, 0, 0, true).then(() => {
|
||||
let pasteContents = ncp.paste();
|
||||
assert.equal(pasteContents, finalStringWithHeader);
|
||||
});
|
||||
});
|
||||
|
||||
test('Copies selection without headers when false passed as parameter', () => {
|
||||
// Set column config in user settings
|
||||
let configResult: {[key: string]: any} = {};
|
||||
configResult[Constants.copyIncludeHeaders] = true;
|
||||
setupWorkspaceConfig(configResult);
|
||||
|
||||
let queryRunner = new QueryRunner(
|
||||
testuri,
|
||||
testuri,
|
||||
testStatusView.object,
|
||||
testSqlToolsServerClient.object,
|
||||
testQueryNotificationHandler.object,
|
||||
testVscodeWrapper.object
|
||||
);
|
||||
queryRunner.uri = testuri;
|
||||
queryRunner.dataResolveReject = {resolve: () => {
|
||||
// Needed to handle the result callback
|
||||
}};
|
||||
// Call handleResult to ensure column header info is seeded
|
||||
queryRunner.handleResult(result);
|
||||
|
||||
// call copyResults with additional parameter indicating to not include headers
|
||||
return queryRunner.copyResults(testRange, 0, 0, false).then(() => {
|
||||
let pasteContents = ncp.paste();
|
||||
assert.equal(pasteContents, finalStringNoHeader);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче