提交 fa46ce74 编写于 作者: P Piotr Bryk

Merge pull request #98 from bryk/more-eslint-rules

Add more ESLint rules for JavaScript linting
......@@ -17,25 +17,59 @@
"modules": true,
},
"rules": {
// Require parens in arrow function arguments.
"arrow-parens": [2, "always"],
// Require dangling comma at the end of any multiline list/object. This is to be consistent
// with backend code.
"comma-dangle": [2, "always-multiline"],
// Enforce newline consistency in member expressions. Dots are places on the same line as
// the property.
"dot-location": [2, "property"],
// Enforce newline at the end of file, with no multiple empty lines.
"eol-last": 2,
// Force using === and !=== except when comparing to null. This is to prevent from bugs that
// come from automatic type conversion in == operator and to be consistent across the codebase.
"eqeqeq": [2, "allow-null"],
// Require a capital letter for constructors.
"new-cap": 2,
// Disallow the omission of parentheses when invoking a constructor with no arguments.
"new-parens": 2,
// Disallow modifying constant variables.
"no-const-assign": 2,
// Disallow this keywords outside of classes or class-like objects.
"no-invalid-this": 2,
// Allow at most one newline between code blocks.
"no-multiple-empty-lines": [2, {"max": 1}],
// Disallow the use of ternary operators when a simpler alternative exists.
"no-unneeded-ternary": 2,
// Avoid code that looks like two expressions but is actually one.
"no-unexpected-multiline": 2,
// Disallow trailing spaces. This is to unify code because editors may have different
// settings.
"no-trailing-spaces": 2,
// Disallow modifying constant variables.
"no-const-assign": 2,
// Force using 'let' or 'const' instead of 'var'. This is to prevent from var hoisting bugs.
"no-var": 2,
// Require one variable declaration statement.
"one-var": [2, "never"],
// Prefer using template literals instead of strings concatenation.
"prefer-template": 2,
// Require use of the second argument for parseInt().
"radix": 2,
// Require semicolons in every place they are valid. This is to prevent from automatic
// semicolon insertion bugs.
"semi": [2, "always"],
// Disallow setting strict mode in files. All JS code in the project uses ES6 modules so is
// implicitly strict.
"strict": [2, "never"],
// Require valid JSDoc.
"valid-jsdoc": [2, {
"prefer": {
"returns": "return"
},
"requireReturnDescription": false,
"requireReturn": false,
"requireParamDescription": false
}],
// No spacing in object literals nor in imports/exports.
"object-curly-spacing": [2, "never"],
},
......
......@@ -22,7 +22,6 @@ import path from 'path';
import conf from './conf';
import goCommand from './gocommand';
/**
* Compiles backend application in development mode and places the binary in the serve
* directory.
......@@ -40,7 +39,6 @@ gulp.task('backend', function(doneFn) {
doneFn);
});
/**
* Compiles backend application in production mode and places the binary in the dist
* directory.
......
......@@ -29,13 +29,11 @@ import path from 'path';
import conf from './conf';
/**
* Builds production package and places it in the dist directory.
*/
gulp.task('build', ['backend:prod', 'build-frontend']);
/**
* Builds production version of the frontend application.
*
......@@ -80,7 +78,6 @@ gulp.task('build-frontend', ['assets', 'index:prod'], function() {
.pipe(gulp.dest(conf.paths.distPublic));
});
/**
* Copies assets to the dist directory.
*/
......@@ -89,7 +86,6 @@ gulp.task('assets', function() {
.pipe(gulp.dest(conf.paths.distPublic));
});
/**
* Cleans all build artifacts.
*/
......
......@@ -22,7 +22,6 @@ import path from 'path';
import conf from './conf';
/**
* Checks whether codebase is in a state that is ready for submission. This means that code
* follows the style guide, it is buildable and all tests pass.
......@@ -31,14 +30,12 @@ import conf from './conf';
**/
gulp.task('check', ['lint', 'build', 'test', 'integration-test:prod']);
/**
* Lints all projects code files.
* // TODO(bryk): Also lint Go files here.
*/
gulp.task('lint', ['lint-javascript', 'check-javascript-format']);
/**
* Lints all projects JavaScript files using ESLint. This includes frontend source code, as well as,
* build scripts.
......@@ -53,7 +50,6 @@ gulp.task('lint-javascript', function() {
.pipe(gulpEslint.failOnError());
});
/**
* Checks whether project's JavaScript files are formatted according to clang-format style.
*/
......@@ -62,7 +58,6 @@ gulp.task('check-javascript-format', function() {
.pipe(gulpClangFormat.checkFormat('file', undefined, {verbose: true, fail: true}));
});
/**
* Formats all project's JavaScript files using clang-format.
*/
......
......@@ -30,12 +30,11 @@ import source from 'vinyl-source-stream';
import conf from './conf';
const kubernetesArchive = 'kubernetes.tar.gz',
kubernetesUrl = 'https://github.com/kubernetes/kubernetes.git', stableVersion = 'v1.1.1',
tarballUrl = 'https://storage.googleapis.com/kubernetes-release/release',
upScript = `${conf.paths.kubernetes}/hack/local-up-cluster.sh`;
const kubernetesArchive = 'kubernetes.tar.gz';
const kubernetesUrl = 'https://github.com/kubernetes/kubernetes.git';
const stableVersion = 'v1.1.1';
const tarballUrl = 'https://storage.googleapis.com/kubernetes-release/release';
const upScript = `${conf.paths.kubernetes}/hack/local-up-cluster.sh`;
/**
* Currently running cluster process object. Null if the cluster is not running.
......@@ -43,8 +42,6 @@ const kubernetesArchive = 'kubernetes.tar.gz',
* @type {?child.ChildProcess}
*/
let clusterProcess = null;
let exec = childProcess.exec;
/**
* A Number, representing the ID value of the timer that is set for function which periodically
......@@ -54,14 +51,13 @@ let exec = childProcess.exec;
*/
let isRunningSetIntervalHandler = null;
/**
* Checks if cluster health check return correct status.
* When custer is up and running then return 'ok'.
* @param {function(?Error=)} doneFn
*/
function clusterHealthCheck(doneFn) {
exec(`curl http://127.0.0.1:8080/healthz/ping`, function(err, stdout) {
childProcess.exec(`curl http://127.0.0.1:8080/healthz/ping`, function(err, stdout) {
if (err) {
return doneFn(new Error(err));
}
......@@ -69,20 +65,18 @@ function clusterHealthCheck(doneFn) {
});
}
/**
* Executes controls command using kubectl.
* @param {string} command
* @param {function(?Error=)} doneFn
*/
function executeKubectlCommand(command, doneFn) {
exec(`${conf.paths.kubernetes}/cluster/kubectl.sh ${command}`, function(err) {
childProcess.exec(`${conf.paths.kubernetes}/cluster/kubectl.sh ${command}`, function(err) {
if (err) return doneFn(new Error(err));
doneFn();
});
}
/**
* Creates cluster from scratch.
* Downloads latest version of kubernetes from git repository.
......@@ -96,7 +90,6 @@ function executeKubectlCommand(command, doneFn) {
*/
gulp.task('local-up-cluster', ['spawn-cluster']);
/**
* Tears down a Kubernetes cluster.
*/
......@@ -112,7 +105,6 @@ gulp.task('kill-cluster', function(doneFn) {
}
});
/**
* Clones kubernetes from git repository. Task skip if kubernetes directory exist.
*/
......@@ -129,7 +121,6 @@ gulp.task('clone-kubernetes', function(doneFn) {
});
});
/**
* Checkouts kubernetes to latest stable version.
*/
......@@ -140,7 +131,6 @@ gulp.task('checkout-kubernetes-version', ['clone-kubernetes'], function(doneFn)
});
});
/**
* Checks if kubectl is already downloaded.
* If not downloads kubectl for all platforms from tarball.
......@@ -163,13 +153,11 @@ gulp.task('download-kubectl', function(doneFn) {
});
});
/**
* Removes kubernetes before git clone command.
*/
gulp.task('clear-kubernetes', function() { return del(conf.paths.kubernetes); });
/**
* Spawns local-up-cluster.sh script.
*/
......@@ -188,7 +176,6 @@ gulp.task(
clusterProcess.on('exit', function() { clusterProcess = null; });
});
/**
* Checks periodically if cluster is up and running.
*/
......@@ -209,7 +196,6 @@ gulp.task('wait-for-cluster', function(doneFn) {
}
});
/**
* Sets a cluster entry in kubeconfig.
* Configures kubernetes server for localhost.
......@@ -223,7 +209,6 @@ gulp.task(
doneFn);
});
/**
* Sets a context entry in kubeconfig as local.
*/
......@@ -233,7 +218,6 @@ gulp.task(
executeKubectlCommand('config set-context local --cluster=local', doneFn);
});
/**
* Sets the current-context in a kubeconfig file
*/
......
......@@ -17,13 +17,11 @@
*/
import path from 'path';
/**
* Base path for all other paths.
*/
const basePath = path.join(__dirname, '../');
/**
* Exported configuration object with common constants used in build pipeline.
*/
......
......@@ -21,7 +21,6 @@ import path from 'path';
import conf from './conf';
/**
* @param {!Array<string>} args
* @param {function(?Error=)} doneFn
......@@ -35,12 +34,11 @@ function spawnDockerProcess(args, doneFn) {
if (code === 0) {
doneFn();
} else {
doneFn(new Error('Docker command error, code:' + code));
doneFn(new Error(`Docker command error, code: ${code}`));
}
});
}
/**
* Creates Docker image for the application. The image is tagged with the image name configuration
* constant.
......@@ -60,7 +58,6 @@ gulp.task('docker-image', ['build', 'docker-file'], function(doneFn) {
doneFn);
});
/**
* Processes the Docker file and places it in the dist folder for building.
*/
......
......@@ -20,7 +20,6 @@ import lodash from 'lodash';
import conf from './conf';
/**
* Spawns Go process wrapped with the Godep command.
*
......@@ -43,7 +42,7 @@ export default function spawnGoProcess(args, doneFn) {
if (code === 0) {
doneFn();
} else {
doneFn(new Error('Go command error, code:' + code));
doneFn(new Error(`Go command error, code: ${code}`));
}
});
}
......@@ -23,11 +23,11 @@ import wiredep from 'wiredep';
import conf from './conf';
/**
* Creates index file in the given directory with dependencies injected from that directory.
*
* @param {string} indexPath
* @return {!stream.Stream}
*/
function createIndexFile(indexPath) {
let injectStyles = gulp.src(path.join(indexPath, '**/*.css'), {read: false});
......@@ -54,13 +54,11 @@ function createIndexFile(indexPath) {
.pipe(browserSync.stream());
}
/**
* Creates frontend application index file with development dependencies injected.
*/
gulp.task('index', ['scripts', 'styles'], function() { return createIndexFile(conf.paths.serve); });
/**
* Creates frontend application index file with production dependencies injected.
*/
......
......@@ -23,7 +23,6 @@ import wiredep from 'wiredep';
import conf from './conf';
/**
* Returns an array of files required by Karma to run the tests.
*
......@@ -43,7 +42,6 @@ function getFileList() {
]);
}
/**
* Exported default function which sets Karma configuration. Required by the framework.
*
......@@ -96,7 +94,7 @@ export default function(config) {
// karma-ng-html2js-preprocessor plugin config.
ngHtml2JsPreprocessor: {
stripPrefix: conf.paths.frontendSrc + '/',
stripPrefix: `${conf.paths.frontendSrc}/`,
moduleName: conf.frontend.moduleName,
},
};
......
......@@ -17,12 +17,12 @@
*
* TODO(bryk): Start using ES6 in this file when supported.
*/
/* eslint no-var: 0 */ // no-var check disabled because this file is not written in ES6.
/* eslint no-var: 0 */ // disabled because this file is not written in ES6.
/* eslint prefer-template: 0 */ // disabled because this file is not written in ES6.
require('babel-core/register');
var conf = require('./conf');
var path = require('path');
/**
* Exported protractor config required by the framework.
*
......
......@@ -24,7 +24,6 @@ import webpackStream from 'webpack-stream';
import conf from './conf';
/**
* Compiles frontend JavaScript files into development bundle located in {conf.paths.serve}
* directory. This has to be done because currently browsers do not handle ES6 syntax and
......@@ -53,7 +52,6 @@ gulp.task('scripts', function() {
.pipe(gulp.dest(conf.paths.serve));
});
/**
* Compiles frontend JavaScript files into production bundle located in {conf.paths.prodTmp}
* directory.
......@@ -115,7 +113,6 @@ gulp.task('scripts:prod', ['angular-templates'], function() {
.pipe(gulp.dest(conf.paths.prodTmp));
});
/**
* Compiles Angular HTML template files into one JS file that serves them through $templateCache.
*/
......
......@@ -25,13 +25,11 @@ import proxyMiddleware from 'proxy-middleware';
import conf from './conf';
/**
* Browser sync instance that serves the application.
*/
export const browserSyncInstance = browserSync.create();
/**
* Currently running backend process object. Null if the backend is not running.
*
......@@ -39,7 +37,6 @@ export const browserSyncInstance = browserSync.create();
*/
let runningBackendProcess = null;
/**
* Initializes BrowserSync tool. Files are served from baseDir directory list and all API calls
* are proxied to a running backend instance. When includeBowerComponents is true, requests for
......@@ -80,7 +77,6 @@ function browserSyncInit(baseDir, includeBowerComponents) {
browserSyncInstance.init(config);
}
/**
* Serves the application in development mode.
*/
......@@ -94,26 +90,22 @@ function serveDevelopmentMode() {
true);
}
/**
* Serves the application in development mode. Watches for changes in the source files to rebuild
* development artifacts.
*/
gulp.task('serve', ['spawn-backend', 'watch'], serveDevelopmentMode);
/**
* Serves the application in development mode.
*/
gulp.task('serve:nowatch', ['spawn-backend', 'index'], serveDevelopmentMode);
/**
* Serves the application in production mode.
*/
gulp.task('serve:prod', ['spawn-backend:prod']);
/**
* Spawns new backend application process and finishes the task immediately. Previously spawned
* backend process is killed beforehand, if any. The frontend pages are served by BrowserSync.
......@@ -130,7 +122,6 @@ gulp.task('spawn-backend', ['backend', 'kill-backend'], function() {
});
});
/**
* Spawns new backend application process and finishes the task immediately. Previously spawned
* backend process is killed beforehand, if any. In production the backend does serve the frontend
......@@ -150,7 +141,6 @@ gulp.task(
});
});
/**
* Kills running backend process (if any).
*/
......@@ -168,7 +158,6 @@ gulp.task('kill-backend', function(doneFn) {
}
});
/**
* Watches for changes in source files and runs Gulp tasks to rebuild them.
*/
......
......@@ -26,7 +26,6 @@ import gulpConcat from 'gulp-concat';
import {browserSyncInstance} from './serve';
import conf from './conf';
/**
* Compiles stylesheets and places them into the serve folder. Each stylesheet file is compiled
* separately.
......@@ -45,7 +44,6 @@ gulp.task('styles', function() {
.pipe(browserSyncInstance.stream());
});
/**
* Compiles stylesheets and places them into the prod tmp folder. Styles are compiled and minified
* into a single file.
......
......@@ -24,7 +24,6 @@ import {browserSyncInstance} from './serve';
import conf from './conf';
import goCommand from './gocommand';
/**
* @param {boolean} singleRun
* @param {function(?Error=)} doneFn
......@@ -37,12 +36,11 @@ function runUnitTests(singleRun, doneFn) {
};
let server = new karma.Server(localConfig, function(failCount) {
doneFn(failCount ? new Error("Failed " + failCount + " tests.") : undefined);
doneFn(failCount ? new Error(`Failed ${failCount} tests.`) : undefined);
});
server.start();
}
/**
* @param {function(?Error=)} doneFn
*/
......@@ -55,7 +53,6 @@ function runBackendTests(doneFn) {
doneFn);
}
/**
* @param {function(?Error=)} doneFn
*/
......@@ -81,39 +78,33 @@ function runProtractorTests(doneFn) {
});
}
/**
* Runs once all unit tests of the application.
*/
gulp.task('test', ['frontend-test', 'backend-test']);
/**
* Runs once all unit tests of the frontend application.
*/
gulp.task('frontend-test', function(doneFn) { runUnitTests(true, doneFn); });
/**
* Runs once all unit tests of the backend application.
*/
gulp.task('backend-test', runBackendTests);
/**
* Runs all unit tests of the application. Watches for changes in the source files to rerun
* the tests.
*/
gulp.task('test:watch', ['frontend-test:watch', 'backend-test:watch']);
/**
* Runs frontend backend application tests. Watches for changes in the source files to rerun
* the tests.
*/
gulp.task('frontend-test:watch', function(doneFn) { runUnitTests(false, doneFn); });
/**
* Runs backend application tests. Watches for changes in the source files to rerun
* the tests.
......@@ -127,13 +118,11 @@ gulp.task('backend-test:watch', ['backend-test'], function() {
['backend-test']);
});
/**
* Runs application integration tests. Uses development version of the application.
*/
gulp.task('integration-test', ['serve:nowatch', 'webdriver-update'], runProtractorTests);
/**
* Runs application integration tests. Uses production version of the application.
*/
......@@ -141,13 +130,11 @@ gulp.task(
'integration-test:prod', ['local-up-cluster', 'serve:prod', 'webdriver-update'],
runProtractorTests);
/**
* Downloads and updates webdriver. Required to keep it up to date.
*/
gulp.task('webdriver-update', gulpProtractor.webdriver_update);
/**
* Kills backend server and cluster, if running.
*/
......
......@@ -4,5 +4,7 @@
"no-undef": 0,
// Disable unused variable checking. Extern functions always have unused variables.
"no-unused-vars": 0,
// Disable JSDoc validation. Extern functions do not have valid JSDoc.
valid-jsdoc: 0,
}
}
......@@ -19,7 +19,6 @@
* @externs
*/
/**
* @typedef {{
* $promise: !angular.$q.Promise
......@@ -27,41 +26,35 @@
*/
angular.ResourceClass;
/**
* @typedef {function(string, !Object=):!angular.Resource}
*/
angular.$resource;
/**
* @constructor
* @template T
*/
angular.Resource = function() {};
/**
* @param {!T} data
* @param {function(!T)=} opt_callback
* @param {function(!angular.$http.Response)=} opt_errback
* @returns {!angular.ResourceClass}
* @return {!angular.ResourceClass}
*/
angular.Resource.prototype.save = function(data, opt_callback, opt_errback) {};
/**
* @param {function(!T)=} opt_callback
* @param {function(!angular.$http.Response)=} opt_errback
* @returns {!angular.ResourceClass}
* @return {!angular.ResourceClass}
*/
angular.Resource.prototype.get = function(opt_callback, opt_errback) {};
/**
* @param {function(!T)=} opt_callback
* @param {function(!angular.$http.Response)=} opt_errback
* @returns {!angular.ResourceClass}
* @return {!angular.ResourceClass}
*/
angular.Resource.prototype.query = function(opt_callback, opt_errback) {};
......@@ -22,10 +22,8 @@
* @externs
*/
const backendApi = {};
/**
* @typedef {{
* port: (number|null),
......@@ -35,7 +33,6 @@ const backendApi = {};
*/
backendApi.PortMapping;
/**
* @typedef {{
* containerImage: string,
......@@ -48,7 +45,6 @@ backendApi.PortMapping;
*/
backendApi.AppDeployment;
/**
* @typedef {{
* replicaSets: !Array<!backendApi.ReplicaSet>
......@@ -56,7 +52,6 @@ backendApi.AppDeployment;
*/
backendApi.ReplicaSetList;
/**
* @typedef {{
* name: string,
......@@ -73,7 +68,6 @@ backendApi.ReplicaSetList;
*/
backendApi.ReplicaSet;
/**
* @typedef {{
* name: string,
......@@ -88,7 +82,6 @@ backendApi.ReplicaSet;
*/
backendApi.ReplicaSetDetail;
/**
* @typedef {{
* name: string,
......@@ -99,7 +92,6 @@ backendApi.ReplicaSetDetail;
*/
backendApi.ReplicaSetPod;
/**
* @typedef {{
* namespaces: !Array<string>
......
......@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Controller for the chrome directive.
*
......
......@@ -14,7 +14,6 @@
import ChromeController from './chrome_controller';
/**
* Returns directive definition object for the chrome directive.
*
......
......@@ -14,7 +14,6 @@
import chromeDirective from './chrome_directive';
/**
* Angular module containing navigation chrome for the application.
*/
......
......@@ -15,7 +15,6 @@
import {stateName as zerostate} from 'zerostate/zerostate_state';
import {stateName as replicasetliststate} from 'replicasetlist/replicasetlist_state';
/**
* Controller for the deploy view.
*
......
......@@ -14,7 +14,6 @@
import stateConfig from './deploy_state';
/**
* Angular module for the deploy view.
*
......
......@@ -14,11 +14,9 @@
import DeployController from './deploy_controller';
/** Name of the state. Can be used in, e.g., $state.go method. */
export const stateName = 'deploy';
/**
* Configures states for the deploy view.
*
......@@ -40,6 +38,7 @@ export default function stateConfig($stateProvider) {
* Resolves namespaces for the deploy view.
*
* @param {!angular.$resource} $resource
* @return {!angular.$q.Promise}
* @ngInject
*/
function resolveNamespaces($resource) {
......
......@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @param {!md.$mdThemingProvider} $mdThemingProvider
* @ngInject
......
......@@ -24,7 +24,6 @@ import replicaSetDetailModule from './replicasetdetail/replicasetdetail_module';
import replicaSetListModule from './replicasetlist/replicasetlist_module';
import zerostateModule from './zerostate/zerostate_module';
export default angular.module(
'kubernetesDashboard',
[
......
......@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Global route configuration for the application.
*
......
......@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Controller for the replica set details view.
*
......@@ -20,11 +19,12 @@
*/
export default class ReplicaSetDetailController {
/**
* @param {!backendApi.ReplicaSetDetail} replicaSetDetail
* @ngInject
*/
constructor(replicaSetDetail) {
/**
* @export {backendApi.ReplicaSetDetail}
* @export {!backendApi.ReplicaSetDetail}
*/
this.replicaSetDetail = replicaSetDetail;
}
......
......@@ -14,7 +14,6 @@
import stateConfig from './replicasetdetail_state';
/**
* Angular module for the Replica Set details view.
*
......
......@@ -14,11 +14,9 @@
import ReplicaSetDetailController from './replicasetdetail_controller';
/** Name of the state. Can be used in, e.g., $state.go method. */
export const stateName = 'replicasetdetail';
/**
* Parameters for this state.
*
......@@ -39,7 +37,6 @@ export class StateParams {
}
}
/**
* Configures states for the service view.
*
......@@ -58,10 +55,10 @@ export default function stateConfig($stateProvider) {
});
}
/**
* @param {!StateParams} $stateParams
* @param {!angular.$resource} $resource
* @return {!angular.$q.Promise}
* @ngInject
*/
function resolveDetails($stateParams, $resource) {
......
......@@ -15,7 +15,6 @@
import {StateParams} from 'replicasetdetail/replicasetdetail_state';
import {stateName} from 'replicasetdetail/replicasetdetail_state';
/**
* Controller for the replica set list view.
*
......
......@@ -14,7 +14,6 @@
import stateConfig from './replicasetlist_state';
/**
* Angular module for the Replica Set list view.
*
......
......@@ -14,11 +14,9 @@
import ReplicaSetListController from './replicasetlist_controller';
/** Name of the state. Can be used in, e.g., $state.go method. */
export const stateName = 'replicasets';
/**
* Configures states for the service view.
*
......
......@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Controller for the zero state view.
*
......
......@@ -14,7 +14,6 @@
import stateConfig from './zerostate_state';
/**
* Angular module for the zero state view.
*
......
......@@ -14,11 +14,9 @@
import ZeroStateController from './zerostate_controller';
/** Name of the state. Can be used in, e.g., $state.go method. */
export const stateName = 'zero';
/**
* Configures states for the zero state view.
*
......
......@@ -14,7 +14,6 @@
import ZerostateController from 'zerostate/zerostate_controller';
describe('Main controller', () => {
let vm;
......
......@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
export default class ZeroStatePageObject {
constructor() { this.deployButton = element(by.css('.kd-zerostate-deploy-bt')); }
}
......@@ -14,7 +14,6 @@
import PageObject from './zerostate_po';
describe('Zero state view', function() {
let page;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册