mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
35
test/OSSREADME.json
Normal file
35
test/OSSREADME.json
Normal file
@@ -0,0 +1,35 @@
|
||||
// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS:
|
||||
|
||||
[{
|
||||
"name": "Jxck/assert",
|
||||
"license": "MIT",
|
||||
"licenseDetail": [
|
||||
"The MIT License (MIT)",
|
||||
"",
|
||||
"Copyright (c) 2011 Jxck",
|
||||
"",
|
||||
"Originally from node.js (http://nodejs.org)",
|
||||
"Copyright Joyent, Inc.",
|
||||
"",
|
||||
"Permission is hereby granted, free of charge, to any person obtaining a copy",
|
||||
"of this software and associated documentation files (the \"Software\"), to deal",
|
||||
"in the Software without restriction, including without limitation the rights",
|
||||
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell",
|
||||
"copies of the Software, and to permit persons to whom the Software is",
|
||||
"furnished to do so, subject to the following conditions:",
|
||||
"",
|
||||
"The above copyright notice and this permission notice shall be included in all",
|
||||
"copies or substantial portions of the Software.",
|
||||
"",
|
||||
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
|
||||
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
|
||||
"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE",
|
||||
"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
|
||||
"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,",
|
||||
"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"repositoryURL": "https://github.com/Jxck/assert",
|
||||
"description": "The file assert.js in this folder is based on https://github.com/Jxck/assert/blob/master/assert.js.",
|
||||
"isDev": true
|
||||
}]
|
||||
32
test/README.md
Normal file
32
test/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Tests
|
||||
|
||||
## Run
|
||||
|
||||
The best way to run the Code tests is from the terminal. To make development changes to unit tests you need to be running `gulp`. See [Development Workflow](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#incremental-build) for more details. From the `vscode` folder run:
|
||||
|
||||
**OS X and Linux**
|
||||
|
||||
./scripts/test.sh
|
||||
|
||||
**Windows**
|
||||
|
||||
scripts\test
|
||||
|
||||
|
||||
## Debug
|
||||
|
||||
To debug tests use `--debug` when running the test script. Also, the set of tests can be reduced with the `--run` and `--runGlob` flags. Both require a file path/pattern. Like so:
|
||||
|
||||
./scripts/test.sh --debug --runGrep **/extHost*.test.js
|
||||
|
||||
## Coverage
|
||||
|
||||
The following command will create a `coverage` folder at the root of the workspace:
|
||||
|
||||
**OS X and Linux**
|
||||
|
||||
./scripts/test.sh --coverage
|
||||
|
||||
**Windows**
|
||||
|
||||
scripts\test --coverage
|
||||
294
test/all.js
Normal file
294
test/all.js
Normal file
@@ -0,0 +1,294 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*eslint-env mocha*/
|
||||
/*global define,run*/
|
||||
|
||||
var assert = require('assert');
|
||||
var path = require('path');
|
||||
var glob = require('glob');
|
||||
var istanbul = require('istanbul');
|
||||
var i_remap = require('remap-istanbul/lib/remap');
|
||||
var jsdom = require('jsdom-no-contextify');
|
||||
var minimatch = require('minimatch');
|
||||
var fs = require('fs');
|
||||
var vm = require('vm');
|
||||
// {{SQL CARBON EDIT}}
|
||||
var TEST_GLOB = '**/*test*/**/*.test.js';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
var SQL_TEST_GLOB = '**/sqltest/**/*.test.js';
|
||||
|
||||
var optimist = require('optimist')
|
||||
.usage('Run the Code tests. All mocha options apply.')
|
||||
.describe('build', 'Run from out-build').boolean('build')
|
||||
.describe('run', 'Run a single file').string('run')
|
||||
.describe('coverage', 'Generate a coverage report').boolean('coverage')
|
||||
.describe('only-monaco-editor', 'Run only monaco editor tests').boolean('only-monaco-editor')
|
||||
.describe('forceLoad', 'Force loading').boolean('forceLoad')
|
||||
.describe('browser', 'Run tests in a browser').boolean('browser')
|
||||
.alias('h', 'help').boolean('h')
|
||||
.describe('h', 'Show help');
|
||||
|
||||
var argv = optimist.argv;
|
||||
|
||||
if (argv.help) {
|
||||
optimist.showHelp();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var out = argv.build ? 'out-build' : 'out';
|
||||
var loader = require('../' + out + '/vs/loader');
|
||||
var src = path.join(path.dirname(__dirname), out);
|
||||
|
||||
function main() {
|
||||
process.on('uncaughtException', function (e) {
|
||||
console.error(e.stack || e);
|
||||
});
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
var loaderConfig = {
|
||||
nodeRequire: require,
|
||||
nodeMain: __filename,
|
||||
baseUrl: path.join(path.dirname(__dirname), 'src'),
|
||||
paths: {
|
||||
'vs': `../${ out }/vs`,
|
||||
'sqltest': `../${ out }/sqltest`,
|
||||
'sql': `../${ out }/sql`,
|
||||
'lib': `../${ out }/lib`,
|
||||
'bootstrap': `../${ out }/bootstrap`
|
||||
},
|
||||
catchError: true,
|
||||
nodeModules: [
|
||||
'@angular/common',
|
||||
'@angular/core',
|
||||
'@angular/forms',
|
||||
'@angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic',
|
||||
'@angular/router',
|
||||
'angular2-grid',
|
||||
'rxjs/add/observable/of',
|
||||
'rxjs/Observable',
|
||||
'rxjs/Subject',
|
||||
'rxjs/Observer'
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
if (argv.coverage) {
|
||||
var instrumenter = new istanbul.Instrumenter();
|
||||
|
||||
var seenSources = {};
|
||||
|
||||
loaderConfig.nodeInstrumenter = function (contents, source) {
|
||||
seenSources[source] = true;
|
||||
|
||||
if (minimatch(source, SQL_TEST_GLOB)) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
return instrumenter.instrumentSync(contents, source);
|
||||
};
|
||||
|
||||
process.on('exit', function (code) {
|
||||
if (code !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv.forceLoad) {
|
||||
// {{SQL CARBON EDIT}}
|
||||
var allFiles = glob.sync(out + '/sqltest/**/*.js');
|
||||
allFiles = allFiles.map(function(source) {
|
||||
return path.join(__dirname, '..', source);
|
||||
});
|
||||
allFiles = allFiles.filter(function(source) {
|
||||
if (seenSources[source]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
if (minimatch(source, SQL_TEST_GLOB)) {
|
||||
return false;
|
||||
}
|
||||
if (/fixtures/.test(source)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
allFiles.forEach(function(source, index) {
|
||||
var contents = fs.readFileSync(source).toString();
|
||||
contents = instrumenter.instrumentSync(contents, source);
|
||||
var stopAt = contents.indexOf('}\n__cov');
|
||||
stopAt = contents.indexOf('}\n__cov', stopAt + 1);
|
||||
|
||||
var str = '(function() {' + contents.substr(0, stopAt + 1) + '});';
|
||||
var r = vm.runInThisContext(str, source);
|
||||
r.call(global);
|
||||
});
|
||||
}
|
||||
|
||||
let remapIgnores = /\b((winjs\.base)|(marked)|(raw\.marked)|(nls)|(css))\.js$/;
|
||||
|
||||
var remappedCoverage = i_remap(global.__coverage__, { exclude: remapIgnores }).getFinalCoverage();
|
||||
|
||||
// The remapped coverage comes out with broken paths
|
||||
var toUpperDriveLetter = function(str) {
|
||||
if (/^[a-z]:/.test(str)) {
|
||||
return str.charAt(0).toUpperCase() + str.substr(1);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
var toLowerDriveLetter = function(str) {
|
||||
if (/^[A-Z]:/.test(str)) {
|
||||
return str.charAt(0).toLowerCase() + str.substr(1);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
var REPO_PATH = toUpperDriveLetter(path.join(__dirname, '..'));
|
||||
var fixPath = function(brokenPath) {
|
||||
var startIndex = brokenPath.indexOf(REPO_PATH);
|
||||
if (startIndex === -1) {
|
||||
return toLowerDriveLetter(brokenPath);
|
||||
}
|
||||
return toLowerDriveLetter(brokenPath.substr(startIndex));
|
||||
};
|
||||
|
||||
var finalCoverage = {};
|
||||
for (var entryKey in remappedCoverage) {
|
||||
var entry = remappedCoverage[entryKey];
|
||||
entry.path = fixPath(entry.path);
|
||||
// {{SQL CARBON EDIT}}
|
||||
if (!entry.path.includes('\\vs\\') && !entry.path.includes('/vs/')) {
|
||||
finalCoverage[fixPath(entryKey)] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
var collector = new istanbul.Collector();
|
||||
collector.add(finalCoverage);
|
||||
|
||||
var coveragePath = path.join(path.dirname(__dirname), '.build', 'coverage');
|
||||
var reportTypes = [];
|
||||
if (argv.run || argv.runGlob) {
|
||||
// single file running
|
||||
coveragePath += '-single';
|
||||
reportTypes = ['lcovonly'];
|
||||
} else {
|
||||
// {{SQL CARBON EDIT}}
|
||||
reportTypes = ['json', 'lcov', 'html', 'cobertura'];
|
||||
}
|
||||
var reporter = new istanbul.Reporter(null, coveragePath);
|
||||
reporter.addAll(reportTypes);
|
||||
reporter.write(collector, true, function () {});
|
||||
});
|
||||
}
|
||||
|
||||
loader.config(loaderConfig);
|
||||
|
||||
global.define = loader;
|
||||
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
|
||||
global.self = global.window = global.document.parentWindow;
|
||||
|
||||
global.Element = global.window.Element;
|
||||
global.HTMLElement = global.window.HTMLElement;
|
||||
global.Node = global.window.Node;
|
||||
global.navigator = global.window.navigator;
|
||||
global.XMLHttpRequest = global.window.XMLHttpRequest;
|
||||
// {{SQL CARBON EDIT}}
|
||||
global.Event = global.window.Event;
|
||||
|
||||
require('reflect-metadata');
|
||||
global.window.Reflect = global.Reflect;
|
||||
global.window.Zone = global.Zone;
|
||||
|
||||
var didErr = false;
|
||||
var write = process.stderr.write;
|
||||
process.stderr.write = function (data) {
|
||||
didErr = didErr || !!data;
|
||||
write.apply(process.stderr, arguments);
|
||||
};
|
||||
|
||||
var loadFunc = null;
|
||||
|
||||
if (argv.runGlob) {
|
||||
loadFunc = cb => {
|
||||
const doRun = tests => {
|
||||
const modulesToLoad = tests.map(test => {
|
||||
if (path.isAbsolute(test)) {
|
||||
test = path.relative(src, path.resolve(test));
|
||||
}
|
||||
|
||||
return test.replace(/(\.js)|(\.d\.ts)|(\.js\.map)$/, '');
|
||||
});
|
||||
define(modulesToLoad, () => cb(null), cb);
|
||||
};
|
||||
|
||||
glob(argv.runGlob, { cwd: src }, function (err, files) { doRun(files); });
|
||||
};
|
||||
} else if (argv.run) {
|
||||
var tests = (typeof argv.run === 'string') ? [argv.run] : argv.run;
|
||||
var modulesToLoad = tests.map(function(test) {
|
||||
return path.relative(src, path.resolve(test)).replace(/(\.js)|(\.d\.ts)|(\.js\.map)$/, '');
|
||||
});
|
||||
loadFunc = cb => {
|
||||
define(modulesToLoad, () => cb(null), cb);
|
||||
};
|
||||
} else if (argv['only-monaco-editor']) {
|
||||
loadFunc = function(cb) {
|
||||
glob(TEST_GLOB, { cwd: src }, function (err, files) {
|
||||
var modulesToLoad = files.map(function (file) {
|
||||
return file.replace(/\.js$/, '');
|
||||
});
|
||||
modulesToLoad = modulesToLoad.filter(function(module) {
|
||||
if (/^vs\/workbench\//.test(module)) {
|
||||
return false;
|
||||
}
|
||||
// platform tests drag in the workbench.
|
||||
// see https://github.com/Microsoft/vscode/commit/12eaba2f64c69247de105c3d9c47308ac6e44bc9
|
||||
// and cry a little
|
||||
if (/^vs\/platform\//.test(module)) {
|
||||
return false;
|
||||
}
|
||||
return !/(\/|\\)node(\/|\\)/.test(module);
|
||||
});
|
||||
console.log(JSON.stringify(modulesToLoad, null, '\t'));
|
||||
define(modulesToLoad, function () { cb(null); }, cb);
|
||||
});
|
||||
};
|
||||
} else {
|
||||
loadFunc = function(cb) {
|
||||
glob(TEST_GLOB, { cwd: src }, function (err, files) {
|
||||
var modulesToLoad = files.map(function (file) {
|
||||
return file.replace(/\.js$/, '');
|
||||
});
|
||||
define(modulesToLoad, function () { cb(null); }, cb);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
loadFunc(function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
process.stderr.write = write;
|
||||
|
||||
// replace the default unexpected error handler to be useful during tests
|
||||
loader(['vs/base/common/errors'], function(errors) {
|
||||
errors.setUnexpectedErrorHandler(function (err) {
|
||||
});
|
||||
|
||||
// fire up mocha
|
||||
run();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (process.argv.some(function (a) { return /^--browser/.test(a); })) {
|
||||
require('./browser');
|
||||
} else {
|
||||
main();
|
||||
}
|
||||
474
test/assert.js
Normal file
474
test/assert.js
Normal file
@@ -0,0 +1,474 @@
|
||||
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
|
||||
//
|
||||
// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
|
||||
//
|
||||
// Copyright (c) 2011 Jxck
|
||||
//
|
||||
// Originally from node.js (http://nodejs.org)
|
||||
// Copyright Joyent, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the 'Software'), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], factory); // AMD
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = factory(); // CommonJS
|
||||
} else {
|
||||
root.assert = factory(); // Global
|
||||
}
|
||||
})(this, function() {
|
||||
|
||||
// UTILITY
|
||||
|
||||
// Object.create compatible in IE
|
||||
var create = Object.create || function(p) {
|
||||
if (!p) throw Error('no type');
|
||||
function f() {};
|
||||
f.prototype = p;
|
||||
return new f();
|
||||
};
|
||||
|
||||
// UTILITY
|
||||
var util = {
|
||||
inherits: function(ctor, superCtor) {
|
||||
ctor.super_ = superCtor;
|
||||
ctor.prototype = create(superCtor.prototype, {
|
||||
constructor: {
|
||||
value: ctor,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
},
|
||||
isArray: function(ar) {
|
||||
return Array.isArray(ar);
|
||||
},
|
||||
isBoolean: function(arg) {
|
||||
return typeof arg === 'boolean';
|
||||
},
|
||||
isNull: function(arg) {
|
||||
return arg === null;
|
||||
},
|
||||
isNullOrUndefined: function(arg) {
|
||||
return arg == null;
|
||||
},
|
||||
isNumber: function(arg) {
|
||||
return typeof arg === 'number';
|
||||
},
|
||||
isString: function(arg) {
|
||||
return typeof arg === 'string';
|
||||
},
|
||||
isSymbol: function(arg) {
|
||||
return typeof arg === 'symbol';
|
||||
},
|
||||
isUndefined: function(arg) {
|
||||
return arg === void 0;
|
||||
},
|
||||
isRegExp: function(re) {
|
||||
return util.isObject(re) && util.objectToString(re) === '[object RegExp]';
|
||||
},
|
||||
isObject: function(arg) {
|
||||
return typeof arg === 'object' && arg !== null;
|
||||
},
|
||||
isDate: function(d) {
|
||||
return util.isObject(d) && util.objectToString(d) === '[object Date]';
|
||||
},
|
||||
isError: function(e) {
|
||||
return isObject(e) &&
|
||||
(objectToString(e) === '[object Error]' || e instanceof Error);
|
||||
},
|
||||
isFunction: function(arg) {
|
||||
return typeof arg === 'function';
|
||||
},
|
||||
isPrimitive: function(arg) {
|
||||
return arg === null ||
|
||||
typeof arg === 'boolean' ||
|
||||
typeof arg === 'number' ||
|
||||
typeof arg === 'string' ||
|
||||
typeof arg === 'symbol' || // ES6 symbol
|
||||
typeof arg === 'undefined';
|
||||
},
|
||||
objectToString: function(o) {
|
||||
return Object.prototype.toString.call(o);
|
||||
}
|
||||
};
|
||||
|
||||
var pSlice = Array.prototype.slice;
|
||||
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
|
||||
var Object_keys = typeof Object.keys === 'function' ? Object.keys : (function() {
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty,
|
||||
hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
|
||||
dontEnums = [
|
||||
'toString',
|
||||
'toLocaleString',
|
||||
'valueOf',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
'constructor'
|
||||
],
|
||||
dontEnumsLength = dontEnums.length;
|
||||
|
||||
return function(obj) {
|
||||
if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
|
||||
throw new TypeError('Object.keys called on non-object');
|
||||
}
|
||||
|
||||
var result = [], prop, i;
|
||||
|
||||
for (prop in obj) {
|
||||
if (hasOwnProperty.call(obj, prop)) {
|
||||
result.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDontEnumBug) {
|
||||
for (i = 0; i < dontEnumsLength; i++) {
|
||||
if (hasOwnProperty.call(obj, dontEnums[i])) {
|
||||
result.push(dontEnums[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
|
||||
// 1. The assert module provides functions that throw
|
||||
// AssertionError's when particular conditions are not met. The
|
||||
// assert module must conform to the following interface.
|
||||
|
||||
var assert = ok;
|
||||
|
||||
// 2. The AssertionError is defined in assert.
|
||||
// new assert.AssertionError({ message: message,
|
||||
// actual: actual,
|
||||
// expected: expected })
|
||||
|
||||
assert.AssertionError = function AssertionError(options) {
|
||||
this.name = 'AssertionError';
|
||||
this.actual = options.actual;
|
||||
this.expected = options.expected;
|
||||
this.operator = options.operator;
|
||||
if (options.message) {
|
||||
this.message = options.message;
|
||||
this.generatedMessage = false;
|
||||
} else {
|
||||
this.message = getMessage(this);
|
||||
this.generatedMessage = true;
|
||||
}
|
||||
var stackStartFunction = options.stackStartFunction || fail;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, stackStartFunction);
|
||||
} else {
|
||||
// try to throw an error now, and from the stack property
|
||||
// work out the line that called in to assert.js.
|
||||
try {
|
||||
this.stack = (new Error).stack.toString();
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
// assert.AssertionError instanceof Error
|
||||
util.inherits(assert.AssertionError, Error);
|
||||
|
||||
function replacer(key, value) {
|
||||
if (util.isUndefined(value)) {
|
||||
return '' + value;
|
||||
}
|
||||
if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) {
|
||||
return value.toString();
|
||||
}
|
||||
if (util.isFunction(value) || util.isRegExp(value)) {
|
||||
return value.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function truncate(s, n) {
|
||||
if (util.isString(s)) {
|
||||
return s.length < n ? s : s.slice(0, n);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
function getMessage(self) {
|
||||
return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' +
|
||||
self.operator + ' ' +
|
||||
truncate(JSON.stringify(self.expected, replacer), 128);
|
||||
}
|
||||
|
||||
// At present only the three keys mentioned above are used and
|
||||
// understood by the spec. Implementations or sub modules can pass
|
||||
// other keys to the AssertionError's constructor - they will be
|
||||
// ignored.
|
||||
|
||||
// 3. All of the following functions must throw an AssertionError
|
||||
// when a corresponding condition is not met, with a message that
|
||||
// may be undefined if not provided. All assertion methods provide
|
||||
// both the actual and expected values to the assertion error for
|
||||
// display purposes.
|
||||
|
||||
function fail(actual, expected, message, operator, stackStartFunction) {
|
||||
throw new assert.AssertionError({
|
||||
message: message,
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
operator: operator,
|
||||
stackStartFunction: stackStartFunction
|
||||
});
|
||||
}
|
||||
|
||||
// EXTENSION! allows for well behaved errors defined elsewhere.
|
||||
assert.fail = fail;
|
||||
|
||||
// 4. Pure assertion tests whether a value is truthy, as determined
|
||||
// by !!guard.
|
||||
// assert.ok(guard, message_opt);
|
||||
// This statement is equivalent to assert.equal(true, !!guard,
|
||||
// message_opt);. To test strictly for the value true, use
|
||||
// assert.strictEqual(true, guard, message_opt);.
|
||||
|
||||
function ok(value, message) {
|
||||
if (!value) fail(value, true, message, '==', assert.ok);
|
||||
}
|
||||
assert.ok = ok;
|
||||
|
||||
// 5. The equality assertion tests shallow, coercive equality with
|
||||
// ==.
|
||||
// assert.equal(actual, expected, message_opt);
|
||||
|
||||
assert.equal = function equal(actual, expected, message) {
|
||||
if (actual != expected) fail(actual, expected, message, '==', assert.equal);
|
||||
};
|
||||
|
||||
// 6. The non-equality assertion tests for whether two objects are not equal
|
||||
// with != assert.notEqual(actual, expected, message_opt);
|
||||
|
||||
assert.notEqual = function notEqual(actual, expected, message) {
|
||||
if (actual == expected) {
|
||||
fail(actual, expected, message, '!=', assert.notEqual);
|
||||
}
|
||||
};
|
||||
|
||||
// 7. The equivalence assertion tests a deep equality relation.
|
||||
// assert.deepEqual(actual, expected, message_opt);
|
||||
|
||||
assert.deepEqual = function deepEqual(actual, expected, message) {
|
||||
if (!_deepEqual(actual, expected)) {
|
||||
fail(actual, expected, message, 'deepEqual', assert.deepEqual);
|
||||
}
|
||||
};
|
||||
|
||||
function _deepEqual(actual, expected) {
|
||||
// 7.1. All identical values are equivalent, as determined by ===.
|
||||
if (actual === expected) {
|
||||
return true;
|
||||
|
||||
// } else if (util.isBuffer(actual) && util.isBuffer(expected)) {
|
||||
// if (actual.length != expected.length) return false;
|
||||
//
|
||||
// for (var i = 0; i < actual.length; i++) {
|
||||
// if (actual[i] !== expected[i]) return false;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
//
|
||||
// 7.2. If the expected value is a Date object, the actual value is
|
||||
// equivalent if it is also a Date object that refers to the same time.
|
||||
} else if (util.isDate(actual) && util.isDate(expected)) {
|
||||
return actual.getTime() === expected.getTime();
|
||||
|
||||
// 7.3 If the expected value is a RegExp object, the actual value is
|
||||
// equivalent if it is also a RegExp object with the same source and
|
||||
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
|
||||
} else if (util.isRegExp(actual) && util.isRegExp(expected)) {
|
||||
return actual.source === expected.source &&
|
||||
actual.global === expected.global &&
|
||||
actual.multiline === expected.multiline &&
|
||||
actual.lastIndex === expected.lastIndex &&
|
||||
actual.ignoreCase === expected.ignoreCase;
|
||||
|
||||
// 7.4. Other pairs that do not both pass typeof value == 'object',
|
||||
// equivalence is determined by ==.
|
||||
} else if (!util.isObject(actual) && !util.isObject(expected)) {
|
||||
return actual == expected;
|
||||
|
||||
// 7.5 For all other Object pairs, including Array objects, equivalence is
|
||||
// determined by having the same number of owned properties (as verified
|
||||
// with Object.prototype.hasOwnProperty.call), the same set of keys
|
||||
// (although not necessarily the same order), equivalent values for every
|
||||
// corresponding key, and an identical 'prototype' property. Note: this
|
||||
// accounts for both named and indexed properties on Arrays.
|
||||
} else {
|
||||
return objEquiv(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
var isArguments = function(object) {
|
||||
return Object.prototype.toString.call(object) == '[object Arguments]';
|
||||
};
|
||||
|
||||
(function() {
|
||||
if (!isArguments(arguments)) {
|
||||
isArguments = function(object) {
|
||||
return object != null &&
|
||||
typeof object === 'object' &&
|
||||
typeof object.callee === 'function' &&
|
||||
typeof object.length === 'number' || false;
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
function objEquiv(a, b) {
|
||||
if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b))
|
||||
return false;
|
||||
// an identical 'prototype' property.
|
||||
if (a.prototype !== b.prototype) return false;
|
||||
//~~~I've managed to break Object.keys through screwy arguments passing.
|
||||
// Converting to array solves the problem.
|
||||
var aIsArgs = isArguments(a),
|
||||
bIsArgs = isArguments(b);
|
||||
if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs))
|
||||
return false;
|
||||
if (aIsArgs) {
|
||||
a = pSlice.call(a);
|
||||
b = pSlice.call(b);
|
||||
return _deepEqual(a, b);
|
||||
}
|
||||
try {
|
||||
var ka = Object_keys(a),
|
||||
kb = Object_keys(b),
|
||||
key, i;
|
||||
} catch (e) {//happens when one is a string literal and the other isn't
|
||||
return false;
|
||||
}
|
||||
// having the same number of owned properties (keys incorporates
|
||||
// hasOwnProperty)
|
||||
if (ka.length != kb.length)
|
||||
return false;
|
||||
//the same set of keys (although not necessarily the same order),
|
||||
ka.sort();
|
||||
kb.sort();
|
||||
//~~~cheap key test
|
||||
for (i = ka.length - 1; i >= 0; i--) {
|
||||
if (ka[i] != kb[i])
|
||||
return false;
|
||||
}
|
||||
//equivalent values for every corresponding key, and
|
||||
//~~~possibly expensive deep test
|
||||
for (i = ka.length - 1; i >= 0; i--) {
|
||||
key = ka[i];
|
||||
if (!_deepEqual(a[key], b[key])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 8. The non-equivalence assertion tests for any deep inequality.
|
||||
// assert.notDeepEqual(actual, expected, message_opt);
|
||||
|
||||
assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
|
||||
if (_deepEqual(actual, expected)) {
|
||||
fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
|
||||
}
|
||||
};
|
||||
|
||||
// 9. The strict equality assertion tests strict equality, as determined by ===.
|
||||
// assert.strictEqual(actual, expected, message_opt);
|
||||
|
||||
assert.strictEqual = function strictEqual(actual, expected, message) {
|
||||
if (actual !== expected) {
|
||||
fail(actual, expected, message, '===', assert.strictEqual);
|
||||
}
|
||||
};
|
||||
|
||||
// 10. The strict non-equality assertion tests for strict inequality, as
|
||||
// determined by !==. assert.notStrictEqual(actual, expected, message_opt);
|
||||
|
||||
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
|
||||
if (actual === expected) {
|
||||
fail(actual, expected, message, '!==', assert.notStrictEqual);
|
||||
}
|
||||
};
|
||||
|
||||
function expectedException(actual, expected) {
|
||||
if (!actual || !expected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(expected) == '[object RegExp]') {
|
||||
return expected.test(actual);
|
||||
} else if (actual instanceof expected) {
|
||||
return true;
|
||||
} else if (expected.call({}, actual) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _throws(shouldThrow, block, expected, message) {
|
||||
var actual;
|
||||
|
||||
if (util.isString(expected)) {
|
||||
message = expected;
|
||||
expected = null;
|
||||
}
|
||||
|
||||
try {
|
||||
block();
|
||||
} catch (e) {
|
||||
actual = e;
|
||||
}
|
||||
|
||||
message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
|
||||
(message ? ' ' + message : '.');
|
||||
|
||||
if (shouldThrow && !actual) {
|
||||
fail(actual, expected, 'Missing expected exception' + message);
|
||||
}
|
||||
|
||||
if (!shouldThrow && expectedException(actual, expected)) {
|
||||
fail(actual, expected, 'Got unwanted exception' + message);
|
||||
}
|
||||
|
||||
if ((shouldThrow && actual && expected &&
|
||||
!expectedException(actual, expected)) || (!shouldThrow && actual)) {
|
||||
throw actual;
|
||||
}
|
||||
}
|
||||
|
||||
// 11. Expected to throw an error:
|
||||
// assert.throws(block, Error_opt, message_opt);
|
||||
|
||||
assert.throws = function(block, /*optional*/error, /*optional*/message) {
|
||||
_throws.apply(this, [true].concat(pSlice.call(arguments)));
|
||||
};
|
||||
|
||||
// EXTENSION! This is annoying to write outside this module.
|
||||
assert.doesNotThrow = function(block, /*optional*/message) {
|
||||
_throws.apply(this, [false].concat(pSlice.call(arguments)));
|
||||
};
|
||||
|
||||
assert.ifError = function(err) { if (err) {throw err;}};
|
||||
|
||||
return assert;
|
||||
});
|
||||
48
test/browser.js
Normal file
48
test/browser.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
var express = require('express');
|
||||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
|
||||
var port = 8887;
|
||||
var root = path.dirname(__dirname);
|
||||
|
||||
function template(str, env) {
|
||||
return str.replace(/{{\s*([\w_\-]+)\s*}}/g, function (all, part) {
|
||||
return env[part];
|
||||
});
|
||||
}
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use('/out', express.static(path.join(root, 'out')));
|
||||
app.use('/test', express.static(path.join(root, 'test')));
|
||||
app.use('/node_modules', express.static(path.join(root, 'node_modules')));
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
glob('**/vs/{base,platform,editor}/**/test/{common,browser}/**/*.test.js', {
|
||||
cwd: path.join(root, 'out'),
|
||||
// ignore: ['**/test/{node,electron*}/**/*.js']
|
||||
}, function (err, files) {
|
||||
if (err) { return res.sendStatus(500); }
|
||||
|
||||
var modules = files
|
||||
.map(function (file) { return file.replace(/\.js$/, ''); });
|
||||
|
||||
fs.readFile(path.join(__dirname, 'index.html'), 'utf8', function (err, templateString) {
|
||||
if (err) { return res.sendStatus(500); }
|
||||
|
||||
res.send(template(templateString, {
|
||||
modules: JSON.stringify(modules)
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(port, function () {
|
||||
console.log('http://localhost:8887/');
|
||||
});
|
||||
125
test/electron/index.js
Normal file
125
test/electron/index.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||
const { tmpdir } = require('os');
|
||||
const { join } = require('path');
|
||||
const path = require('path');
|
||||
const mocha = require('mocha');
|
||||
const events = require('events');
|
||||
|
||||
const optimist = require('optimist')
|
||||
.describe('grep', 'only run tests matching <pattern>').alias('grep', 'g').alias('grep', 'f').string('grep')
|
||||
.describe('run', 'only run tests from <file>').string('run')
|
||||
.describe('runGlob', 'only run tests matching <file_pattern>').alias('runGlob', 'runGrep').string('runGlob')
|
||||
.describe('build', 'run with build output (out-build)').boolean('build')
|
||||
.describe('coverage', 'generate coverage report').boolean('coverage')
|
||||
.describe('debug', 'open dev tools, keep window open, reuse app data').string('debug')
|
||||
.describe('reporter', 'the mocha reporter').string('reporter').default('reporter', process.platform === 'win32' ? 'dot' : 'spec')
|
||||
.describe('help', 'show the help').alias('help', 'h');
|
||||
|
||||
const argv = optimist.argv;
|
||||
|
||||
if (argv.help) {
|
||||
optimist.showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!argv.debug) {
|
||||
app.setPath('userData', join(tmpdir(), `vscode-tests-${Date.now()}`));
|
||||
}
|
||||
|
||||
function deserializeSuite(suite) {
|
||||
return {
|
||||
title: suite.title,
|
||||
fullTitle: () => suite.fullTitle,
|
||||
timeout: () => suite.timeout,
|
||||
retries: () => suite.retries,
|
||||
enableTimeouts: () => suite.enableTimeouts,
|
||||
slow: () => suite.slow,
|
||||
bail: () => suite.bail,
|
||||
};
|
||||
}
|
||||
|
||||
function deserializeRunnable(runnable) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
fullTitle: () => runnable.fullTitle,
|
||||
async: runnable.async,
|
||||
slow: () => runnable.slow,
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration
|
||||
};
|
||||
}
|
||||
|
||||
function deserializeError(err) {
|
||||
const inspect = err.inspect;
|
||||
err.inspect = () => inspect;
|
||||
return err;
|
||||
}
|
||||
|
||||
class IPCRunner extends events.EventEmitter {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.didFail = false;
|
||||
|
||||
ipcMain.on('start', () => this.emit('start'));
|
||||
ipcMain.on('end', () => this.emit('end'));
|
||||
ipcMain.on('suite', (e, suite) => this.emit('suite', deserializeSuite(suite)));
|
||||
ipcMain.on('suite end', (e, suite) => this.emit('suite end', deserializeSuite(suite)));
|
||||
ipcMain.on('test', (e, test) => this.emit('test', deserializeRunnable(test)));
|
||||
ipcMain.on('test end', (e, test) => this.emit('test end', deserializeRunnable(test)));
|
||||
ipcMain.on('hook', (e, hook) => this.emit('hook', deserializeRunnable(hook)));
|
||||
ipcMain.on('hook end', (e, hook) => this.emit('hook end', deserializeRunnable(hook)));
|
||||
ipcMain.on('pass', (e, test) => this.emit('pass', deserializeRunnable(test)));
|
||||
ipcMain.on('fail', (e, test, err) => {
|
||||
this.didFail = true;
|
||||
this.emit('fail', deserializeRunnable(test), deserializeError(err));
|
||||
});
|
||||
ipcMain.on('pending', (e, test) => this.emit('pending', deserializeRunnable(test)));
|
||||
}
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
|
||||
const win = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 800,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
webSecurity: false
|
||||
}
|
||||
});
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
if (argv.debug) {
|
||||
win.show();
|
||||
win.webContents.openDevTools('right');
|
||||
}
|
||||
win.webContents.send('run', argv);
|
||||
});
|
||||
|
||||
win.loadURL(`file://${__dirname}/renderer.html`);
|
||||
|
||||
const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', argv.reporter);
|
||||
let Reporter;
|
||||
|
||||
try {
|
||||
Reporter = require(reporterPath);
|
||||
} catch (err) {
|
||||
console.warn(`could not load reporter: ${argv.reporter}`);
|
||||
Reporter = process.platform === 'win32' ? mocha.reporters.Dot : mocha.reporters.Spec;
|
||||
}
|
||||
|
||||
const runner = new IPCRunner();
|
||||
new Reporter(runner);
|
||||
|
||||
if (!argv.debug) {
|
||||
ipcMain.on('all done', () => app.exit(runner.didFail ? 1 : 0));
|
||||
}
|
||||
});
|
||||
19
test/electron/renderer.html
Normal file
19
test/electron/renderer.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>VSCode Tests</title>
|
||||
<link href="../../node_modules/mocha/mocha.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script src="../../node_modules/mocha/mocha.js"></script>
|
||||
|
||||
<script>
|
||||
mocha.setup('tdd');
|
||||
require('./renderer');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
265
test/electron/renderer.js
Normal file
265
test/electron/renderer.js
Normal file
@@ -0,0 +1,265 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*eslint-env mocha*/
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const minimatch = require('minimatch');
|
||||
const istanbul = require('istanbul');
|
||||
const i_remap = require('remap-istanbul/lib/remap');
|
||||
|
||||
let _tests_glob = '**/test/**/*.test.js';
|
||||
let loader;
|
||||
let _out;
|
||||
|
||||
function initLoader(opts) {
|
||||
let outdir = opts.build ? 'out-build' : 'out';
|
||||
_out = path.join(__dirname, `../../${outdir}`);
|
||||
|
||||
// setup loader
|
||||
loader = require(`${_out}/vs/loader`);
|
||||
const loaderConfig = {
|
||||
nodeRequire: require,
|
||||
nodeMain: __filename,
|
||||
catchError: true,
|
||||
baseUrl: path.join(__dirname, '../../src'),
|
||||
paths: {
|
||||
'vs': `../${outdir}/vs`,
|
||||
'lib': `../${outdir}/lib`,
|
||||
'bootstrap': `../${outdir}/bootstrap`
|
||||
}
|
||||
};
|
||||
|
||||
// nodeInstrumenter when coverage is requested
|
||||
if (opts.coverage) {
|
||||
const instrumenter = new istanbul.Instrumenter();
|
||||
|
||||
loaderConfig.nodeInstrumenter = function (contents, source) {
|
||||
return minimatch(source, _tests_glob)
|
||||
? contents // don't instrument tests itself
|
||||
: instrumenter.instrumentSync(contents, source);
|
||||
};
|
||||
}
|
||||
|
||||
loader.require.config(loaderConfig);
|
||||
}
|
||||
|
||||
function createCoverageReport(opts) {
|
||||
return new Promise(resolve => {
|
||||
|
||||
if (!opts.coverage) {
|
||||
return resolve(undefined);
|
||||
}
|
||||
|
||||
const exclude = /\b((winjs\.base)|(marked)|(raw\.marked)|(nls)|(css))\.js$/;
|
||||
const remappedCoverage = i_remap(global.__coverage__, { exclude: exclude }).getFinalCoverage();
|
||||
|
||||
// The remapped coverage comes out with broken paths
|
||||
function toUpperDriveLetter(str) {
|
||||
if (/^[a-z]:/.test(str)) {
|
||||
return str.charAt(0).toUpperCase() + str.substr(1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
function toLowerDriveLetter(str) {
|
||||
if (/^[A-Z]:/.test(str)) {
|
||||
return str.charAt(0).toLowerCase() + str.substr(1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
const REPO_PATH = toUpperDriveLetter(path.join(__dirname, '../..'));
|
||||
const fixPath = function (brokenPath) {
|
||||
const startIndex = brokenPath.indexOf(REPO_PATH);
|
||||
if (startIndex === -1) {
|
||||
return toLowerDriveLetter(brokenPath);
|
||||
}
|
||||
return toLowerDriveLetter(brokenPath.substr(startIndex));
|
||||
};
|
||||
|
||||
const finalCoverage = Object.create(null);
|
||||
for (const entryKey in remappedCoverage) {
|
||||
const entry = remappedCoverage[entryKey];
|
||||
entry.path = fixPath(entry.path);
|
||||
finalCoverage[fixPath(entryKey)] = entry;
|
||||
}
|
||||
|
||||
const collector = new istanbul.Collector();
|
||||
collector.add(finalCoverage);
|
||||
|
||||
let coveragePath = path.join(path.dirname(__dirname), '../.build/coverage');
|
||||
let reportTypes = [];
|
||||
if (opts.run || opts.runGlob) {
|
||||
// single file running
|
||||
coveragePath += '-single';
|
||||
reportTypes = ['lcovonly'];
|
||||
} else {
|
||||
reportTypes = ['json', 'lcov', 'html'];
|
||||
}
|
||||
|
||||
const reporter = new istanbul.Reporter(null, coveragePath);
|
||||
reporter.addAll(reportTypes);
|
||||
reporter.write(collector, true, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function loadTestModules(opts) {
|
||||
|
||||
if (opts.run) {
|
||||
const files = Array.isArray(opts.run) ? opts.run : [opts.run];
|
||||
const modules = files.map(file => {
|
||||
return path.relative(_out, file).replace(/\.js$/, '');
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.require(modules, resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
const pattern = opts.runGlob || _tests_glob;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
glob(pattern, { cwd: _out }, (err, files) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const modules = files.map(file => file.replace(/\.js$/, ''));
|
||||
resolve(modules);
|
||||
});
|
||||
}).then(modules => {
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.require(modules, resolve, reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadTests(opts) {
|
||||
|
||||
const _unexpectedErrors = [];
|
||||
const _loaderErrors = [];
|
||||
|
||||
// collect loader errors
|
||||
loader.require.config({
|
||||
onError(err) {
|
||||
_loaderErrors.push(err);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
// collect unexpected errors
|
||||
loader.require(['vs/base/common/errors'], function (errors) {
|
||||
errors.setUnexpectedErrorHandler(function (err) {
|
||||
let stack = (err ? err.stack : null);
|
||||
if (!stack) {
|
||||
stack = new Error().stack;
|
||||
}
|
||||
|
||||
_unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack);
|
||||
});
|
||||
});
|
||||
|
||||
return loadTestModules(opts).then(() => {
|
||||
suite('Unexpected Errors & Loader Errors', function () {
|
||||
test('should not have unexpected errors', function () {
|
||||
const errors = _unexpectedErrors.concat(_loaderErrors);
|
||||
if (errors.length) {
|
||||
errors.forEach(function (stack) {
|
||||
console.error('');
|
||||
console.error(stack);
|
||||
});
|
||||
assert.ok(false, errors);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function serializeSuite(suite) {
|
||||
return {
|
||||
title: suite.title,
|
||||
fullTitle: suite.fullTitle(),
|
||||
timeout: suite.timeout(),
|
||||
retries: suite.retries(),
|
||||
enableTimeouts: suite.enableTimeouts(),
|
||||
slow: suite.slow(),
|
||||
bail: suite.bail()
|
||||
};
|
||||
}
|
||||
|
||||
function serializeRunnable(runnable) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
fullTitle: runnable.fullTitle(),
|
||||
async: runnable.async,
|
||||
slow: runnable.slow(),
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration
|
||||
};
|
||||
}
|
||||
|
||||
function serializeError(err) {
|
||||
return {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
actual: err.actual,
|
||||
expected: err.expected,
|
||||
uncaught: err.uncaught,
|
||||
showDiff: err.showDiff,
|
||||
inspect: typeof err.inspect === 'function' ? err.inspect() : ''
|
||||
};
|
||||
}
|
||||
|
||||
class IPCReporter {
|
||||
|
||||
constructor(runner) {
|
||||
runner.on('start', () => ipcRenderer.send('start'));
|
||||
runner.on('end', () => ipcRenderer.send('end'));
|
||||
runner.on('suite', suite => ipcRenderer.send('suite', serializeSuite(suite)));
|
||||
runner.on('suite end', suite => ipcRenderer.send('suite end', serializeSuite(suite)));
|
||||
runner.on('test', test => ipcRenderer.send('test', serializeRunnable(test)));
|
||||
runner.on('test end', test => ipcRenderer.send('test end', serializeRunnable(test)));
|
||||
runner.on('hook', hook => ipcRenderer.send('hook', serializeRunnable(hook)));
|
||||
runner.on('hook end', hook => ipcRenderer.send('hook end', serializeRunnable(hook)));
|
||||
runner.on('pass', test => ipcRenderer.send('pass', serializeRunnable(test)));
|
||||
runner.on('fail', (test, err) => ipcRenderer.send('fail', serializeRunnable(test), serializeError(err)));
|
||||
runner.on('pending', test => ipcRenderer.send('pending', serializeRunnable(test)));
|
||||
}
|
||||
}
|
||||
|
||||
function runTests(opts) {
|
||||
|
||||
return loadTests(opts).then(() => {
|
||||
|
||||
if (opts.grep) {
|
||||
mocha.grep(opts.grep);
|
||||
}
|
||||
|
||||
if (!opts.debug) {
|
||||
mocha.reporter(IPCReporter);
|
||||
}
|
||||
|
||||
const runner = mocha.run(() => {
|
||||
createCoverageReport(opts).then(() => {
|
||||
ipcRenderer.send('all done');
|
||||
});
|
||||
});
|
||||
|
||||
if (opts.debug) {
|
||||
runner.on('fail', (test, err) => {
|
||||
|
||||
console.error(test.fullTitle());
|
||||
console.error(err.stack);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ipcRenderer.on('run', (e, opts) => {
|
||||
initLoader(opts);
|
||||
runTests(opts).catch(err => console.error(err));
|
||||
});
|
||||
31
test/index.html
Normal file
31
test/index.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>VSCode Tests</title>
|
||||
<link href="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<!--<script src="https://cdn.rawgit.com/jquery/jquery/2.1.4/dist/jquery.min.js"></script>
|
||||
<script src="https://cdn.rawgit.com/Automattic/expect.js/0.3.1/index.js"></script>-->
|
||||
<script src="/out/vs/loader.js"></script>
|
||||
<script src="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.js"></script>
|
||||
|
||||
<script>
|
||||
mocha.setup('tdd');
|
||||
|
||||
require.config({
|
||||
baseUrl: '/out',
|
||||
paths: {
|
||||
assert: '/test/assert.js',
|
||||
sinon: '/node_modules/sinon/pkg/sinon-1.17.7.js'
|
||||
}
|
||||
});
|
||||
|
||||
require({{ modules }}, function () {
|
||||
mocha.run();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
5
test/mocha.opts
Normal file
5
test/mocha.opts
Normal file
@@ -0,0 +1,5 @@
|
||||
--delay
|
||||
--ui tdd
|
||||
--timeout 10000
|
||||
test/all.js
|
||||
|
||||
7
test/smoke/.gitignore
vendored
Normal file
7
test/smoke/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
Thumbs.db
|
||||
node_modules/
|
||||
out/
|
||||
keybindings.*.json
|
||||
test_data/
|
||||
13
test/smoke/Audit.md
Normal file
13
test/smoke/Audit.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# VS Code Smoke Tests Failures History
|
||||
This file contains a history of smoke test failures which could be avoided if particular techniques were used in the test (e.g. binding test elements with HTML5 `data-*` attribute).
|
||||
|
||||
To better understand what can be employed in smoke test to ensure its stability, it is important to understand patterns that led to smoke test breakage. This markdown is a result of work on [this issue](https://github.com/Microsoft/vscode/issues/27906).
|
||||
|
||||
# Log
|
||||
1. This following change led to the smoke test failure because DOM element's attribute `a[title]` was changed:
|
||||
[eac49a3](https://github.com/Microsoft/vscode/commit/eac49a321b84cb9828430e9dcd3f34243a3480f7)
|
||||
|
||||
This attribute was used in the smoke test to grab the contents of SCM part in status bar:
|
||||
[0aec2d6](https://github.com/Microsoft/vscode/commit/0aec2d6838b5e65cc74c33b853ffbd9fa191d636)
|
||||
|
||||
2. To be continued...
|
||||
64
test/smoke/README.md
Normal file
64
test/smoke/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# VS Code Smoke Testing
|
||||
|
||||
- Run `npm install`
|
||||
- Start the tests: `npm test -- --latest "path/to/binary"`.
|
||||
|
||||
If you want to include 'Data Migration' area tests use `npm test -- --latest path/to/binary --stable path/to/currentStable` respectively.
|
||||
|
||||
Detailed prerequisites and running steps are described [in our smoke test wiki](https://github.com/Microsoft/vscode/wiki/Smoke-Test#automated-smoke-test).
|
||||
|
||||
# Architecture
|
||||
* `main.js` is used to prepare all smoke test dependencies (fetching key bindings and 'Express' repository, running `npm install` there).
|
||||
* `mocha-runner.js` launches Mocha programmatically. It is spawned in Node environment from main.js to ensure that it is possible to listen on `stderr`s (primary `process.stderr` is not readable otherwise). This is accomplished because WebDriverIO command deprecation warnings need to be redirected to a separate log. Those warnings are coming from WebDriverIO because ChromeDriver has not migrated from JsonWire to W3C WebDriver protocol.
|
||||
* `test.ts` contains the main smoke test suite calling the tests that are bundled in areas and defined in `./tests/`. It includes all tests separated into mocha `describe()` groups that represent each of the areas of [Smoke Test document](https://github.com/Microsoft/vscode/wiki/Smoke-Test).
|
||||
|
||||
* `./areas/` folder contains a `.ts` file per each area of the document. E.g. `'Search'` area goes under `'search.ts'`. Every area file contains a list of methods with the name that represents the action that can be performed in the corresponding test. This reduces the amount of test suite code and means that if the UI changes, the fix need only be applied in one place. The name of the method reflects the action the tester would do if he would perform the test manually. See [Selenium Page Objects Wiki](https://github.com/SeleniumHQ/selenium/wiki/PageObjects) and [Selenium Bot Style Tests Wiki](https://github.com/SeleniumHQ/selenium/wiki/Bot-Style-Tests) for a good explanation of the implementation. Every smoke test area contains methods that are used in a bot-style approach in `main.ts`.
|
||||
* `./spectron/` wraps the Spectron, with WebDriverIO API wrapped in `client.ts` and instance of Spectron Application is wrapped in `application.ts`.
|
||||
|
||||
* `./test_data/` folder contains temporary data used by smoke test (cloned express repository, temporary user-data-dir/extensions-dir).
|
||||
* `./test_data/screenshots` has action screenshots captured by a smoke test when performing actions during runtime. Screenshots are split in folders per each test.
|
||||
|
||||
# Adding new area
|
||||
To contribute a new smoke test area, add `${area}.ts` file under `./areas/`. All related tests to the area should go to the alike named file under `./tests/${area}.ts`. This has to follow the bot-style approach described in the links mentioned above. Methods should be calling WebDriverIO API through `SpectronClient` class. If there is no existing WebDriverIO method, add it to the class.
|
||||
|
||||
# Adding new test
|
||||
To add new test, `./test/${area}.ts` should be updated. The same instruction-style principle needs to be followed with the called area method names that reflect manual tester's actions.
|
||||
|
||||
# Debugging
|
||||
1. Add the following configuration to launch.json, specifying binaries in `args`:
|
||||
```json
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Smoke Test",
|
||||
"program": "${workspaceRoot}/test/smoke/out/main.js",
|
||||
"cwd": "${workspaceRoot}/test/smoke",
|
||||
"timeout": 240000,
|
||||
"port": 9999,
|
||||
"args": [
|
||||
"-l",
|
||||
"path/to/Code.exe"
|
||||
],
|
||||
"outFiles": [
|
||||
"${cwd}/out/**/*.js"
|
||||
]
|
||||
}
|
||||
```
|
||||
2. In main.js add `--debug-brk=9999` as a first argument to the place where `out/mocha-runner.js` is spawned.
|
||||
|
||||
# Screenshots
|
||||
Almost on every automated test action it captures a screenshot. These help to determine an issue, if smoke test fails. The normal workflow is that you understand what code is doing and then try to match it up with screenshots obtained from the test.
|
||||
|
||||
# Running "Out of Sources"
|
||||
If you did a fix in VS Code that you need in order for the smoke test to succeed, here is how you can run the smoke test against the sources of VS Code:
|
||||
* Set related environment variables in the console:
|
||||
* `export NODE_ENV=development`
|
||||
* `export VSCODE_DEV=1`
|
||||
* `export VSCODE_CLI=1`
|
||||
* open `application.ts`
|
||||
* pass in the vscode folder as argument to the application
|
||||
* e.g. instead of `args: args` type `args: ['/Users/bpasero/Development/vscode', ...args]`
|
||||
* `cd test/smoke`
|
||||
* `npm install`
|
||||
* `npm test -- --latest <path to electron>`
|
||||
* e.g. on macOS: `npm test -- --latest <path to vscode>/.build/electron/Code\ -\ OSS.app/Contents/MacOS/Electron`
|
||||
26
test/smoke/package.json
Normal file
26
test/smoke/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "code-oss-dev-smoke-test",
|
||||
"version": "0.1.0",
|
||||
"main": "./src/main.js",
|
||||
"scripts": {
|
||||
"compile": "tsc",
|
||||
"pretest": "tsc",
|
||||
"test": "node out/main.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^2.2.41",
|
||||
"@types/node": "^6.0.70",
|
||||
"@types/webdriverio": "^4.6.1",
|
||||
"@types/electron": "~1.4.37",
|
||||
"@types/rimraf": "^0.0.28",
|
||||
"@types/htmlparser2": "^3.7.29",
|
||||
"mocha": "^3.2.0",
|
||||
"spectron": "~3.6.4",
|
||||
"typescript": "^2.2.2",
|
||||
"rimraf": "^2.6.1",
|
||||
"commander": "^2.9.0",
|
||||
"simple-git": "^1.73.0",
|
||||
"strip-json-comments": "^2.0.1",
|
||||
"htmlparser2": "^3.9.2"
|
||||
}
|
||||
}
|
||||
188
test/smoke/src/areas/common.ts
Normal file
188
test/smoke/src/areas/common.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
import { Util } from '../helpers/utilities';
|
||||
|
||||
/**
|
||||
* Contains methods that are commonly used across test areas.
|
||||
*/
|
||||
export class CommonActions {
|
||||
private util: Util;
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
this.util = new Util();
|
||||
}
|
||||
|
||||
public async getWindowTitle(): Promise<any> {
|
||||
return this.spectron.client.getTitle();
|
||||
}
|
||||
|
||||
public enter(): Promise<any> {
|
||||
return this.spectron.client.keys(['Enter', 'NULL']);
|
||||
}
|
||||
|
||||
public async addSetting(setting: string, value: string): Promise<any> {
|
||||
await this.spectron.command('workbench.action.openGlobalSettings');
|
||||
await this.spectron.wait();
|
||||
await this.spectron.client.keys(['ArrowDown', 'NULL', 'ArrowRight', 'NULL'], false);
|
||||
await this.spectron.client.keys(`"${setting}": "${value}"`);
|
||||
await this.spectron.wait();
|
||||
return this.saveOpenedFile();
|
||||
}
|
||||
|
||||
public async newUntitledFile(): Promise<any> {
|
||||
await this.spectron.command('workbench.action.files.newUntitledFile');
|
||||
return this.spectron.wait();
|
||||
}
|
||||
|
||||
public closeTab(): Promise<any> {
|
||||
return this.spectron.client.keys(['Control', 'w', 'NULL']);
|
||||
}
|
||||
|
||||
public async getTab(tabName: string, active?: boolean): Promise<any> {
|
||||
await this.closeCurrentNotification(); // close any notification messages that could overlap tabs
|
||||
|
||||
let tabSelector = active ? '.tab.active' : 'div';
|
||||
let el = await this.spectron.client.element(`.tabs-container ${tabSelector}[aria-label="${tabName}, tab"]`);
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async selectTab(tabName: string): Promise<any> {
|
||||
await this.closeCurrentNotification(); // close any notification messages that could overlap tabs
|
||||
return this.spectron.client.click(`.tabs-container div[aria-label="${tabName}, tab"]`);
|
||||
}
|
||||
|
||||
public async openFirstMatchFile(fileName: string): Promise<any> {
|
||||
await this.openQuickOpen();
|
||||
await this.type(fileName);
|
||||
await this.spectron.wait();
|
||||
await this.enter();
|
||||
return this.spectron.wait();
|
||||
}
|
||||
|
||||
public saveOpenedFile(): Promise<any> {
|
||||
return this.spectron.command('workbench.action.files.save');
|
||||
}
|
||||
|
||||
public type(text: string): Promise<any> {
|
||||
let spectron = this.spectron;
|
||||
|
||||
return new Promise(function (res) {
|
||||
let textSplit = text.split(' ');
|
||||
|
||||
async function type(i: number) {
|
||||
if (!textSplit[i] || textSplit[i].length <= 0) {
|
||||
return res();
|
||||
}
|
||||
|
||||
const toType = textSplit[i + 1] ? `${textSplit[i]} ` : textSplit[i];
|
||||
await spectron.client.keys(toType, false);
|
||||
await spectron.client.keys(['NULL']);
|
||||
await type(i + 1);
|
||||
}
|
||||
|
||||
return type(0);
|
||||
});
|
||||
}
|
||||
|
||||
public showCommands(): Promise<any> {
|
||||
return this.spectron.command('workbench.action.showCommands');
|
||||
}
|
||||
|
||||
public openQuickOpen(): Promise<any> {
|
||||
return this.spectron.command('workbench.action.quickOpen');
|
||||
}
|
||||
|
||||
public closeQuickOpen(): Promise<any> {
|
||||
return this.spectron.command('workbench.action.closeQuickOpen');
|
||||
}
|
||||
|
||||
public selectNextQuickOpenElement(): Promise<any> {
|
||||
return this.spectron.client.keys(['ArrowDown', 'NULL']);
|
||||
}
|
||||
|
||||
public async getQuickOpenElements(): Promise<number> {
|
||||
const elements = await this.spectron.waitFor(this.spectron.client.elements, 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row');
|
||||
return elements.value.length;
|
||||
}
|
||||
|
||||
public async openFile(fileName: string, explorer?: boolean): Promise<any> {
|
||||
let selector = `div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)}`;
|
||||
if (explorer) {
|
||||
selector += ' explorer-item';
|
||||
}
|
||||
selector += '"]';
|
||||
|
||||
try {
|
||||
await this.spectron.waitFor(this.spectron.client.doubleClick, selector);
|
||||
} catch (e) {
|
||||
return Promise.reject(`Cannot fine ${fileName} in a viewlet.`);
|
||||
}
|
||||
|
||||
return this.spectron.wait();
|
||||
}
|
||||
|
||||
public getExtensionSelector(fileName: string): string {
|
||||
const extension = fileName.split('.')[1];
|
||||
if (extension === 'js') {
|
||||
return 'js-ext-file-icon javascript-lang-file-icon';
|
||||
} else if (extension === 'json') {
|
||||
return 'json-ext-file-icon json-lang-file-icon';
|
||||
} else if (extension === 'md') {
|
||||
return 'md-ext-file-icon markdown-lang-file-icon';
|
||||
}
|
||||
|
||||
throw new Error('No class defined for this file extension');
|
||||
}
|
||||
|
||||
public async getEditorFirstLinePlainText(): Promise<any> {
|
||||
const trials = 3;
|
||||
let retry = 0;
|
||||
let error;
|
||||
|
||||
while (retry < trials) {
|
||||
try {
|
||||
const span = await this.spectron.client.getText('.view-lines span span');
|
||||
if (Array.isArray(span)) {
|
||||
return span[0];
|
||||
}
|
||||
|
||||
return span;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
retry++;
|
||||
|
||||
if (retry < trials) {
|
||||
await this.spectron.wait();
|
||||
} else {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject('Could not obtain text on the first line of an editor: ' + error);
|
||||
}
|
||||
|
||||
public removeFile(filePath: string): void {
|
||||
this.util.removeFile(filePath);
|
||||
}
|
||||
|
||||
public removeDirectory(directory: string): Promise<any> {
|
||||
try {
|
||||
return this.util.rimraf(directory);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to remove ${directory} with an error: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
private closeCurrentNotification(): Promise<any> {
|
||||
return this.spectron.command('workbench.action.closeMessages');
|
||||
}
|
||||
}
|
||||
64
test/smoke/src/areas/configuration-views.ts
Normal file
64
test/smoke/src/areas/configuration-views.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
|
||||
export enum ActivityBarPosition {
|
||||
LEFT = 0,
|
||||
RIGHT = 1
|
||||
};
|
||||
|
||||
export class ConfigurationView {
|
||||
// Stores key binding defined for the toggle of activity bar position
|
||||
private keybinding: string[];
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public async getEditorLineNumbers(): Promise<any> {
|
||||
const lineNumbers = await this.spectron.client.elements('.line-numbers');
|
||||
|
||||
return lineNumbers.value.length;
|
||||
}
|
||||
|
||||
public enterKeybindingsView(): any {
|
||||
return this.spectron.command('workbench.action.openGlobalKeybindings');
|
||||
}
|
||||
|
||||
public selectFirstKeybindingsMatch(): any {
|
||||
return this.spectron.waitFor(this.spectron.client.click, 'div[aria-label="Keybindings"] .monaco-list-row.keybinding-item');
|
||||
}
|
||||
|
||||
public changeKeybinding(): any {
|
||||
return this.spectron.command('editor.action.defineKeybinding');
|
||||
}
|
||||
|
||||
public enterBinding(keys: string[]): any {
|
||||
this.keybinding = keys;
|
||||
return this.spectron.client.keys(keys);
|
||||
}
|
||||
|
||||
public toggleActivityBarPosition(): any {
|
||||
return this.spectron.client.keys(this.keybinding);
|
||||
}
|
||||
|
||||
public async getActivityBar(position: ActivityBarPosition) {
|
||||
let positionClass: string;
|
||||
|
||||
if (position === ActivityBarPosition.LEFT) {
|
||||
positionClass = 'left';
|
||||
} else if (position === ActivityBarPosition.RIGHT) {
|
||||
positionClass = 'right';
|
||||
} else {
|
||||
throw new Error('No such position for activity bar defined.');
|
||||
}
|
||||
try {
|
||||
return await this.spectron.waitFor(this.spectron.client.getHTML, `.part.activitybar.${positionClass}`);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
62
test/smoke/src/areas/css.ts
Normal file
62
test/smoke/src/areas/css.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
|
||||
export enum CSSProblem {
|
||||
WARNING = 0,
|
||||
ERROR = 1
|
||||
};
|
||||
|
||||
export class CSS {
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public openQuickOutline(): any {
|
||||
return this.spectron.command('workbench.action.gotoSymbol');
|
||||
}
|
||||
|
||||
public toggleProblemsView(): any {
|
||||
return this.spectron.command('workbench.actions.view.problems');
|
||||
}
|
||||
|
||||
public async getEditorProblem(problemType: CSSProblem): Promise<any> {
|
||||
let selector;
|
||||
if (problemType === CSSProblem.WARNING) {
|
||||
selector = 'greensquiggly';
|
||||
} else if (problemType === CSSProblem.ERROR) {
|
||||
selector = 'redsquiggly';
|
||||
} else {
|
||||
throw new Error('No such problem type defined.');
|
||||
}
|
||||
|
||||
let el = await this.spectron.client.element(`.view-overlays .cdr.${selector}`);
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async getProblemsViewsProblem(problemType: CSSProblem): Promise<any> {
|
||||
let selector;
|
||||
if (problemType === CSSProblem.WARNING) {
|
||||
selector = 'warning';
|
||||
} else if (problemType === CSSProblem.ERROR) {
|
||||
selector = 'error';
|
||||
} else {
|
||||
throw new Error('No such problem type defined.');
|
||||
}
|
||||
|
||||
let el = await this.spectron.client.element(`div[aria-label="Problems grouped by files"] .icon.${selector}`);
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
26
test/smoke/src/areas/data-loss.ts
Normal file
26
test/smoke/src/areas/data-loss.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
|
||||
export class DataLoss {
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
}
|
||||
|
||||
public openExplorerViewlet(): Promise<any> {
|
||||
return this.spectron.command('workbench.view.explorer');
|
||||
}
|
||||
|
||||
public async verifyTabIsDirty(tabName: string, active?: boolean): Promise<any> {
|
||||
let activeSelector = active ? '.active' : '';
|
||||
let el = await this.spectron.client.element(`.tabs-container .tab.dirty${activeSelector}[aria-label="${tabName}, tab"]`);
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
112
test/smoke/src/areas/extensions.ts
Normal file
112
test/smoke/src/areas/extensions.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
import { CommonActions } from './common';
|
||||
|
||||
var htmlparser = require('htmlparser2');
|
||||
|
||||
export class Extensions {
|
||||
|
||||
private readonly extensionsViewletSelector = 'div[id="workbench.view.extensions"]';
|
||||
private viewletExtensionIndex: number;
|
||||
|
||||
constructor(private spectron: SpectronApplication, private common: CommonActions) {
|
||||
}
|
||||
|
||||
public async openExtensionsViewlet(): Promise<any> {
|
||||
await this.spectron.command('workbench.view.extensions');
|
||||
return this.spectron.wait();
|
||||
}
|
||||
|
||||
public async searchForExtension(name: string): Promise<any> {
|
||||
const searchBoxSelector = `${this.extensionsViewletSelector} .search-box`;
|
||||
|
||||
await this.spectron.client.clearElement(searchBoxSelector);
|
||||
try {
|
||||
await this.spectron.client.click(searchBoxSelector, false);
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to click on search box in extensions viewlet.');
|
||||
}
|
||||
await this.spectron.client.keys(name);
|
||||
|
||||
return this.spectron.client.keys(['NULL', 'Enter', 'NULL']);
|
||||
}
|
||||
|
||||
public async installExtension(name: string): Promise<any> {
|
||||
const extensionListSelector = `${this.extensionsViewletSelector} .monaco-list-rows`;
|
||||
this.viewletExtensionIndex = await this.getExtensionIndex(name, extensionListSelector);
|
||||
|
||||
try {
|
||||
return this.spectron.client.click(`${extensionListSelector}>:nth-child(${this.viewletExtensionIndex}) .extension .extension-action.install`);
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to click on install button for selected extension.');
|
||||
}
|
||||
}
|
||||
|
||||
public getExtensionReloadText(): Promise<any> {
|
||||
try {
|
||||
return this.spectron.waitFor(this.spectron.client.getText, `${this.extensionsViewletSelector} .monaco-list-rows>:nth-child(${this.viewletExtensionIndex}) .extension .extension-action.reload`);
|
||||
} catch (e) {
|
||||
return Promise.reject('Reload was not prompted for an installed extension.');
|
||||
}
|
||||
}
|
||||
|
||||
public async activateExtension(): Promise<any> {
|
||||
await this.common.showCommands();
|
||||
await this.common.type('Smoke Test Check');
|
||||
await this.spectron.wait();
|
||||
return this.common.enter();
|
||||
}
|
||||
|
||||
public verifyStatusbarItem(): Promise<any> {
|
||||
try {
|
||||
return this.spectron.waitFor(this.spectron.client.getText, '.statusbar-item.statusbar-entry span[title="smoke test"]');
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to validate extension contribution.');
|
||||
}
|
||||
}
|
||||
|
||||
private getExtensionIndex(name: string, extensionListSelector: string): Promise<number> {
|
||||
return this.spectron.waitFor(this.spectron.client.getHTML, extensionListSelector).then(html => {
|
||||
return new Promise<number>((res, rej) => {
|
||||
let extensionIndex: number = 0;
|
||||
let extension: boolean;
|
||||
let tags: string[] = [];
|
||||
let parser = new htmlparser.Parser({
|
||||
onopentag: function (name, attribs) {
|
||||
if (name === 'div' && attribs.class === 'extension') {
|
||||
extensionIndex++;
|
||||
extension = true;
|
||||
}
|
||||
if (extension) {
|
||||
tags.push(name);
|
||||
}
|
||||
},
|
||||
ontext: function (text) {
|
||||
if (extension && text === name) {
|
||||
parser.end();
|
||||
}
|
||||
},
|
||||
onclosetag: function (name) {
|
||||
if (extension) {
|
||||
tags.pop();
|
||||
}
|
||||
if (extension && tags.length === 0) {
|
||||
extension = false;
|
||||
}
|
||||
},
|
||||
onend: function () {
|
||||
if (extensionIndex === 0) {
|
||||
return rej(`${name} extension was not found.`);
|
||||
}
|
||||
return res(extensionIndex);
|
||||
}
|
||||
});
|
||||
parser.write(html);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
21
test/smoke/src/areas/first-experience.ts
Normal file
21
test/smoke/src/areas/first-experience.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
|
||||
export class FirstExperience {
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public async getWelcomeTab(): Promise<any> {
|
||||
let el = await this.spectron.client.element('.vs_code_welcome_page-name-file-icon');
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
167
test/smoke/src/areas/git.ts
Normal file
167
test/smoke/src/areas/git.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
import { CommonActions } from './common';
|
||||
|
||||
var htmlparser = require('htmlparser2');
|
||||
|
||||
export class Git {
|
||||
private editorChangeIndex: number;
|
||||
|
||||
constructor(private spectron: SpectronApplication, private commonActions: CommonActions) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public openGitViewlet(): Promise<any> {
|
||||
return this.spectron.command('workbench.view.scm');
|
||||
}
|
||||
|
||||
public getScmIconChanges(): Promise<any> {
|
||||
return this.spectron.waitFor(this.spectron.client.getText, 'div[id="workbench.parts.activitybar"] .badge.scm-viewlet-label .badge-content');
|
||||
}
|
||||
|
||||
public async verifyScmChange(fileName: string): Promise<any> {
|
||||
let el;
|
||||
try {
|
||||
el = await this.spectron.client.element(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.commonActions.getExtensionSelector(fileName)}"]`);
|
||||
} catch (e) {
|
||||
return Promise.reject(`${fileName} change is not present in SCM viewlet.`);
|
||||
}
|
||||
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async getOriginalAppJsBodyVarName(): Promise<any> {
|
||||
this.editorChangeIndex = await this.getFirstChangeIndex('cdr line-delete', '.editor.original .view-overlays');
|
||||
return this.spectron.waitFor(this.spectron.client.getText, `.editor.original .view-lines>:nth-child(${this.editorChangeIndex}) .mtk11`);
|
||||
}
|
||||
|
||||
public getModifiedAppJsBodyVarName(): Promise<any> {
|
||||
return this.spectron.waitFor(this.spectron.client.getText, `.editor.modified .view-lines>:nth-child(${this.editorChangeIndex}) .mtk11`);
|
||||
}
|
||||
|
||||
public async stageFile(fileName: string): Promise<any> {
|
||||
try {
|
||||
await this.spectron.client.moveToObject(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.commonActions.getExtensionSelector(fileName)}"`);
|
||||
} catch (e) {
|
||||
return Promise.reject(`${fileName} was not found in SCM viewlet`);
|
||||
}
|
||||
|
||||
await this.spectron.wait();
|
||||
|
||||
try {
|
||||
await this.spectron.client.click('.action-label.icon.contrib-cmd-icon-4');
|
||||
} catch (e) {
|
||||
return Promise.reject('Stage button was not found');
|
||||
}
|
||||
return this.spectron.wait();
|
||||
}
|
||||
|
||||
public async unstageFile(fileName: string): Promise<any> {
|
||||
try {
|
||||
await this.spectron.client.moveToObject(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.commonActions.getExtensionSelector(fileName)}"`);
|
||||
} catch (e) {
|
||||
return Promise.reject(`${fileName} was not found in SCM viewlet`);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.spectron.client.click('.action-label.icon.contrib-cmd-icon-6');
|
||||
} catch (e) {
|
||||
return Promise.reject('Unstage button was not found.');
|
||||
}
|
||||
return this.spectron.wait();
|
||||
}
|
||||
|
||||
public async getStagedCount(): Promise<any> {
|
||||
let scmHeaders: Array<string>;
|
||||
try {
|
||||
scmHeaders = await this.spectron.waitFor(this.spectron.client.getText, '.scm-status.show-file-icons .monaco-list-rows .name'); // get all headers
|
||||
}
|
||||
catch (e) {
|
||||
return Promise.reject('No row names in SCM viewlet were found.');
|
||||
}
|
||||
|
||||
const stagedTitle = scmHeaders.find((val) => {
|
||||
return val.match(/staged/i) ? true : false;
|
||||
});
|
||||
|
||||
if (!stagedTitle) {
|
||||
return Promise.reject(`No 'Staged' header title found in SCM viewlet`);
|
||||
}
|
||||
|
||||
const monacoRowIndex = scmHeaders.indexOf(stagedTitle);
|
||||
try {
|
||||
return this.spectron.waitFor(this.spectron.client.getText, `.scm-status.show-file-icons .monaco-list-rows>:nth-child(${monacoRowIndex + 1}) .monaco-count-badge`);
|
||||
} catch (e) {
|
||||
return Promise.reject('Stage count badge cannot be found');
|
||||
}
|
||||
}
|
||||
|
||||
public focusOnCommitBox(): Promise<any> {
|
||||
try {
|
||||
return this.spectron.client.click('div[id="workbench.view.scm"] textarea');
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to focus on commit box: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
public async pressCommit(): Promise<any> {
|
||||
try {
|
||||
await this.spectron.client.click('.action-label.icon.contrib-cmd-icon-10');
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to press commit: ' + e);
|
||||
}
|
||||
|
||||
return this.spectron.wait();
|
||||
}
|
||||
|
||||
public getOutgoingChanges(): Promise<string> {
|
||||
try {
|
||||
return this.spectron.client.getText('a[title="Synchronize Changes"]');
|
||||
} catch (e) {
|
||||
return Promise.reject(`Failed to obtain 'synchronize changes' title value from the status bar.`);
|
||||
}
|
||||
}
|
||||
|
||||
private getFirstChangeIndex(changeClass: string, selector: string): Promise<number> {
|
||||
return this.spectron.waitFor(this.spectron.client.getHTML, selector).then(html => {
|
||||
return new Promise<number>((res, rej) => {
|
||||
let lineIndex: number = 0;
|
||||
let changeFound: boolean;
|
||||
let tags: string[] = [];
|
||||
let parser = new htmlparser.Parser({
|
||||
onopentag: function (name: string, attribs: any) {
|
||||
tags.push(name);
|
||||
if (name === 'div' && !attribs.class) {
|
||||
lineIndex++;
|
||||
} else if (name === 'div' && attribs.class === changeClass) {
|
||||
changeFound = true;
|
||||
parser.end();
|
||||
}
|
||||
},
|
||||
onclosetag: function (name) {
|
||||
// Terminate once last tag is closed
|
||||
tags.pop();
|
||||
if (!changeFound && tags.length === 0) {
|
||||
parser.end();
|
||||
}
|
||||
},
|
||||
onend: function () {
|
||||
if (!changeFound) {
|
||||
return rej(`No changes in the diff found.`);
|
||||
}
|
||||
return res(lineIndex);
|
||||
}
|
||||
});
|
||||
parser.write(html);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
54
test/smoke/src/areas/integrated-terminal.ts
Normal file
54
test/smoke/src/areas/integrated-terminal.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
import { CommonActions } from './common';
|
||||
|
||||
export class IntegratedTerminal {
|
||||
|
||||
public static terminalSelector = 'div[id="workbench.panel.terminal"]';
|
||||
public static terminalRowsSelector = 'div[id="workbench.panel.terminal"] .xterm-rows';
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public async openTerminal(commonActions: CommonActions): Promise<any> {
|
||||
// Backquote dispatching does not work in OS X
|
||||
if (process.platform === 'darwin') {
|
||||
await commonActions.showCommands();
|
||||
await commonActions.type('Toggle Integrated Terminal');
|
||||
return commonActions.enter();
|
||||
}
|
||||
|
||||
await this.spectron.command('workbench.action.terminal.toggleTerminal');
|
||||
|
||||
// If no terminal panel was opened, try triggering terminal from quick open
|
||||
try {
|
||||
await this.spectron.client.getHTML(IntegratedTerminal.terminalSelector);
|
||||
} catch (e) {
|
||||
await commonActions.openQuickOpen();
|
||||
await this.spectron.client.keys('>Toggle Integrated Terminal');
|
||||
await this.spectron.client.keys(['Enter', 'NULL']);
|
||||
}
|
||||
}
|
||||
|
||||
public async commandOutputHas(result: string): Promise<boolean> {
|
||||
const rows = await this.spectron.client.elements(`${IntegratedTerminal.terminalRowsSelector} div`);
|
||||
for (let i = 0; i < rows.value.length; i++) {
|
||||
let rowText;
|
||||
try {
|
||||
rowText = await this.spectron.client.getText(`${IntegratedTerminal.terminalRowsSelector}>:nth-child(${i + 1})`);
|
||||
} catch (e) {
|
||||
return Promise.reject(`Failed to obtain text from line ${i + 1} from the terminal.`);
|
||||
}
|
||||
if (rowText.trim() === result) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
53
test/smoke/src/areas/javascript-debug.ts
Normal file
53
test/smoke/src/areas/javascript-debug.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
var stripJsonComments = require('strip-json-comments');
|
||||
|
||||
export class JavaScriptDebug {
|
||||
private readonly sidebarSelector = '.margin-view-overlays';
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public openDebugViewlet(): Promise<any> {
|
||||
return this.spectron.command('workbench.view.debug');
|
||||
}
|
||||
|
||||
public async pressConfigureLaunchJson(): Promise<any> {
|
||||
try {
|
||||
await this.spectron.waitFor(this.spectron.client.click, 'ul[aria-label="Debug actions"] .action-label.icon.debug-action.configure');
|
||||
} catch (e) {
|
||||
return Promise.reject('Clicking on debug configuration gear failed.');
|
||||
}
|
||||
await this.spectron.wait();
|
||||
await this.spectron.client.keys(['ArrowDown', 'NULL', 'Enter']);
|
||||
return this.spectron.wait();
|
||||
}
|
||||
|
||||
public async getProgramConfigValue(): Promise<any> {
|
||||
const lines = stripJsonComments(await this.spectron.client.getText('.view-lines'));
|
||||
const json = JSON.parse(lines);
|
||||
return json.configurations[0].program;
|
||||
}
|
||||
|
||||
public setBreakpointOnLine(lineNumber: number): Promise<any> {
|
||||
try {
|
||||
return this.spectron.client.leftClick(`${this.sidebarSelector}>:nth-child(${lineNumber})`, 5, 5);
|
||||
} catch (e) {
|
||||
return Promise.reject('Setting breakpoint failed: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
public async verifyBreakpointOnLine(lineNumber: number): Promise<any> {
|
||||
let el = await this.spectron.client.element(`${this.sidebarSelector}>:nth-child(${lineNumber}) .cgmr.debug-breakpoint-glyph`);
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
186
test/smoke/src/areas/javascript.ts
Normal file
186
test/smoke/src/areas/javascript.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
|
||||
var htmlparser = require('htmlparser2');
|
||||
|
||||
export class JavaScript {
|
||||
private appVarSelector: string;
|
||||
private expressVarSelector: string;
|
||||
|
||||
private foldSelector: string;
|
||||
private foldLine: number;
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public openQuickOutline(): Promise<any> {
|
||||
return this.spectron.command('workbench.action.gotoSymbol');
|
||||
}
|
||||
|
||||
public async findAppReferences(): Promise<any> {
|
||||
await this.setAppVarSelector();
|
||||
try {
|
||||
await this.spectron.client.click(this.appVarSelector, false);
|
||||
} catch (e) {
|
||||
return Promise.reject(`Failed to select 'app' variable.`);
|
||||
}
|
||||
|
||||
return this.spectron.command('editor.action.referenceSearch.trigger');
|
||||
}
|
||||
|
||||
public async getTitleReferencesCount(): Promise<any> {
|
||||
const meta = await this.spectron.client.getText('.reference-zone-widget.results-loaded .peekview-title .meta');
|
||||
|
||||
return meta.match(/\d+/)[0];
|
||||
}
|
||||
|
||||
public async getTreeReferencesCount(): Promise<any> {
|
||||
const treeElems = await this.spectron.client.elements('.reference-zone-widget.results-loaded .ref-tree.inline .show-twisties .monaco-tree-row');
|
||||
|
||||
return treeElems.value.length;
|
||||
}
|
||||
|
||||
public async renameApp(newValue: string): Promise<any> {
|
||||
await this.setAppVarSelector();
|
||||
|
||||
try {
|
||||
await this.spectron.client.click(this.appVarSelector);
|
||||
} catch (e) {
|
||||
return Promise.reject(`Failed to select 'app' variable.`);
|
||||
}
|
||||
await this.spectron.command('editor.action.rename');
|
||||
await this.spectron.wait();
|
||||
return this.spectron.client.keys(newValue, false);
|
||||
}
|
||||
|
||||
public async getNewAppName(): Promise<any> {
|
||||
return this.spectron.client.getText(this.appVarSelector);
|
||||
}
|
||||
|
||||
public async toggleFirstCommentFold(): Promise<any> {
|
||||
this.foldLine = await this.getLineIndexOfFirstFoldableElement(`.margin-view-overlays`);
|
||||
this.foldSelector = `.margin-view-overlays>:nth-child(${this.foldLine})`;
|
||||
|
||||
try {
|
||||
return this.spectron.client.click(`${this.foldSelector} .cldr.folding`);
|
||||
} catch (e) {
|
||||
return Promise.reject('Clicking on fold element failed ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
public async getFirstCommentFoldedIcon(): Promise<any> {
|
||||
if (!this.foldSelector) {
|
||||
return Promise.reject('No code folding happened to be able to check for a folded icon.');
|
||||
}
|
||||
|
||||
return this.spectron.client.getHTML(`${this.foldSelector} .cldr.folding.collapsed`);
|
||||
}
|
||||
|
||||
public async getNextLineNumberAfterFold(): Promise<any> {
|
||||
if (!this.foldLine) {
|
||||
return Promise.reject('Folded line was not set, most likely because fold was not toggled initially.');
|
||||
}
|
||||
|
||||
return this.spectron.client.getText(`.margin-view-overlays>:nth-child(${this.foldLine + 1}) .line-numbers`);
|
||||
}
|
||||
|
||||
public async goToExpressDefinition(): Promise<any> {
|
||||
await this.setExpressVarSelector();
|
||||
try {
|
||||
await this.spectron.client.click(this.expressVarSelector);
|
||||
} catch (e) {
|
||||
return Promise.reject(`Clicking on express variable failed: ` + e);
|
||||
}
|
||||
|
||||
return this.spectron.command('editor.action.goToDeclaration');
|
||||
}
|
||||
|
||||
public async peekExpressDefinition(): Promise<any> {
|
||||
await this.setExpressVarSelector();
|
||||
try {
|
||||
await this.spectron.client.click(this.expressVarSelector);
|
||||
} catch (e) {
|
||||
return Promise.reject('Clicking on express variable failed: ' + e);
|
||||
}
|
||||
|
||||
return this.spectron.command('editor.action.previewDeclaration');
|
||||
}
|
||||
|
||||
public async getPeekExpressResultName(): Promise<any> {
|
||||
return this.spectron.client.getText('.reference-zone-widget.results-loaded .filename');
|
||||
}
|
||||
|
||||
private async setAppVarSelector(): Promise<any> {
|
||||
if (!this.appVarSelector) {
|
||||
const lineIndex = await this.getLineIndexOfFirst('app', '.view-lines');
|
||||
this.appVarSelector = `.view-lines>:nth-child(${lineIndex}) .mtk11`;
|
||||
}
|
||||
}
|
||||
|
||||
private async setExpressVarSelector(): Promise<any> {
|
||||
if (!this.expressVarSelector) {
|
||||
const lineIndex = await this.getLineIndexOfFirst('express', '.view-lines');
|
||||
this.expressVarSelector = `.view-lines>:nth-child(${lineIndex}) .mtk10`;
|
||||
}
|
||||
}
|
||||
|
||||
private getLineIndexOfFirst(string: string, selector: string): Promise<number> {
|
||||
return this.spectron.waitFor(this.spectron.client.getHTML, selector).then(html => {
|
||||
return new Promise<number>((res, rej) => {
|
||||
let lineIndex: number = 0;
|
||||
let stringFound: boolean;
|
||||
let parser = new htmlparser.Parser({
|
||||
onopentag: function (name: string, attribs: any) {
|
||||
if (name === 'div' && attribs.class === 'view-line') {
|
||||
lineIndex++;
|
||||
}
|
||||
},
|
||||
ontext: function (text) {
|
||||
if (!stringFound && text === string) {
|
||||
stringFound = true;
|
||||
parser.end();
|
||||
}
|
||||
},
|
||||
onend: function () {
|
||||
if (!stringFound) {
|
||||
return rej(`No ${string} in editor found.`);
|
||||
}
|
||||
return res(lineIndex);
|
||||
}
|
||||
});
|
||||
parser.write(html);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getLineIndexOfFirstFoldableElement(selector: string): Promise<number> {
|
||||
return this.spectron.waitFor(this.spectron.client.getHTML, selector).then(html => {
|
||||
return new Promise<number>((res, rej) => {
|
||||
let lineIndex: number = 0;
|
||||
let foldFound: boolean;
|
||||
let parser = new htmlparser.Parser({
|
||||
onopentag: function (name: string, attribs: any) {
|
||||
if (name === 'div' && !attribs.class) {
|
||||
lineIndex++;
|
||||
} else if (name === 'div' && attribs.class.indexOf('cldr folding') !== -1) {
|
||||
foldFound = true;
|
||||
parser.end();
|
||||
}
|
||||
},
|
||||
onend: function () {
|
||||
if (!foldFound) {
|
||||
return rej(`No foldable elements found.`);
|
||||
}
|
||||
return res(lineIndex);
|
||||
}
|
||||
});
|
||||
parser.write(html);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
70
test/smoke/src/areas/localization.ts
Normal file
70
test/smoke/src/areas/localization.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
|
||||
export enum ViewletType {
|
||||
SEARCH = 0,
|
||||
SCM = 1,
|
||||
DEBUG = 2,
|
||||
EXTENSIONS = 3
|
||||
}
|
||||
|
||||
export class Localization {
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public async getOpenEditorsText(): Promise<string> {
|
||||
let explorerTitles;
|
||||
try {
|
||||
explorerTitles = await this.spectron.client.getText('div[id="workbench.view.explorer"] .title span');
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to get span of title in explorer viewlet.');
|
||||
}
|
||||
|
||||
return explorerTitles[0];
|
||||
}
|
||||
|
||||
public async openViewlet(type: ViewletType): Promise<any> {
|
||||
let command;
|
||||
|
||||
switch (type) {
|
||||
case ViewletType.SEARCH:
|
||||
command = 'workbench.view.search';
|
||||
break;
|
||||
case ViewletType.SCM:
|
||||
command = 'workbench.view.scm';
|
||||
break;
|
||||
case ViewletType.DEBUG:
|
||||
command = 'workbench.view.debug';
|
||||
break;
|
||||
case ViewletType.EXTENSIONS:
|
||||
command = 'workbench.view.extensions';
|
||||
break;
|
||||
}
|
||||
|
||||
await this.spectron.command(command, false);
|
||||
return this.spectron.wait();
|
||||
}
|
||||
|
||||
public getOpenedViewletTitle(): Promise<string> {
|
||||
try {
|
||||
return this.spectron.client.getText('div[id="workbench.parts.sidebar"] .title-label span');
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to get span of title label in explorer viewlet.');
|
||||
}
|
||||
}
|
||||
|
||||
public getExtensionsSearchPlaceholder(): Promise<string> {
|
||||
try {
|
||||
return this.spectron.client.getAttribute('div[id="workbench.view.extensions"] .search-box', 'placeholder');
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to get extension viewlet search box placeholder.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
74
test/smoke/src/areas/search.ts
Normal file
74
test/smoke/src/areas/search.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
|
||||
export class Search {
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public openSearchViewlet(): Promise<any> {
|
||||
return this.spectron.command('workbench.view.search');
|
||||
}
|
||||
|
||||
public async searchFor(text: string): Promise<any> {
|
||||
await this.spectron.client.keys(text);
|
||||
return this.spectron.client.keys(['NULL', 'Enter', 'NULL'], false);
|
||||
}
|
||||
|
||||
public setReplaceText(text: string): any {
|
||||
try {
|
||||
return this.spectron.client.setValue('.viewlet .input[title="Replace"]', text);
|
||||
} catch (e) {
|
||||
return Promise.reject('Cannot set replace input in the viewlet: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
public replaceFirstMatch(): any {
|
||||
try {
|
||||
return this.spectron.client.click('.monaco-tree-rows.show-twisties .action-label.icon.action-replace-all');
|
||||
} catch (e) {
|
||||
return Promise.reject('Cannot replace the search first match: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
public getResultText(): any {
|
||||
return this.spectron.waitFor(this.spectron.client.getText, '.search-viewlet .message>p');
|
||||
}
|
||||
|
||||
public toggleSearchDetails(): any {
|
||||
try {
|
||||
return this.spectron.client.click('.query-details .more');
|
||||
} catch (e) {
|
||||
return Promise.reject('Toggling search details failed: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
public toggleReplace(): any {
|
||||
try {
|
||||
return this.spectron.client.click('.monaco-button.toggle-replace-button.collapse');
|
||||
} catch (e) {
|
||||
return Promise.reject('Toggling replace failed: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
public hoverOverResultCount(): any {
|
||||
try {
|
||||
return this.spectron.waitFor(this.spectron.client.moveToObject, '.monaco-count-badge');
|
||||
} catch (e) {
|
||||
return Promise.reject('Hovering over result count failed: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
public dismissResult(): any {
|
||||
try {
|
||||
return this.spectron.client.click('.action-label.icon.action-remove');
|
||||
} catch (e) {
|
||||
return Promise.reject('Clicking on dismissing result failed: ' + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
103
test/smoke/src/areas/statusbar.ts
Normal file
103
test/smoke/src/areas/statusbar.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
|
||||
export enum StatusBarElement {
|
||||
BRANCH_STATUS = 0,
|
||||
SYNC_STATUS = 1,
|
||||
PROBLEMS_STATUS = 2,
|
||||
SELECTION_STATUS = 3,
|
||||
INDENTATION_STATUS = 4,
|
||||
ENCODING_STATUS = 5,
|
||||
EOL_STATUS = 6,
|
||||
LANGUAGE_STATUS = 7,
|
||||
FEEDBACK_ICON = 8
|
||||
}
|
||||
|
||||
export class StatusBar {
|
||||
|
||||
private selectorsMap: Map<StatusBarElement, string>;
|
||||
private readonly mainSelector = 'div[id="workbench.parts.statusbar"]';
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
this.populateSelectorsMap();
|
||||
}
|
||||
|
||||
public async isVisible(element: StatusBarElement): Promise<boolean> {
|
||||
const selector = this.selectorsMap.get(element);
|
||||
if (!selector) {
|
||||
throw new Error('No such element in the status bar defined.');
|
||||
}
|
||||
|
||||
return this.spectron.client.isVisible(selector);
|
||||
}
|
||||
|
||||
public async clickOn(element: StatusBarElement): Promise<any> {
|
||||
const selector = this.selectorsMap.get(element);
|
||||
if (!selector) {
|
||||
throw new Error('No such element in the status bar defined.');
|
||||
}
|
||||
|
||||
try {
|
||||
return this.spectron.client.click(selector);
|
||||
} catch (e) {
|
||||
return Promise.reject(`Clicking on status bar element ${selector} failed.`);
|
||||
}
|
||||
}
|
||||
|
||||
public async getProblemsView(): Promise<any> {
|
||||
let el = await this.spectron.client.element('div[id="workbench.panel.markers"]');
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async getFeedbackView(): Promise<any> {
|
||||
let el = await this.spectron.client.element('.feedback-form');
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public isQuickOpenWidgetVisible(): Promise<any> {
|
||||
return this.spectron.client.isVisible('.quick-open-widget');
|
||||
}
|
||||
|
||||
public async getEditorHighlightedLine(lineNumber: number): Promise<any> {
|
||||
let el = await this.spectron.client.element(`.monaco-editor .view-overlays>:nth-child(${lineNumber}) .current-line`);
|
||||
if (el.status === 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async getEOLMode(): Promise<any> {
|
||||
const selector = this.selectorsMap.get(StatusBarElement.EOL_STATUS);
|
||||
if (!selector) {
|
||||
throw new Error('No such element in the status bar defined.');
|
||||
}
|
||||
|
||||
return this.spectron.client.getText(selector);
|
||||
}
|
||||
|
||||
private populateSelectorsMap(): void {
|
||||
this.selectorsMap = new Map<StatusBarElement, string>();
|
||||
this.selectorsMap.set(StatusBarElement.BRANCH_STATUS, `${this.mainSelector} .octicon.octicon-git-branch`);
|
||||
this.selectorsMap.set(StatusBarElement.SYNC_STATUS, `${this.mainSelector} .octicon.octicon-sync`);
|
||||
this.selectorsMap.set(StatusBarElement.PROBLEMS_STATUS, `${this.mainSelector} .task-statusbar-item[title="Problems"]`);
|
||||
this.selectorsMap.set(StatusBarElement.SELECTION_STATUS, `${this.mainSelector} .editor-status-selection`);
|
||||
this.selectorsMap.set(StatusBarElement.INDENTATION_STATUS, `${this.mainSelector} .editor-status-indentation`);
|
||||
this.selectorsMap.set(StatusBarElement.ENCODING_STATUS, `${this.mainSelector} .editor-status-encoding`);
|
||||
this.selectorsMap.set(StatusBarElement.EOL_STATUS, `${this.mainSelector} .editor-status-eol`);
|
||||
this.selectorsMap.set(StatusBarElement.LANGUAGE_STATUS, `${this.mainSelector} .editor-status-mode`);
|
||||
this.selectorsMap.set(StatusBarElement.FEEDBACK_ICON, `${this.mainSelector} .dropdown.send-feedback`);
|
||||
}
|
||||
}
|
||||
89
test/smoke/src/areas/tasks.ts
Normal file
89
test/smoke/src/areas/tasks.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
import { IntegratedTerminal } from './integrated-terminal';
|
||||
|
||||
export class Tasks {
|
||||
|
||||
private readonly outputViewSelector = IntegratedTerminal.terminalRowsSelector;
|
||||
private readonly workbenchPanelSelector = 'div[id="workbench.parts.panel"]';
|
||||
private readonly problemsViewSelector = 'div[id="workbench.panel.markers"] .monaco-tree-row.expanded';
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public async build(): Promise<any> {
|
||||
await this.spectron.command('workbench.action.tasks.build');
|
||||
await this.spectron.wait(); // wait for build to finish
|
||||
|
||||
// Validate that it has finished
|
||||
let trial = 0;
|
||||
while (trial < 3) {
|
||||
// Determine build status based on the statusbar indicator, don't continue until task has been terminated
|
||||
try {
|
||||
return await this.spectron.client.getValue('.task-statusbar-item-progress.builder-hidden');
|
||||
} catch (e) {
|
||||
await this.spectron.wait();
|
||||
trial++;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject('Could not determine if the task was terminated based on status bar progress spinner.');
|
||||
}
|
||||
|
||||
public openProblemsView(): Promise<any> {
|
||||
return this.spectron.command('workbench.actions.view.problems');
|
||||
}
|
||||
|
||||
public async outputContains(string: string): Promise<boolean> {
|
||||
const output: string = await this.spectron.waitFor(this.spectron.client.getText, this.outputViewSelector);
|
||||
|
||||
if (output.indexOf(string) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async selectOutputViewType(type: string): Promise<any> {
|
||||
await this.openOutputView();
|
||||
|
||||
try {
|
||||
return this.spectron.client.selectByValue(`${this.workbenchPanelSelector} .select-box`, type);
|
||||
} catch (e) {
|
||||
return Promise.reject(`Failed to select ${type} as workbench panel output.`);
|
||||
}
|
||||
}
|
||||
|
||||
public getOutputViewType(): Promise<any> {
|
||||
return this.spectron.client.getValue(`${this.workbenchPanelSelector} .select-box`);
|
||||
}
|
||||
|
||||
public getProblemsViewFirstElementName(): Promise<any> {
|
||||
try {
|
||||
return this.spectron.waitFor(this.spectron.client.getText, `${this.problemsViewSelector} .label-name`);
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to get problem label from Problems view: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
public getProblemsViewFirstElementCount(): Promise<any> {
|
||||
try {
|
||||
return this.spectron.waitFor(this.spectron.client.getText, `${this.problemsViewSelector} .monaco-count-badge`);
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to get problem count from Problems view: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
private openOutputView(): Promise<any> {
|
||||
try {
|
||||
return this.spectron.command('workbench.action.output.toggleOutput');
|
||||
} catch (e) {
|
||||
return Promise.reject('Failed to toggle output view');
|
||||
}
|
||||
}
|
||||
}
|
||||
40
test/smoke/src/helpers/screenshot.ts
Normal file
40
test/smoke/src/helpers/screenshot.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../spectron/application';
|
||||
var fs = require('fs');
|
||||
|
||||
const __testTime = new Date().toISOString();
|
||||
|
||||
export class Screenshot {
|
||||
private index: number = 0;
|
||||
private testPath: string;
|
||||
|
||||
constructor(private spectron: SpectronApplication, testName: string, testRetry: number) {
|
||||
const testTime = this.sanitizeFolderName(__testTime);
|
||||
testName = this.sanitizeFolderName(testName);
|
||||
|
||||
this.testPath = `test_data/screenshots/${testTime}/${testName}/${testRetry}`;
|
||||
this.createFolder(this.testPath);
|
||||
}
|
||||
|
||||
public async capture(): Promise<any> {
|
||||
const image = await this.spectron.app.browserWindow.capturePage();
|
||||
await new Promise((c, e) => fs.writeFile(`${this.testPath}/${this.index++}.png`, image, err => err ? e(err) : c()));
|
||||
}
|
||||
|
||||
private createFolder(name: string): void {
|
||||
name.split('/').forEach((folderName, i, fullPath) => {
|
||||
const folder = fullPath.slice(0, i + 1).join('/');
|
||||
if (!fs.existsSync(folder)) {
|
||||
fs.mkdirSync(folder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sanitizeFolderName(name: string): string {
|
||||
return name.replace(/[&*:\/]/g, '');
|
||||
}
|
||||
}
|
||||
37
test/smoke/src/helpers/utilities.ts
Normal file
37
test/smoke/src/helpers/utilities.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
var fs = require('fs');
|
||||
var rimraf = require('rimraf');
|
||||
|
||||
/**
|
||||
* Contains methods that are commonly used across test areas.
|
||||
*/
|
||||
export class Util {
|
||||
constructor() {
|
||||
// noop
|
||||
}
|
||||
|
||||
public removeFile(filePath: string): void {
|
||||
try {
|
||||
fs.unlinkSync(`${filePath}`);
|
||||
} catch (e) {
|
||||
if (e.code !== 'ENOENT') {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public rimraf(directory: string): Promise<any> {
|
||||
return new Promise((res, rej) => {
|
||||
rimraf(directory, (err) => {
|
||||
if (err) {
|
||||
rej(err);
|
||||
}
|
||||
res();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
222
test/smoke/src/main.ts
Normal file
222
test/smoke/src/main.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
const program = require('commander');
|
||||
const git = require('simple-git')();
|
||||
const child_process = require('child_process');
|
||||
const path = require('path');
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
const testDataPath = path.join(process.cwd(), 'test_data');
|
||||
const codeWorkspacePath = path.join(testDataPath, 'smoketest.code-workspace');
|
||||
const testRepoUrl = 'https://github.com/Microsoft/vscode-smoketest-express';
|
||||
const testRepoLocalDir = path.join(testDataPath, 'vscode-smoketest-express');
|
||||
const keybindingsUrl = 'https://raw.githubusercontent.com/Microsoft/vscode-docs/master/scripts/keybindings';
|
||||
|
||||
mkdirp.sync(testDataPath);
|
||||
|
||||
program
|
||||
.option('-l, --latest <file path>', 'path to the latest VS Code to test')
|
||||
.option('-s, --stable [file path]', 'path to the stable VS Code to be used in data migration tests');
|
||||
|
||||
program.on('--help', () => {
|
||||
console.log(' Examples:');
|
||||
console.log('');
|
||||
console.log(' $ npm test -- --latest path/to/binary');
|
||||
console.log(' $ npm test -- -l path/to/binary');
|
||||
console.log('');
|
||||
console.log(' $ npm test -- --latest path/to/latest/binary --stable path/to/stable/binary');
|
||||
console.log(' $ npm test -- -l path/to/latest/binary -s path/to/stable/binary');
|
||||
console.log('');
|
||||
});
|
||||
program.parse(process.argv);
|
||||
|
||||
if (!program.latest) {
|
||||
fail('You must specify the binary to run the smoke test against');
|
||||
}
|
||||
if (!binaryExists(program.latest) || (program.stable && !binaryExists(program.stable))) {
|
||||
fail('The file path to electron binary does not exist or permissions do not allow to execute it. Please check the path provided.');
|
||||
}
|
||||
if (parseInt(process.version.substr(1)) < 6) {
|
||||
fail('Please update your Node version to greater than 6 to run the smoke test.');
|
||||
}
|
||||
|
||||
// Setting up environment variables
|
||||
process.env.VSCODE_LATEST_PATH = program.latest;
|
||||
if (program.stable) {
|
||||
process.env.VSCODE_STABLE_PATH = program.stable;
|
||||
}
|
||||
process.env.SMOKETEST_REPO = testRepoLocalDir;
|
||||
if (program.latest && (program.latest.indexOf('Code - Insiders') /* macOS/Windows */ || program.latest.indexOf('code-insiders') /* Linux */) >= 0) {
|
||||
process.env.VSCODE_EDITION = 'insiders';
|
||||
}
|
||||
process.env.VSCODE_WORKSPACE_PATH = codeWorkspacePath;
|
||||
|
||||
// Setting up 'vscode-smoketest-express' project
|
||||
let os = process.platform.toString();
|
||||
if (os === 'darwin') {
|
||||
os = 'osx';
|
||||
}
|
||||
else if (os === 'win32') {
|
||||
os = 'win';
|
||||
}
|
||||
|
||||
main().catch(err => console.error(err));
|
||||
|
||||
async function main(): Promise<void> {
|
||||
await getKeybindings(`${keybindingsUrl}/doc.keybindings.${os}.json`, path.join(testDataPath, 'keybindings.json'));
|
||||
|
||||
const workspace = {
|
||||
id: (Date.now() + Math.round(Math.random() * 1000)).toString(),
|
||||
folders: [
|
||||
toUri(path.join(testRepoLocalDir, 'public')),
|
||||
toUri(path.join(testRepoLocalDir, 'routes')),
|
||||
toUri(path.join(testRepoLocalDir, 'views'))
|
||||
]
|
||||
};
|
||||
|
||||
await createWorkspaceFile(codeWorkspacePath, workspace);
|
||||
await cleanOrClone(testRepoUrl, testRepoLocalDir);
|
||||
await execute('npm install', testRepoLocalDir);
|
||||
await runTests();
|
||||
}
|
||||
|
||||
function fail(errorMessage): void {
|
||||
console.error(errorMessage);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function toUri(path: string): string {
|
||||
if (os === 'win') {
|
||||
return `file:///${path.replace(/\\/g, '/')}`;
|
||||
}
|
||||
|
||||
return `file://${path}`;
|
||||
}
|
||||
|
||||
function runTests(): void {
|
||||
console.log('Running tests...');
|
||||
var proc = child_process.spawn(process.execPath, [
|
||||
'out/mocha-runner.js'
|
||||
]);
|
||||
proc.stdout.on('data', data => {
|
||||
console.log(data.toString());
|
||||
});
|
||||
proc.stderr.on('data', data => {
|
||||
var date = new Date().toLocaleString();
|
||||
fs.appendFile(path.join(testDataPath, 'errors.log'), `${date}: ${data.toString()}`, (err) => {
|
||||
if (err) {
|
||||
throw new Error(`Could not write stderr to errors.log with the following error: ${err}`);
|
||||
};
|
||||
});
|
||||
});
|
||||
proc.on('exit', (code) => {
|
||||
process.exit(code);
|
||||
});
|
||||
}
|
||||
|
||||
async function cleanOrClone(repo: string, dir: string): Promise<any> {
|
||||
console.log('Cleaning or cloning test project repository...');
|
||||
|
||||
if (!folderExists(dir)) {
|
||||
await gitClone(repo, dir);
|
||||
} else {
|
||||
git.cwd(dir);
|
||||
await new Promise((c, e) => git.fetch(err => err ? e(err) : c()));
|
||||
await gitResetAndClean();
|
||||
}
|
||||
}
|
||||
|
||||
function gitClone(repo: string, dir: string): Promise<any> {
|
||||
return new Promise((res, rej) => {
|
||||
git.clone(repo, dir, () => {
|
||||
console.log('Test repository successfully cloned.');
|
||||
res();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function gitResetAndClean(): Promise<any> {
|
||||
await new Promise((c, e) => git.reset(['FETCH_HEAD', '--hard'], err => err ? e(err) : c()));
|
||||
await new Promise((c, e) => git.clean('f', ['-d'], err => err ? e(err) : c()));
|
||||
console.log('Test project was successfully reset to initial state.');
|
||||
}
|
||||
|
||||
function execute(cmd: string, dir: string): Promise<any> {
|
||||
return new Promise((res, rej) => {
|
||||
console.log(`Running ${cmd}...`);
|
||||
child_process.exec(cmd, { cwd: dir, stdio: [0, 1, 2] }, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
rej(error);
|
||||
}
|
||||
if (stderr) {
|
||||
console.error(stderr);
|
||||
}
|
||||
console.log(stdout);
|
||||
res();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getKeybindings(url: string, location: string): Promise<any> {
|
||||
console.log(`Fetching keybindings from ${url}...`);
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(url, (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
reject(`Failed to obtain key bindings with response code: ${res.statusCode}`);
|
||||
}
|
||||
|
||||
var buffer: Buffer[] = [];
|
||||
res.on('data', (chunk) => buffer.push(chunk));
|
||||
res.on('end', () => {
|
||||
fs.writeFile(location, Buffer.concat(buffer), 'utf8', () => {
|
||||
console.log('Keybindings were successfully fetched.');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}).on('error', (e) => {
|
||||
reject(`Failed to obtain key bindings with an error: ${e}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createWorkspaceFile(path: string, workspace: any): Promise<any> {
|
||||
console.log(`Creating workspace file at ${path}...`);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.exists(path, exists => {
|
||||
if (exists) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
fs.writeFile(path, JSON.stringify(workspace, null, '\t'), error => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function folderExists(folder: string): boolean {
|
||||
try {
|
||||
fs.accessSync(folder, 'rw');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function binaryExists(filePath: string): boolean {
|
||||
try {
|
||||
fs.accessSync(filePath, 'x');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
16
test/smoke/src/mocha-runner.ts
Normal file
16
test/smoke/src/mocha-runner.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const MochaTest = require('mocha');
|
||||
|
||||
const mochaTest = new MochaTest({
|
||||
timeout: 60000,
|
||||
slow: 10000,
|
||||
useColors: true
|
||||
});
|
||||
mochaTest.addFile(require('path').join(process.cwd(), 'out/test.js'));
|
||||
mochaTest.run((failures) => {
|
||||
process.exit(failures);
|
||||
});
|
||||
171
test/smoke/src/spectron/application.ts
Normal file
171
test/smoke/src/spectron/application.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Application } from 'spectron';
|
||||
import { SpectronClient } from './client';
|
||||
import { Screenshot } from '../helpers/screenshot';
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
export const LATEST_PATH = process.env.VSCODE_LATEST_PATH;
|
||||
export const STABLE_PATH = process.env.VSCODE_STABLE_PATH;
|
||||
export const WORKSPACE_PATH = process.env.SMOKETEST_REPO;
|
||||
export const CODE_WORKSPACE_PATH = process.env.VSCODE_WORKSPACE_PATH;
|
||||
export const USER_DIR = 'test_data/temp_user_dir';
|
||||
export const EXTENSIONS_DIR = 'test_data/temp_extensions_dir';
|
||||
|
||||
/**
|
||||
* Wraps Spectron's Application instance with its used methods.
|
||||
*/
|
||||
export class SpectronApplication {
|
||||
public client: SpectronClient;
|
||||
|
||||
private spectron: Application;
|
||||
private keybindings: any[];
|
||||
private screenshot: Screenshot;
|
||||
|
||||
private readonly sampleExtensionsDir: string = 'test_data/sample_extensions_dir';
|
||||
private readonly pollTrials = 50;
|
||||
private readonly pollTimeout = 1; // in secs
|
||||
|
||||
constructor(electronPath: string, testName: string, private testRetry: number, args?: string[], chromeDriverArgs?: string[]) {
|
||||
if (!args) {
|
||||
args = [];
|
||||
}
|
||||
|
||||
// Prevent 'Getting Started' web page from opening on clean user-data-dir
|
||||
args.push('--skip-getting-started');
|
||||
|
||||
// Ensure that running over custom extensions directory, rather than picking up the one that was used by a tester previously
|
||||
let extensionDirIsSet = false;
|
||||
for (let arg of args) {
|
||||
if (arg.startsWith('--extensions-dir')) {
|
||||
extensionDirIsSet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!extensionDirIsSet) {
|
||||
args.push(`--extensions-dir=${this.sampleExtensionsDir}`);
|
||||
}
|
||||
|
||||
this.spectron = new Application({
|
||||
path: electronPath,
|
||||
args: args,
|
||||
chromeDriverArgs: chromeDriverArgs,
|
||||
startTimeout: 10000,
|
||||
requireName: 'nodeRequire'
|
||||
});
|
||||
this.testRetry += 1; // avoid multiplication by 0 for wait times
|
||||
this.screenshot = new Screenshot(this, testName, testRetry);
|
||||
this.client = new SpectronClient(this.spectron, this.screenshot);
|
||||
this.retrieveKeybindings();
|
||||
}
|
||||
|
||||
public get app(): Application {
|
||||
return this.spectron;
|
||||
}
|
||||
|
||||
public async start(): Promise<any> {
|
||||
await this.spectron.start();
|
||||
await this.focusOnWindow(1); // focuses on main renderer window
|
||||
await this.checkWindowReady();
|
||||
}
|
||||
|
||||
public async stop(): Promise<any> {
|
||||
if (this.spectron && this.spectron.isRunning()) {
|
||||
return await this.spectron.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public waitFor(func: (...args: any[]) => any, args: any): Promise<any> {
|
||||
return this.callClientAPI(func, args);
|
||||
}
|
||||
|
||||
public wait(): Promise<any> {
|
||||
return new Promise(resolve => setTimeout(resolve, this.testRetry * this.pollTimeout * 1000));
|
||||
}
|
||||
|
||||
public focusOnWindow(index: number): Promise<any> {
|
||||
return this.client.windowByIndex(index);
|
||||
}
|
||||
|
||||
private async checkWindowReady(): Promise<any> {
|
||||
await this.waitFor(this.spectron.client.getHTML, '[id="workbench.main.container"]');
|
||||
}
|
||||
|
||||
private retrieveKeybindings() {
|
||||
fs.readFile(path.join(process.cwd(), `test_data/keybindings.json`), 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
this.keybindings = JSON.parse(data);
|
||||
} catch (e) {
|
||||
throw new Error(`Error parsing keybindings JSON: ${e}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async callClientAPI(func: (...args: any[]) => Promise<any>, args: any): Promise<any> {
|
||||
let trial = 1;
|
||||
|
||||
while (true) {
|
||||
if (trial > this.pollTrials) {
|
||||
throw new Error(`Could not retrieve the element in ${this.testRetry * this.pollTrials * this.pollTimeout} seconds.`);
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await func.call(this.client, args, false);
|
||||
} catch (e) { }
|
||||
|
||||
if (result && result !== '') {
|
||||
await this.screenshot.capture();
|
||||
return result;
|
||||
}
|
||||
|
||||
await this.wait();
|
||||
trial++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the command from keybindings file and executes it with WebdriverIO client API
|
||||
* @param command command (e.g. 'workbench.action.files.newUntitledFile')
|
||||
*/
|
||||
public command(command: string, capture?: boolean): Promise<any> {
|
||||
const binding = this.keybindings.find(x => x['command'] === command);
|
||||
if (!binding) {
|
||||
return Promise.reject(`Key binding for ${command} was not found.`);
|
||||
}
|
||||
|
||||
const keys: string = binding.key;
|
||||
let keysToPress: string[] = [];
|
||||
|
||||
const chords = keys.split(' ');
|
||||
chords.forEach((chord) => {
|
||||
const keys = chord.split('+');
|
||||
keys.forEach((key) => keysToPress.push(this.transliterate(key)));
|
||||
keysToPress.push('NULL');
|
||||
});
|
||||
|
||||
return this.client.keys(keysToPress, capture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transliterates key names from keybindings file to WebdriverIO keyboard actions defined in:
|
||||
* https://w3c.github.io/webdriver/webdriver-spec.html#keyboard-actions
|
||||
*/
|
||||
private transliterate(key: string): string {
|
||||
switch (key) {
|
||||
case 'ctrl':
|
||||
return 'Control';
|
||||
case 'cmd':
|
||||
return 'Meta';
|
||||
default:
|
||||
return key.length === 1 ? key : key.charAt(0).toUpperCase() + key.slice(1);
|
||||
};
|
||||
}
|
||||
}
|
||||
127
test/smoke/src/spectron/client.ts
Normal file
127
test/smoke/src/spectron/client.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Application } from 'spectron';
|
||||
import { Screenshot } from '../helpers/screenshot';
|
||||
|
||||
/**
|
||||
* Abstracts the Spectron's WebdriverIO managed client property on the created Application instances.
|
||||
*/
|
||||
export class SpectronClient {
|
||||
|
||||
constructor(private spectron: Application, private shot: Screenshot) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public windowByIndex(index: number): Promise<any> {
|
||||
return this.spectron.client.windowByIndex(index);
|
||||
}
|
||||
|
||||
public async keys(keys: string[] | string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.keys(keys);
|
||||
}
|
||||
|
||||
public async getText(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.getText(selector);
|
||||
}
|
||||
|
||||
public async getHTML(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.getHTML(selector);
|
||||
}
|
||||
|
||||
public async click(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.click(selector);
|
||||
}
|
||||
|
||||
public async doubleClick(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.doubleClick(selector);
|
||||
}
|
||||
|
||||
public async leftClick(selector: string, xoffset: number, yoffset: number, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.leftClick(selector, xoffset, yoffset);
|
||||
}
|
||||
|
||||
public async rightClick(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.rightClick(selector);
|
||||
}
|
||||
|
||||
public async moveToObject(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.moveToObject(selector);
|
||||
}
|
||||
|
||||
public async setValue(selector: string, text: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.setValue(selector, text);
|
||||
}
|
||||
|
||||
public async elements(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.elements(selector);
|
||||
}
|
||||
|
||||
public async element(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.element(selector);
|
||||
}
|
||||
|
||||
public async dragAndDrop(sourceElem: string, destinationElem: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.dragAndDrop(sourceElem, destinationElem);
|
||||
}
|
||||
|
||||
public async selectByValue(selector: string, value: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.selectByValue(selector, value);
|
||||
}
|
||||
|
||||
public async getValue(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.getValue(selector);
|
||||
}
|
||||
|
||||
public async getAttribute(selector: string, attribute: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return Promise.resolve(this.spectron.client.getAttribute(selector, attribute));
|
||||
}
|
||||
|
||||
public clearElement(selector: string): any {
|
||||
return this.spectron.client.clearElement(selector);
|
||||
}
|
||||
|
||||
public buttonDown(): any {
|
||||
return this.spectron.client.buttonDown();
|
||||
}
|
||||
|
||||
public buttonUp(): any {
|
||||
return this.spectron.client.buttonUp();
|
||||
}
|
||||
|
||||
public async isVisible(selector: string, capture: boolean = true): Promise<any> {
|
||||
await this.screenshot(capture);
|
||||
return this.spectron.client.isVisible(selector);
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return this.spectron.client.getTitle();
|
||||
}
|
||||
|
||||
private async screenshot(capture: boolean): Promise<any> {
|
||||
if (capture) {
|
||||
try {
|
||||
await this.shot.capture();
|
||||
} catch (e) {
|
||||
throw new Error(`Screenshot could not be captured: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
test/smoke/src/test.ts
Normal file
40
test/smoke/src/test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { testDataMigration } from './tests/data-migration';
|
||||
import { testDataLoss } from './tests/data-loss';
|
||||
import { testExplorer } from './tests/explorer';
|
||||
import { testConfigViews } from './tests/configuration-views';
|
||||
import { testSearch } from './tests/search';
|
||||
import { testCSS } from './tests/css';
|
||||
import { testJavaScript } from './tests/javascript';
|
||||
import { testJavaScriptDebug } from './tests/javascript-debug';
|
||||
import { testGit } from './tests/git';
|
||||
import { testIntegratedTerminal } from './tests/integrated-terminal';
|
||||
import { testStatusbar } from './tests/statusbar';
|
||||
import { testTasks } from './tests/tasks';
|
||||
import { testExtensions } from './tests/extensions';
|
||||
import { testLocalization } from './tests/localization';
|
||||
import { testMultiRoot } from './tests/multiroot';
|
||||
|
||||
describe('Smoke:', () => {
|
||||
testDataMigration();
|
||||
testDataLoss();
|
||||
testExplorer();
|
||||
testConfigViews();
|
||||
testSearch();
|
||||
testCSS();
|
||||
testJavaScript();
|
||||
testJavaScriptDebug();
|
||||
testGit();
|
||||
testIntegratedTerminal();
|
||||
testStatusbar();
|
||||
testTasks();
|
||||
testExtensions();
|
||||
testLocalization();
|
||||
if (process.env.VSCODE_EDITION === 'insiders') {
|
||||
testMultiRoot(); // only enabled in insiders
|
||||
}
|
||||
});
|
||||
57
test/smoke/src/tests/configuration-views.ts
Normal file
57
test/smoke/src/tests/configuration-views.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { ConfigurationView, ActivityBarPosition } from '../areas/configuration-views';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testConfigViews() {
|
||||
describe('Configuration and views', () => {
|
||||
let configView: ConfigurationView;
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
configView = new ConfigurationView(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('turns off editor line numbers and verifies the live change', async function () {
|
||||
await common.newUntitledFile();
|
||||
await app.wait();
|
||||
let elementsCount = await configView.getEditorLineNumbers();
|
||||
assert.equal(elementsCount, 1, 'Line numbers are not present in the editor before disabling them.');
|
||||
await common.addSetting('editor.lineNumbers', 'off');
|
||||
await app.wait();
|
||||
elementsCount = await configView.getEditorLineNumbers();
|
||||
assert.equal(elementsCount, 0, 'Line numbers are still present in the editor after disabling them.');
|
||||
});
|
||||
|
||||
it(`changes 'workbench.action.toggleSidebarPosition' command key binding and verifies it`, async function () {
|
||||
await configView.enterKeybindingsView();
|
||||
await common.type('workbench.action.toggleSidebarPosition');
|
||||
await app.wait();
|
||||
await configView.selectFirstKeybindingsMatch();
|
||||
await configView.changeKeybinding();
|
||||
await configView.enterBinding(['Control', 'u', 'NULL']);
|
||||
await common.enter();
|
||||
let html = await configView.getActivityBar(ActivityBarPosition.RIGHT);
|
||||
assert.equal(html, undefined, 'Activity bar is positioned on the right, whereas should not be.');
|
||||
await app.wait();
|
||||
await configView.toggleActivityBarPosition();
|
||||
html = await configView.getActivityBar(ActivityBarPosition.RIGHT);
|
||||
assert.ok(html, 'Activity bar was not moved to right after toggling its position.');
|
||||
});
|
||||
});
|
||||
}
|
||||
61
test/smoke/src/tests/css.ts
Normal file
61
test/smoke/src/tests/css.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { CSS, CSSProblem } from '../areas/css';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testCSS() {
|
||||
describe('CSS', () => {
|
||||
let css: CSS;
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
css = new CSS(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('verifies quick outline', async function () {
|
||||
await common.openFirstMatchFile('style.css');
|
||||
await css.openQuickOutline();
|
||||
await app.wait();
|
||||
const count = await common.getQuickOpenElements();
|
||||
assert.equal(count, 2, 'Quick outline symbol count is wrong.');
|
||||
});
|
||||
|
||||
it('verifies warnings for the empty rule', async function () {
|
||||
await common.openFirstMatchFile('style.css');
|
||||
await common.type('.foo{}');
|
||||
await app.wait();
|
||||
let warning = await css.getEditorProblem(CSSProblem.WARNING);
|
||||
assert.ok(warning, `Warning squiggle is not shown in 'style.css'.`);
|
||||
await css.toggleProblemsView();
|
||||
warning = await css.getProblemsViewsProblem(CSSProblem.WARNING);
|
||||
assert.ok(warning, 'Warning does not appear in Problems view.');
|
||||
});
|
||||
|
||||
it('verifies that warning becomes an error once setting changed', async function () {
|
||||
await common.addSetting('css.lint.emptyRules', 'error');
|
||||
await common.openFirstMatchFile('style.css');
|
||||
await common.type('.foo{}');
|
||||
await app.wait();
|
||||
let error = await css.getEditorProblem(CSSProblem.ERROR);
|
||||
assert.ok(error, `Error squiggle is not shown in 'style.css'.`);
|
||||
await css.toggleProblemsView();
|
||||
error = await css.getProblemsViewsProblem(CSSProblem.ERROR);
|
||||
assert.ok(error, `Error does not appear in Problems view`);
|
||||
});
|
||||
});
|
||||
}
|
||||
74
test/smoke/src/tests/data-loss.ts
Normal file
74
test/smoke/src/tests/data-loss.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, USER_DIR, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { DataLoss } from '../areas/data-loss';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
let dl: DataLoss;
|
||||
|
||||
export function testDataLoss() {
|
||||
describe('Data Loss', () => {
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH], [`--user-data-dir=${USER_DIR}`]);
|
||||
common = new CommonActions(app);
|
||||
dl = new DataLoss(app);
|
||||
await common.removeDirectory(USER_DIR);
|
||||
|
||||
await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it(`verifies that 'hot exit' works for dirty files`, async function () {
|
||||
const textToType = 'Hello, Code', fileName = 'readme.md', untitled = 'Untitled-1';
|
||||
await common.newUntitledFile();
|
||||
await common.type(textToType);
|
||||
await dl.openExplorerViewlet();
|
||||
await common.openFile(fileName, true);
|
||||
await common.type(textToType);
|
||||
|
||||
await app.stop();
|
||||
await app.start();
|
||||
|
||||
// check tab presence
|
||||
assert.ok(await common.getTab(untitled), `${untitled} tab is not present after reopening.`);
|
||||
assert.ok(await common.getTab(fileName, true), `${fileName} tab is not present or is not active after reopening.`);
|
||||
// check if they marked as dirty (icon) and active tab is the last opened
|
||||
assert.ok(await dl.verifyTabIsDirty(untitled), `${untitled} tab is not dirty after reopening.`);
|
||||
assert.ok(await dl.verifyTabIsDirty(fileName, true), `${fileName} tab is not dirty after reopening.`);
|
||||
});
|
||||
|
||||
it(`verifies that contents of the dirty files are restored after 'hot exit'`, async function () {
|
||||
// make one dirty file,
|
||||
// create one untitled file
|
||||
const textToType = 'Hello, Code';
|
||||
|
||||
// create one untitled file
|
||||
await common.newUntitledFile();
|
||||
await common.type(textToType);
|
||||
|
||||
// make one dirty file,
|
||||
await common.openFile('readme.md', true);
|
||||
await common.type(textToType);
|
||||
|
||||
await app.stop();
|
||||
await app.start();
|
||||
|
||||
// check their contents
|
||||
let fileDirt = await common.getEditorFirstLinePlainText();
|
||||
assert.equal(fileDirt, textToType, 'Active file contents are different after restore.');
|
||||
await common.selectTab('Untitled-1');
|
||||
fileDirt = await common.getEditorFirstLinePlainText();
|
||||
assert.equal(fileDirt, textToType, 'Untitled file edit are different after restore.');
|
||||
});
|
||||
});
|
||||
}
|
||||
105
test/smoke/src/tests/data-migration.ts
Normal file
105
test/smoke/src/tests/data-migration.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, USER_DIR, STABLE_PATH, LATEST_PATH, WORKSPACE_PATH, EXTENSIONS_DIR } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testDataMigration() {
|
||||
if (!STABLE_PATH) {
|
||||
return;
|
||||
}
|
||||
|
||||
describe('Data Migration', () => {
|
||||
|
||||
afterEach(async function () {
|
||||
await app.stop();
|
||||
await common.removeDirectory(USER_DIR);
|
||||
return await common.removeDirectory(EXTENSIONS_DIR);
|
||||
});
|
||||
|
||||
function setupSpectron(context: Mocha.ITestCallbackContext, appPath: string, args?: string[]): void {
|
||||
if (!args) {
|
||||
args = [];
|
||||
}
|
||||
args.push(`--extensions-dir=${EXTENSIONS_DIR}`);
|
||||
|
||||
app = new SpectronApplication(appPath, context.test.fullTitle(), context.test.currentRetry(), args, [`--user-data-dir=${USER_DIR}`]);
|
||||
common = new CommonActions(app);
|
||||
}
|
||||
|
||||
it('checks if the Untitled file is restored migrating from stable to latest', async function () {
|
||||
const textToType = 'Very dirty file';
|
||||
|
||||
// Setting up stable version
|
||||
setupSpectron(this, STABLE_PATH);
|
||||
await app.start();
|
||||
|
||||
await common.newUntitledFile();
|
||||
await common.type(textToType);
|
||||
await app.stop();
|
||||
|
||||
await app.wait(); // wait until all resources are released (e.g. locked local storage)
|
||||
|
||||
// Checking latest version for the restored state
|
||||
setupSpectron(this, LATEST_PATH);
|
||||
await app.start();
|
||||
|
||||
assert.ok(await common.getTab('Untitled-1'), 'Untitled-1 tab was not restored after migration.');
|
||||
await common.selectTab('Untitled-1');
|
||||
const editorText = await common.getEditorFirstLinePlainText();
|
||||
assert.equal(editorText, textToType, 'Typed editor text does not match to the one after migration.');
|
||||
});
|
||||
|
||||
it('checks if the newly created dirty file is restored migrating from stable to latest', async function () {
|
||||
const fileName = 'test_data/plainFile',
|
||||
firstTextPart = 'This is going to be an unsaved file', secondTextPart = '_that is dirty.';
|
||||
|
||||
// Setting up stable version
|
||||
setupSpectron(this, STABLE_PATH, [fileName]);
|
||||
await common.removeFile(`${fileName}`);
|
||||
await app.start();
|
||||
|
||||
await common.type(firstTextPart);
|
||||
await common.saveOpenedFile();
|
||||
await app.wait();
|
||||
await common.type(secondTextPart);
|
||||
|
||||
await app.stop();
|
||||
await app.wait(); // wait until all resources are released (e.g. locked local storage)
|
||||
|
||||
// Checking latest version for the restored state
|
||||
setupSpectron(this, LATEST_PATH);
|
||||
await app.start();
|
||||
assert.ok(await common.getTab(fileName.split('/')[1]), `${fileName} was not restored after migration.`);
|
||||
await common.selectTab(fileName.split('/')[1]);
|
||||
const editorText = await common.getEditorFirstLinePlainText();
|
||||
assert.equal(editorText, firstTextPart.concat(secondTextPart), 'Entered text was not correctly restored after migration.');
|
||||
|
||||
// Cleanup
|
||||
await common.removeFile(`${fileName}`);
|
||||
});
|
||||
|
||||
it('cheks if opened tabs are restored migrating from stable to latest', async function () {
|
||||
const fileName1 = 'app.js', fileName2 = 'jsconfig.json', fileName3 = 'readme.md';
|
||||
setupSpectron(this, STABLE_PATH, [WORKSPACE_PATH]);
|
||||
await app.start();
|
||||
await common.openFile(fileName1, true);
|
||||
await common.openFile(fileName2, true);
|
||||
await common.openFile(fileName3, true);
|
||||
await app.stop();
|
||||
|
||||
setupSpectron(this, LATEST_PATH, [WORKSPACE_PATH]);
|
||||
await app.start();
|
||||
assert.ok(await common.getTab(fileName1), `${fileName1} tab was not restored after migration.`);
|
||||
assert.ok(await common.getTab(fileName2), `${fileName2} tab was not restored after migration.`);
|
||||
assert.ok(await common.getTab(fileName3), `${fileName3} tab was not restored after migration.`);
|
||||
});
|
||||
});
|
||||
}
|
||||
43
test/smoke/src/tests/explorer.ts
Normal file
43
test/smoke/src/tests/explorer.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testExplorer() {
|
||||
describe('Explorer', () => {
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('quick open search produces correct result', async function () {
|
||||
await common.openQuickOpen();
|
||||
await common.type('.js');
|
||||
await app.wait();
|
||||
const elCount = await common.getQuickOpenElements();
|
||||
assert.equal(elCount, 7);
|
||||
});
|
||||
|
||||
it('quick open respects fuzzy matching', async function () {
|
||||
await common.openQuickOpen();
|
||||
await common.type('a.s');
|
||||
await app.wait();
|
||||
const elCount = await common.getQuickOpenElements();
|
||||
assert.equal(elCount, 3);
|
||||
});
|
||||
});
|
||||
}
|
||||
74
test/smoke/src/tests/extensions.ts
Normal file
74
test/smoke/src/tests/extensions.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH, EXTENSIONS_DIR } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { Extensions } from '../areas/extensions';
|
||||
|
||||
var dns = require('dns');
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testExtensions() {
|
||||
|
||||
describe('Extensions', () => {
|
||||
let extensions: Extensions;
|
||||
const extensionName = 'vscode-smoketest-check';
|
||||
|
||||
beforeEach(async function () {
|
||||
const network = await networkAttached();
|
||||
if (!network) {
|
||||
return Promise.reject('There is no network connection for testing extensions.');
|
||||
}
|
||||
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH, `--extensions-dir=${EXTENSIONS_DIR}`]);
|
||||
common = new CommonActions(app);
|
||||
extensions = new Extensions(app, common);
|
||||
await common.removeDirectory(EXTENSIONS_DIR);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
await app.stop();
|
||||
return await common.removeDirectory(EXTENSIONS_DIR);
|
||||
});
|
||||
|
||||
it(`installs 'vscode-smoketest-check' extension and verifies reload is prompted`, async function () {
|
||||
await extensions.openExtensionsViewlet();
|
||||
await extensions.searchForExtension(extensionName);
|
||||
await app.wait();
|
||||
await extensions.installExtension(extensionName);
|
||||
await app.wait();
|
||||
assert.ok(await extensions.getExtensionReloadText(), 'Reload was not prompted after extension installation.');
|
||||
});
|
||||
|
||||
it(`installs an extension and checks if it works on restart`, async function () {
|
||||
await extensions.openExtensionsViewlet();
|
||||
await extensions.searchForExtension(extensionName);
|
||||
await app.wait();
|
||||
await extensions.installExtension(extensionName);
|
||||
await app.wait();
|
||||
await extensions.getExtensionReloadText();
|
||||
|
||||
await app.stop();
|
||||
await app.wait(); // wait until all resources are released (e.g. locked local storage)
|
||||
await app.start();
|
||||
await extensions.activateExtension();
|
||||
const statusbarText = await extensions.verifyStatusbarItem();
|
||||
assert.equal(statusbarText, 'VS Code Smoke Test Check', 'Extension contribution text does not match expected.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function networkAttached(): Promise<boolean> {
|
||||
return new Promise((res, rej) => {
|
||||
dns.resolve('marketplace.visualstudio.com', (err) => {
|
||||
err ? res(false) : res(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
69
test/smoke/src/tests/git.ts
Normal file
69
test/smoke/src/tests/git.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { Git } from '../areas/git';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testGit() {
|
||||
describe('Git', () => {
|
||||
let git: Git;
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
git = new Git(app, common);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('verifies current changes are picked up by Git viewlet', async function () {
|
||||
const changesCount = await git.getScmIconChanges();
|
||||
assert.equal(changesCount, 2);
|
||||
await git.openGitViewlet();
|
||||
assert.ok(await git.verifyScmChange('app.js'), 'app.js change does not appear in SCM viewlet.');
|
||||
assert.ok(await git.verifyScmChange('launch.json'), 'launch.json change does not appear in SCM viewlet.');
|
||||
});
|
||||
|
||||
it(`verifies 'app.js' diff viewer changes`, async function () {
|
||||
await git.openGitViewlet();
|
||||
await common.openFile('app.js');
|
||||
const original = await git.getOriginalAppJsBodyVarName();
|
||||
assert.equal(original, 'bodyParser', 'Original value from diff view is wrong.');
|
||||
const modified = await git.getModifiedAppJsBodyVarName();
|
||||
assert.equal(modified, 'ydobParser', 'Modified value from diff view is wrong.');
|
||||
});
|
||||
|
||||
it(`stages 'app.js' changes and checks stage count`, async function () {
|
||||
await git.openGitViewlet();
|
||||
await app.wait();
|
||||
await git.stageFile('app.js');
|
||||
const stagedCount = await git.getStagedCount();
|
||||
assert.equal(stagedCount, 1);
|
||||
|
||||
// Return back to unstaged state
|
||||
await git.unstageFile('app.js');
|
||||
});
|
||||
|
||||
it(`stages, commits change to 'app.js' locally and verifies outgoing change`, async function () {
|
||||
await git.openGitViewlet();
|
||||
await app.wait();
|
||||
await git.stageFile('app.js');
|
||||
await git.focusOnCommitBox();
|
||||
await common.type('Test commit');
|
||||
await git.pressCommit();
|
||||
const changes = await git.getOutgoingChanges();
|
||||
assert.equal(changes, ' 0↓ 1↑', 'Changes indicator is wrong in a status bar.');
|
||||
});
|
||||
});
|
||||
}
|
||||
40
test/smoke/src/tests/integrated-terminal.ts
Normal file
40
test/smoke/src/tests/integrated-terminal.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { IntegratedTerminal } from '../areas/integrated-terminal';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testIntegratedTerminal() {
|
||||
describe('Integrated Terminal', () => {
|
||||
let terminal: IntegratedTerminal;
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
terminal = new IntegratedTerminal(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it(`opens terminal, runs 'echo' and verifies the output`, async function () {
|
||||
const command = 'echo test';
|
||||
await terminal.openTerminal(common);
|
||||
await app.wait();
|
||||
await common.type(command);
|
||||
await common.enter();
|
||||
await app.wait();
|
||||
assert.ok(await terminal.commandOutputHas('test'), 'Terminal output does not contain echo.');
|
||||
});
|
||||
});
|
||||
}
|
||||
44
test/smoke/src/tests/javascript-debug.ts
Normal file
44
test/smoke/src/tests/javascript-debug.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { JavaScriptDebug } from '../areas/javascript-debug';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testJavaScriptDebug() {
|
||||
describe('Debugging JavaScript', () => {
|
||||
let jsDebug: JavaScriptDebug;
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
jsDebug = new JavaScriptDebug(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('autodetects program attribute for launch.json', async function () {
|
||||
await jsDebug.openDebugViewlet();
|
||||
await jsDebug.pressConfigureLaunchJson();
|
||||
const value = await jsDebug.getProgramConfigValue();
|
||||
process.platform === 'win32' ? assert.equal(value, '${workspaceRoot}\\bin\\www') : assert.equal(value, '${workspaceRoot}/bin/www');
|
||||
});
|
||||
|
||||
it(`can set a breakpoint and verify if it's set`, async function () {
|
||||
await common.openFirstMatchFile('index.js');
|
||||
await jsDebug.setBreakpointOnLine(6);
|
||||
const breakpoint = await jsDebug.verifyBreakpointOnLine(6);
|
||||
assert.ok(breakpoint, 'Breakpoint was not found on line 6.');
|
||||
});
|
||||
});
|
||||
}
|
||||
87
test/smoke/src/tests/javascript.ts
Normal file
87
test/smoke/src/tests/javascript.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { JavaScript } from '../areas/javascript';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testJavaScript() {
|
||||
describe('JavaScript', () => {
|
||||
let js: JavaScript;
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
js = new JavaScript(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('shows correct quick outline', async function () {
|
||||
await common.openFirstMatchFile('bin/www');
|
||||
await js.openQuickOutline();
|
||||
await app.wait();
|
||||
const symbols = await common.getQuickOpenElements();
|
||||
assert.equal(symbols, 12, 'Quick outline elements count does not match to expected.');
|
||||
});
|
||||
|
||||
it(`finds 'All References' to 'app'`, async function () {
|
||||
await common.openFirstMatchFile('bin/www');
|
||||
await js.findAppReferences();
|
||||
await app.wait();
|
||||
const titleCount = await js.getTitleReferencesCount();
|
||||
assert.equal(titleCount, 3, 'References count in widget title is not as expected.');
|
||||
const treeCount = await js.getTreeReferencesCount();
|
||||
assert.equal(treeCount, 3, 'References count in tree is not as expected.');
|
||||
});
|
||||
|
||||
it(`renames local 'app' variable`, async function () {
|
||||
await common.openFirstMatchFile('bin/www');
|
||||
|
||||
const newVarName = 'newApp';
|
||||
await js.renameApp(newVarName);
|
||||
await common.enter();
|
||||
const newName = await js.getNewAppName();
|
||||
assert.equal(newName, newVarName);
|
||||
});
|
||||
|
||||
it('folds/unfolds the code correctly', async function () {
|
||||
await common.openFirstMatchFile('bin/www');
|
||||
// Fold
|
||||
await js.toggleFirstCommentFold();
|
||||
const foldedIcon = await js.getFirstCommentFoldedIcon();
|
||||
assert.ok(foldedIcon, 'Folded icon was not found in the margin.');
|
||||
let nextLineNumber = await js.getNextLineNumberAfterFold();
|
||||
assert.equal(nextLineNumber, 7, 'Line number after folded code is wrong.');
|
||||
// Unfold
|
||||
await js.toggleFirstCommentFold();
|
||||
nextLineNumber = await js.getNextLineNumberAfterFold();
|
||||
assert.equal(nextLineNumber, 4, 'Line number with unfolded code is wrong.');
|
||||
});
|
||||
|
||||
it(`verifies that 'Go To Definition' works`, async function () {
|
||||
await common.openFirstMatchFile('app.js');
|
||||
await js.goToExpressDefinition();
|
||||
await app.wait();
|
||||
assert.ok(await common.getTab('index.d.ts'), 'Tab opened when navigating to definition is not as expected.');
|
||||
});
|
||||
|
||||
it(`verifies that 'Peek Definition' works`, async function () {
|
||||
await common.openFirstMatchFile('app.js');
|
||||
await js.peekExpressDefinition();
|
||||
await app.wait();
|
||||
const definitionFilename = await js.getPeekExpressResultName();
|
||||
assert.equal(definitionFilename, 'index.d.ts', 'Peek result is not as expected.');
|
||||
});
|
||||
});
|
||||
}
|
||||
50
test/smoke/src/tests/localization.ts
Normal file
50
test/smoke/src/tests/localization.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH, USER_DIR } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { Localization, ViewletType } from '../areas/localization';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testLocalization() {
|
||||
describe('Localization', () => {
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.test.fullTitle(), this.test.currentRetry(), [WORKSPACE_PATH, '--locale=DE'], [`--user-data-dir=${USER_DIR}`]);
|
||||
common = new CommonActions(app);
|
||||
const locale = new Localization(app);
|
||||
common.removeDirectory(USER_DIR);
|
||||
|
||||
await app.start();
|
||||
|
||||
let text = await locale.getOpenEditorsText();
|
||||
assert.equal(text.toLowerCase(), 'geöffnete editoren');
|
||||
|
||||
await locale.openViewlet(ViewletType.SEARCH);
|
||||
text = await locale.getOpenedViewletTitle();
|
||||
assert.equal(text.toLowerCase(), 'suchen');
|
||||
|
||||
await locale.openViewlet(ViewletType.SCM);
|
||||
await app.wait(); // wait until git extension is loaded
|
||||
text = await locale.getOpenedViewletTitle();
|
||||
assert.equal(text.toLowerCase(), 'quellcodeverwaltung: git');
|
||||
|
||||
await locale.openViewlet(ViewletType.DEBUG);
|
||||
text = await locale.getOpenedViewletTitle();
|
||||
assert.equal(text.toLowerCase(), 'debuggen');
|
||||
|
||||
await locale.openViewlet(ViewletType.EXTENSIONS);
|
||||
text = await locale.getExtensionsSearchPlaceholder();
|
||||
assert.equal(text.toLowerCase(), 'nach erweiterungen im marketplace suchen');
|
||||
});
|
||||
});
|
||||
}
|
||||
43
test/smoke/src/tests/multiroot.ts
Normal file
43
test/smoke/src/tests/multiroot.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, CODE_WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testMultiRoot() {
|
||||
describe('Multi Root', () => {
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [CODE_WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('shows results from all folders', async function () {
|
||||
await common.openQuickOpen();
|
||||
await app.wait();
|
||||
await common.type('*.*');
|
||||
await app.wait();
|
||||
const elCount = await common.getQuickOpenElements();
|
||||
assert.equal(elCount, 6);
|
||||
});
|
||||
|
||||
it('shows workspace name in title', async function () {
|
||||
await app.wait();
|
||||
const title = await common.getWindowTitle();
|
||||
assert.ok(title.indexOf('smoketest (Workspace)') >= 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
73
test/smoke/src/tests/search.ts
Normal file
73
test/smoke/src/tests/search.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { Search } from '../areas/search';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testSearch() {
|
||||
describe('Search', () => {
|
||||
let search: Search;
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
search = new Search(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('searches for body & checks for correct result number', async function () {
|
||||
const s = search;
|
||||
await s.openSearchViewlet();
|
||||
await s.searchFor('body');
|
||||
const result = await s.getResultText();
|
||||
assert.equal(result, '7 results in 4 files');
|
||||
});
|
||||
|
||||
it('searches only for *.js files & checks for correct result number', async function () {
|
||||
const s = search;
|
||||
await s.openSearchViewlet();
|
||||
await s.searchFor('body');
|
||||
await s.toggleSearchDetails();
|
||||
await s.searchFor('*.js');
|
||||
const results = await s.getResultText();
|
||||
assert.equal(results, '4 results in 1 file');
|
||||
});
|
||||
|
||||
it('dismisses result & checks for correct result number', async function () {
|
||||
const s = search;
|
||||
await s.openSearchViewlet();
|
||||
await s.searchFor('body');
|
||||
await s.hoverOverResultCount();
|
||||
await s.dismissResult();
|
||||
await app.wait();
|
||||
const result = await s.getResultText();
|
||||
assert.equal(result, '3 results in 3 files', 'Result number after dismissal does not match to expected.');
|
||||
});
|
||||
|
||||
it('replaces first search result with a replace term', async function () {
|
||||
const s = search;
|
||||
await s.openSearchViewlet();
|
||||
await s.searchFor('body');
|
||||
await s.toggleReplace();
|
||||
await s.setReplaceText('ydob');
|
||||
await s.hoverOverResultCount();
|
||||
await s.replaceFirstMatch();
|
||||
await app.wait();
|
||||
await common.saveOpenedFile();
|
||||
const result = await s.getResultText();
|
||||
assert.equal(result, '3 results in 3 files', 'Result number after replacemenet does not match to expected.');
|
||||
});
|
||||
});
|
||||
}
|
||||
94
test/smoke/src/tests/statusbar.ts
Normal file
94
test/smoke/src/tests/statusbar.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { CommonActions } from '../areas/common';
|
||||
import { StatusBarElement, StatusBar } from '../areas/statusbar';
|
||||
|
||||
let app: SpectronApplication;
|
||||
let common: CommonActions;
|
||||
|
||||
export function testStatusbar() {
|
||||
describe('Status Bar', () => {
|
||||
let statusBar: StatusBar;
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
common = new CommonActions(app);
|
||||
statusBar = new StatusBar(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('verifies presence of all default status bar elements', async function () {
|
||||
await app.wait();
|
||||
assert.ok(await statusBar.isVisible(StatusBarElement.BRANCH_STATUS), 'Branch indicator is not visible.');
|
||||
assert.ok(await statusBar.isVisible(StatusBarElement.FEEDBACK_ICON), 'Feedback icon is not visible.');
|
||||
assert.ok(await statusBar.isVisible(StatusBarElement.SYNC_STATUS), 'Sync indicator is not visible.');
|
||||
assert.ok(await statusBar.isVisible(StatusBarElement.PROBLEMS_STATUS), 'Problems indicator is not visible.');
|
||||
|
||||
await common.openFirstMatchFile('app.js');
|
||||
assert.ok(await statusBar.isVisible(StatusBarElement.ENCODING_STATUS), 'Encoding indicator is not visible.');
|
||||
assert.ok(await statusBar.isVisible(StatusBarElement.EOL_STATUS), 'EOL indicator is not visible.');
|
||||
assert.ok(await statusBar.isVisible(StatusBarElement.INDENTATION_STATUS), 'Indentation indicator is not visible.');
|
||||
assert.ok(await statusBar.isVisible(StatusBarElement.LANGUAGE_STATUS), 'Language indicator is not visible.');
|
||||
assert.ok(await statusBar.isVisible(StatusBarElement.SELECTION_STATUS), 'Selection indicator is not visible.');
|
||||
});
|
||||
|
||||
it(`verifies that 'quick open' opens when clicking on status bar elements`, async function () {
|
||||
await app.wait();
|
||||
await statusBar.clickOn(StatusBarElement.BRANCH_STATUS);
|
||||
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for branch indicator.');
|
||||
await common.closeQuickOpen();
|
||||
|
||||
await common.openFirstMatchFile('app.js');
|
||||
await statusBar.clickOn(StatusBarElement.INDENTATION_STATUS);
|
||||
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for indentation indicator.');
|
||||
await common.closeQuickOpen();
|
||||
await statusBar.clickOn(StatusBarElement.ENCODING_STATUS);
|
||||
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for encoding indicator.');
|
||||
await common.closeQuickOpen();
|
||||
await statusBar.clickOn(StatusBarElement.EOL_STATUS);
|
||||
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for EOL indicator.');
|
||||
await common.closeQuickOpen();
|
||||
await statusBar.clickOn(StatusBarElement.LANGUAGE_STATUS);
|
||||
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for language indicator.');
|
||||
await common.closeQuickOpen();
|
||||
});
|
||||
|
||||
it(`verifies that 'Problems View' appears when clicking on 'Problems' status element`, async function () {
|
||||
await statusBar.clickOn(StatusBarElement.PROBLEMS_STATUS);
|
||||
assert.ok(await statusBar.getProblemsView());
|
||||
});
|
||||
|
||||
it(`verifies that 'Tweet us feedback' pop-up appears when clicking on 'Feedback' icon`, async function () {
|
||||
await statusBar.clickOn(StatusBarElement.FEEDBACK_ICON);
|
||||
assert.ok(await statusBar.getFeedbackView());
|
||||
});
|
||||
|
||||
it(`checks if 'Go to Line' works if called from the status bar`, async function () {
|
||||
await common.openFirstMatchFile('app.js');
|
||||
await statusBar.clickOn(StatusBarElement.SELECTION_STATUS);
|
||||
const lineNumber = 15;
|
||||
await common.type(lineNumber.toString());
|
||||
await common.enter();
|
||||
assert.ok(await statusBar.getEditorHighlightedLine(lineNumber), 'Editor does not highlight the line.');
|
||||
});
|
||||
|
||||
it(`verifies if changing EOL is reflected in the status bar`, async function () {
|
||||
await common.openFirstMatchFile('app.js');
|
||||
await statusBar.clickOn(StatusBarElement.EOL_STATUS);
|
||||
await common.selectNextQuickOpenElement();
|
||||
await common.enter();
|
||||
const currentEOL = await statusBar.getEOLMode();
|
||||
assert.equal(currentEOL, 'CRLF');
|
||||
});
|
||||
});
|
||||
}
|
||||
56
test/smoke/src/tests/tasks.ts
Normal file
56
test/smoke/src/tests/tasks.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
|
||||
import { Tasks } from '../areas/tasks';
|
||||
|
||||
let app: SpectronApplication;
|
||||
|
||||
export function testTasks() {
|
||||
describe('Tasks', () => {
|
||||
let tasks: Tasks;
|
||||
|
||||
beforeEach(async function () {
|
||||
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
|
||||
tasks = new Tasks(app);
|
||||
|
||||
return await app.start();
|
||||
});
|
||||
afterEach(async function () {
|
||||
return await app.stop();
|
||||
});
|
||||
|
||||
it('verifies that eslint task results in 1 problem', async function () {
|
||||
const expectedOutput = '1 problem (0 errors, 1 warning)';
|
||||
await tasks.build();
|
||||
const actualOutput = await tasks.outputContains(expectedOutput);
|
||||
assert.ok(actualOutput, `Output does not contain the following string: '${expectedOutput}'`);
|
||||
});
|
||||
|
||||
it(`is able to select 'Git' output`, async function () {
|
||||
await tasks.build();
|
||||
await app.wait();
|
||||
await tasks.selectOutputViewType('Git');
|
||||
const viewType = await tasks.getOutputViewType();
|
||||
assert.equal(viewType, 'Git');
|
||||
});
|
||||
|
||||
it('ensures that build task produces no-unused-vars message', async function () {
|
||||
await tasks.build();
|
||||
assert.ok(await tasks.outputContains(`'next' is defined but never used`), `Output does not contain no-unused-vars message`);
|
||||
});
|
||||
|
||||
it(`verifies build error is reflected in 'Problems View'`, async function () {
|
||||
await tasks.build();
|
||||
await tasks.openProblemsView();
|
||||
const problemName = await tasks.getProblemsViewFirstElementName();
|
||||
assert.equal(problemName, 'index.js', `'index.js' is not a build error.`);
|
||||
const problemsCount = await tasks.getProblemsViewFirstElementCount();
|
||||
assert.equal(problemsCount, '1', `Problem count is different to expected.`);
|
||||
});
|
||||
});
|
||||
}
|
||||
21
test/smoke/tsconfig.json
Normal file
21
test/smoke/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"noImplicitAny": false,
|
||||
"removeComments": false,
|
||||
"preserveConstEnums": true,
|
||||
"target": "es2016",
|
||||
"strictNullChecks": true,
|
||||
"noUnusedParameters": false,
|
||||
"noUnusedLocals": true,
|
||||
"outDir": "out",
|
||||
"sourceMap": true,
|
||||
"lib": [
|
||||
"es2016",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user