diff --git a/.gitignore b/.gitignore index 770ac709..e5143c11 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ dir.txt release/ build/ +coverage/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..249cc3ed --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: node_js +dist: trusty +addons: + chrome: stable +node_js: + - node +#addons: +# sauce_connect: true +sudo: false +cache: + directories: + - node_modules +script: + - npm run test:cov + #- npm run test:sauce +after_script: + - npm install coveralls && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage \ No newline at end of file diff --git a/test/admin.html b/examples/admin.html similarity index 100% rename from test/admin.html rename to examples/admin.html diff --git a/test/all.html b/examples/all.html similarity index 100% rename from test/all.html rename to examples/all.html diff --git a/test/button.html b/examples/button.html similarity index 100% rename from test/button.html rename to examples/button.html diff --git a/test/carousel.html b/examples/carousel.html similarity index 100% rename from test/carousel.html rename to examples/carousel.html diff --git a/test/code.html b/examples/code.html similarity index 100% rename from test/code.html rename to examples/code.html diff --git a/test/element.html b/examples/element.html similarity index 100% rename from test/element.html rename to examples/element.html diff --git a/test/extend.html b/examples/extend.html similarity index 100% rename from test/extend.html rename to examples/extend.html diff --git a/test/flow.html b/examples/flow.html similarity index 100% rename from test/flow.html rename to examples/flow.html diff --git a/test/form.html b/examples/form.html similarity index 100% rename from test/form.html rename to examples/form.html diff --git a/test/js/child/test.js b/examples/js/child/test.js similarity index 100% rename from test/js/child/test.js rename to examples/js/child/test.js diff --git a/test/js/index.js b/examples/js/index.js similarity index 100% rename from test/js/index.js rename to examples/js/index.js diff --git a/test/json/table/demo1.json b/examples/json/table/demo1.json similarity index 100% rename from test/json/table/demo1.json rename to examples/json/table/demo1.json diff --git a/test/json/table/demo2.json b/examples/json/table/demo2.json similarity index 100% rename from test/json/table/demo2.json rename to examples/json/table/demo2.json diff --git a/test/json/upload/demoLayEdit.json b/examples/json/upload/demoLayEdit.json similarity index 100% rename from test/json/upload/demoLayEdit.json rename to examples/json/upload/demoLayEdit.json diff --git a/test/laydate.html b/examples/laydate.html similarity index 100% rename from test/laydate.html rename to examples/laydate.html diff --git a/test/layedit.html b/examples/layedit.html similarity index 100% rename from test/layedit.html rename to examples/layedit.html diff --git a/test/layer.html b/examples/layer.html similarity index 100% rename from test/layer.html rename to examples/layer.html diff --git a/test/layout.html b/examples/layout.html similarity index 100% rename from test/layout.html rename to examples/layout.html diff --git a/test/laypage.html b/examples/laypage.html similarity index 100% rename from test/laypage.html rename to examples/laypage.html diff --git a/test/responsive.html b/examples/responsive.html similarity index 100% rename from test/responsive.html rename to examples/responsive.html diff --git a/test/table.html b/examples/table.html similarity index 100% rename from test/table.html rename to examples/table.html diff --git a/test/tree.html b/examples/tree.html similarity index 100% rename from test/tree.html rename to examples/tree.html diff --git a/test/upload.html b/examples/upload.html similarity index 100% rename from test/upload.html rename to examples/upload.html diff --git a/test/util.html b/examples/util.html similarity index 100% rename from test/util.html rename to examples/util.html diff --git a/test/xingzuo.html b/examples/xingzuo.html similarity index 100% rename from test/xingzuo.html rename to examples/xingzuo.html diff --git a/karma.conf.base.js b/karma.conf.base.js new file mode 100644 index 00000000..42e4d9aa --- /dev/null +++ b/karma.conf.base.js @@ -0,0 +1,125 @@ +/** + * @file karma自动化测试配置 + * @author fe.xiaowu@gmail.com + */ + +/** + * 源文件 + * + * @type {Array} + */ +var sourceFileMap = [ + 'src/layui.js', + 'src/lay/modules/jquery.js', + 'src/lay/modules/carousel.js', + 'src/lay/modules/code.js', + 'src/lay/modules/element.js', + 'src/lay/modules/flow.js', + 'src/lay/modules/form.js', + 'src/lay/modules/laydate.js', + 'src/lay/modules/layedit.js', + 'src/lay/modules/layer.js', + 'src/lay/modules/laypage.js', + 'src/lay/modules/laytpl.js', + 'src/lay/modules/table.js', + 'src/lay/modules/tree.js', + 'src/lay/modules/upload.js', + 'src/lay/modules/util.js', + 'src/lay/modules/mobile/zepto.js', + 'src/lay/modules/mobile/layer-mobile.js', + 'src/lay/modules/mobile/upload-mobile.js' +]; + +/** + * 测试覆盖率文件, 要忽略 jquery.js、zepto.js + * + * @type {Object} + */ +var coverageFileMap = {}; +sourceFileMap.filter(function (uri) { + return !/(jquery|zepto)\.js$/.test(uri); +}).forEach(function (uri) { + coverageFileMap[uri] = ['coverage']; +}); + +module.exports = function (config) { + return { + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + // Important: 所有插件必须在此声明 + plugins: ['karma-*'], + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + // Important: 下列数组中文件将『逆序载入』 + frameworks: ['mocha', 'chai', 'chai-sinon'], + + + // list of files / patterns to load in the browser + files: sourceFileMap.concat('test/**/*.js'), + + + // list of files to exclude + exclude: [], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: coverageFileMap, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: [ + 'mocha' + // 'coverage' + ], + + coverageReporter: { + // specify a common output directory + dir: '.', + reporters: [ + // { type: 'html', subdir: 'report-html' }, + { + type: 'lcov', + subdir: 'coverage' + }, + { + type: 'text-summary' + } + ] + }, + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + // Note: 如果要调试Karma,请设置为DEBUG + logLevel: config.LOG_INFO, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: [ + 'PhantomJS' + ], + + + // enable / disable watching file and executing tests whenever any file changes + // Note: 代码改动自动运行测试,需要singleRun为false + autoWatch: false, + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + // 脚本调用请设为 true + singleRun: true + }; +}; diff --git a/karma.conf.sauce.js b/karma.conf.sauce.js new file mode 100644 index 00000000..4cd95698 --- /dev/null +++ b/karma.conf.sauce.js @@ -0,0 +1,105 @@ +/** + * @file karma配置 + * @author fe.xiaowu@gmail.com + */ + +var base = require('./karma.conf.base.js'); + +var customLaunchers = { + // Safari + sl_ios_safari: { + base: 'SauceLabs', + browserName: 'Safari' + }, + + // 安卓浏览器 + sl_android_4_4: { + base: 'SauceLabs', + browserName: 'android', + version: '4.4' + }, + sl_android_5: { + base: 'SauceLabs', + browserName: 'android', + version: '5' + }, + sl_android_6: { + base: 'SauceLabs', + browserName: 'android', + version: '6' + }, + + // chrome + sl_ios_chrome: { + base: 'SauceLabs', + browserName: 'chrome' + }, + + sl_ie_8: { + base: 'SauceLabs', + browserName: 'internet explorer', + version: '8' + }, + sl_ie_9: { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 7', + version: '9' + }, + sl_ie_10: { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 8', + version: '10' + }, + sl_ie_11: { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 8.1', + version: '11' + }, + + sl_firefox: { + base: 'SauceLabs', + browserName: 'firefox', + platform: 'Windows 7' + } +}; + +// 不支持本地运行 +if (!process.env.TRAVIS) { + console.error('不支持本地运行, 请使用 npm run test!'); + process.exit(1); +} + +// 变量检查 +if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) { + console.error('---------------'); + console.error('Make sure the SAUCE_USERNAME and SAUCE_ACCESS_KEY environment variables are set.'); + console.error('---------------'); + process.exit(1); +} + +module.exports = function (config) { + var options = Object.assign(base(config), { + reporters: ['mocha', 'saucelabs'], + sauceLabs: { + 'testName': 'layui test case', + 'recordVideo': false, + 'recordScreenshots': false, + 'startConnect': false, + 'connectOptions': { + 'no-ssl-bump-domains': 'all' + }, + 'public': 'public', + 'build': process.env.CIRCLE_BUILD_NUM || process.env.SAUCE_BUILD_ID || 'build-' + Date.now(), + 'tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER + }, + customLaunchers: customLaunchers, + browsers: Object.keys(customLaunchers), + captureTimeout: 1000 * 60 * 5, + browserNoActivityTimeout: 1000 * 60 * 5 + }); + + config.set(options); +}; diff --git a/karma.conf.unit.js b/karma.conf.unit.js new file mode 100644 index 00000000..724a5576 --- /dev/null +++ b/karma.conf.unit.js @@ -0,0 +1,12 @@ +/** + * @file karma配置 + * @author fe.xiaowu@gmail.com + */ + +var base = require('./karma.conf.base.js'); + +module.exports = function (config) { + var options = Object.assign(base(config), {}); + + config.set(options); +}; diff --git a/package.json b/package.json index 9fd721ed..7ff7a732 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,10 @@ "main": "layui.js", "license": "MIT", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "karma start karma.conf.unit.js", + "test:cov": "npm test -- --reporters mocha,coverage", + "test:sauce": "karma start karma.conf.sauce.js", + "test:watch": "npm test -- --auto-watch --no-single-run" }, "repository": { "type": "git", @@ -24,7 +27,19 @@ "gulp-rename": "^1.2.2", "del": "^2.2.2", "gulp-zip": "^4.0.0", - "minimist": "^1.2.0" + "minimist": "^1.2.0", + "chai": "^3.5.0", + "karma": "^1.5.0", + "karma-chai": "^0.1.0", + "karma-chai-sinon": "^0.1.5", + "karma-coverage": "^1.1.1", + "karma-mocha": "^1.3.0", + "karma-sauce-launcher": "^1.1.0", + "karma-mocha-reporter": "^2.2.3", + "karma-phantomjs-launcher": "^1.0.4", + "mocha": "^3.2.0", + "sinon": "^2.0.0", + "sinon-chai": "^2.8.0" }, "bugs": { "url": "https://github.com/sentsin/layui/issues" @@ -34,16 +49,6 @@ "test": "test" }, "dependencies": { - "gulp": "^3.9.1", - "gulp-uglify": "^1.5.4", - "gulp-minify-css": "^1.2.4", - "gulp-concat": "^2.6.0", - "gulp-header": "^1.8.8", - "gulp-if": "^2.0.1", - "gulp-rename": "^1.2.2", - "del": "^2.2.2", - "gulp-zip": "^gulp-zip", - "minimist": "^1.2.0" }, "keywords": [ "layui", diff --git a/test/laytpl.js b/test/laytpl.js new file mode 100644 index 00000000..cb047a66 --- /dev/null +++ b/test/laytpl.js @@ -0,0 +1,227 @@ +/** + * @file laytpl - 测试 + * @author xuexb + */ + +/* global layui */ + +var laytpl = layui.laytpl; + +describe('laytpl', function () { + it('param is error', function () { + [ + [], {}, + null, + 1, + true + ].forEach(function (key) { + expect(laytpl(key)).to.have.string('Laytpl Error'); + }); + }); + + it('async render callback', function (done) { + expect(laytpl('').render()).to.have.string('Laytpl Error'); + + laytpl('{{ d.name }}是一位公猿').render({ + name: '贤心' + }, function (result) { + expect(result).to.equal('贤心是一位公猿'); + done(); + }); + }); + + it('sync result', function () { + var result = laytpl('{{ d.name }}是一位公猿').render({ + name: '贤心' + }); + expect(result).to.equal('贤心是一位公猿'); + }); + + it('cached', function () { + var compile = laytpl('{{ d.name }}'); + + expect(compile.render({ + name: 1 + })).to.equal('1'); + + expect(compile.render({ + name: 2 + })).to.equal('2'); + }); + + it('unescape result', function () { + var result = laytpl('{{ d.name }}
').render({ + name: 'laytpl' + }); + expect(result).to.equal('laytpl
'); + }); + + it('escape result', function () { + var result = laytpl('{{= d.name }}
').render({ + name: 'laytpl' + }); + expect(result).to.equal('<em>laytpl</em>
'); + }); + + describe('typeof result', function () { + it('string', function () { + expect(laytpl('{{ d.name }}').render({ + name: 1 + })).to.be.a('string'); + + expect(laytpl([ + '{{# ', + ' if (true) {', + ' return "1";', + ' }', + '}}' + ].join('')).render({})).to.be.a('string'); + }); + + // 表达式返回boolean + it('boolean', function () { + expect(laytpl([ + '{{# ', + ' if (true) {', + ' return true;', + ' }', + '}}' + ].join('')).render({})).to.be.a('boolean'); + }); + + it('number', function () { + expect(laytpl([ + '{{# ', + ' if (true) {', + ' return 1;', + ' }', + '}}' + ].join('')).render({})).to.be.a('number'); + }); + }); + + describe('method config', function () { + // reset + afterEach(function () { + laytpl.config({ + open: '{{', + close: '}}' + }); + }); + + it('typeof', function () { + expect(laytpl.config).to.be.a('function'); + }); + + it('param is empty', function () { + expect(laytpl.config()).to.be.undefined; + }); + + it('set open', function () { + laytpl.config({ + open: '<%' + }); + + var result = laytpl([ + '<%# var name = "laytpl"; }}', + '你好, <% name }}, <% d.date }}' + ].join('')).render({ + date: '2017' + }); + expect(result).to.equal('你好, laytpl, 2017'); + }); + + it('set open and close', function () { + laytpl.config({ + open: '<%', + close: '%>' + }); + + var result = laytpl([ + '<%# var name = "laytpl"; %>', + '你好, <% name %>, <% d.date %>' + ].join('')).render({ + date: '2017' + }); + expect(result).to.equal('你好, laytpl, 2017'); + }); + }); + + describe('js expression', function () { + it('var', function () { + var result = laytpl('{{# var type = 1; }}{{ type }}{{ d.name }}').render({ + name: 2 + }); + expect(result).to.equal('12'); + }); + + it('function', function () { + var result = laytpl('{{# var fn = function () {return "ok"}; }}{{ fn() }}').render({}); + expect(result).to.equal('ok'); + }); + + it('for', function () { + var result = laytpl([ + '{{# ', + ' var fn = function () {', + ' var num = 0;', + ' for (var i = 0; i < 10;i++) {', + ' num += 1;', + ' }', + ' return num', + ' };', + '}}', + '{{# ', + ' var name = "laytpl";', + '}}', + '你好, {{ name }}, {{ d.name }}, {{ fn() }}' + ].join('')).render({ + name: 'ok' + }); + expect(result).to.equal('你好, laytpl, ok, 10'); + }); + + it('if else', function () { + var result; + result = laytpl([ + '{{# ', + ' if (true) {', + ' return true;', + ' }', + ' else {', + ' return false', + ' }', + '}}' + ].join('')).render({}); + expect(result).to.be.true; + + result = laytpl([ + '{{# ', + ' if (!true) {', + ' return true;', + ' }', + ' else {', + ' return false', + ' }', + '}}' + ].join('')).render({}); + expect(result).to.be.false; + }); + }); + + describe('parse error', function () { + it('error var', function () { + var result = laytpl('{{ data.xxoo }}').render({}); + + expect(result).to.have.string('Can\'t find variable: data'); + expect(result).to.have.string('Laytpl Error'); + }); + + it('error expression', function () { + var result = laytpl('{{# var xxoo = ; }}').render({}); + + expect(result).to.have.string('Laytpl Error'); + expect(result).to.have.string('Unexpected token \';\''); + }); + }); +});