diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..8625258 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: Run tests +on: + workflow_dispatch: + push: + branches: + - 'master' + +jobs: + test: + name: Run karma tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: 10.x + - name: Install chromium + run: + sudo DEBIAN_FRONTEND=noninteractive apt-get update && + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y chromium-browser + + - name: Install the project + run: npm install + + - name: Run lint + run: npm run lint + + - name: Run tests + run: npm run test:unit diff --git a/karma.conf.js b/karma.conf.js index 054c371..7cd4a15 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,31 +1,33 @@ // Karma configuration -"use strict"; -const path = require("path"); -const VueLoaderPlugin = require("vue-loader/lib/plugin"); +'use strict' +const path = require('path') +const VueLoaderPlugin = require('vue-loader/lib/plugin') -function resolve(dir) { - return path.join(__dirname, dir); +function resolve (dir) { + return path.join(__dirname, dir) } -module.exports = function(config) { +module.exports = function (config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: "", + basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ["mocha", "sinon-chai"], + frameworks: ['mocha', 'sinon-chai'], // list of files / patterns to load in the browser files: [ - "./karma.files.js", - { pattern: 'node_modules/sql.js/dist/sql-wasm.wasm', + './karma.files.js', + { + pattern: 'node_modules/sql.js/dist/sql-wasm.wasm', watched: false, included: false, served: true, nocache: false }, - { pattern: 'node_modules/sql.js/dist/worker.sql-wasm.js', + { + pattern: 'node_modules/sql.js/dist/worker.sql-wasm.js', watched: false, included: false, served: true, @@ -39,29 +41,29 @@ module.exports = function(config) { // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - "./karma.files.js": ["webpack"] + './karma.files.js': ['webpack'] }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ["spec", "coverage"], + reporters: ['spec', 'coverage'], coverageReporter: { - dir: "coverage", - reporters: [{ type: "lcov", subdir: "." }, { type: "text-summary" }] + dir: 'coverage', + reporters: [{ type: 'lcov', subdir: '.' }, { type: 'text-summary' }] }, // !!DONOT delete this reporter, or vue-cli-addon-ui-karma doesnot work jsonResultReporter: { - outputFile: "report/karma-result.json", + outputFile: 'report/karma-result.json', isSynchronous: true }, junitReporter: { - outputDir: "report", // results will be saved as $outputDir/$browserName.xml + outputDir: 'report', // results will be saved as $outputDir/$browserName.xml outputFile: undefined, // if included, results will be saved as $outputDir/$browserName/$outputFile - suite: "", // suite will become the package name attribute in xml testsuite element + suite: '', // suite will become the package name attribute in xml testsuite element useBrowserName: true, // add browser name to report and classes names nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element @@ -83,7 +85,7 @@ module.exports = function(config) { // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ["ChromiumHeadless"], + browsers: ['ChromiumHeadless'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits @@ -98,16 +100,16 @@ module.exports = function(config) { }, browserConsoleLogOptions: { terminal: true, - level: "" + level: '' }, webpack: { - mode: "development", - entry: "./src/main.js", + mode: 'development', + entry: './src/main.js', resolve: { - extensions: [".js", ".vue", ".json"], + extensions: ['.js', '.vue', '.json'], alias: { - vue$: "vue/dist/vue.esm.js", - "@": resolve("src") + vue$: 'vue/dist/vue.esm.js', + '@': resolve('src') } }, module: { @@ -117,7 +119,7 @@ module.exports = function(config) { exclude: /(node_modules|bower_components)/, use: [ { - loader: "babel-loader" + loader: 'babel-loader' } ] }, @@ -127,7 +129,7 @@ module.exports = function(config) { exclude: /(node_modules|bower_components|\.spec\.js$|\/file)/, use: [ { - loader: "istanbul-instrumenter-loader", + loader: 'istanbul-instrumenter-loader', options: { esModules: true } @@ -136,34 +138,34 @@ module.exports = function(config) { }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, - loader: "url-loader" + loader: 'url-loader' }, { test: /\.vue$/, - loader: "vue-loader", + loader: 'vue-loader', options: { loaders: { - js: "babel-loader" + js: 'babel-loader' }, postLoaders: { - js: "istanbul-instrumenter-loader?esModules=true" + js: 'istanbul-instrumenter-loader?esModules=true' } } }, { test: /\.css$/, - use: ["vue-style-loader", "css-loader"] + use: ['vue-style-loader', 'css-loader'] }, { test: /\.scss$/, - use: ["vue-style-loader", "css-loader", "sass-loader"] + use: ['vue-style-loader', 'css-loader', 'sass-loader'] }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, - loader: "url-loader", + loader: 'url-loader', options: { limit: 10000, - name: resolve("fonts/[name].[hash:7].[ext]") + name: resolve('fonts/[name].[hash:7].[ext]') } } ] @@ -174,7 +176,7 @@ module.exports = function(config) { } }, proxies: { - "/js/": "/base/node_modules/sql.js/dist/" + '/js/': '/base/node_modules/sql.js/dist/' } - }); -}; + }) +} diff --git a/karma.files.js b/karma.files.js index 7115137..7b7f82d 100644 --- a/karma.files.js +++ b/karma.files.js @@ -1,16 +1,16 @@ -import Vue from "vue"; +import Vue from 'vue' -Vue.config.productionTip = false; +Vue.config.productionTip = false // require all test files (files that ends with .spec.js) -const testsContext = require.context("./tests/unit", true, /\.spec.js$/); +const testsContext = require.context('./tests/unit', true, /\.spec.js$/) // Read more about why we need to call testContext: // https://www.npmjs.com/package/require-context#context-api -testsContext.keys().forEach(testsContext); +testsContext.keys().forEach(testsContext) // require all src files except main.js for coverage. // you can also change this to match only the subset of files that // you want coverage for. -const srcContext = require.context("./src", true, /^\.\/(?!main(\.js)?$)/); -srcContext.keys().forEach(srcContext); \ No newline at end of file +const srcContext = require.context('./src', true, /^\.\/(?!main(\.js)?$)/) +srcContext.keys().forEach(srcContext) diff --git a/package-lock.json b/package-lock.json index e3860c4..e6a3a93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sqliteviz", - "version": "0.6.0", + "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "sqliteviz", - "version": "0.6.0", + "version": "1.0.0", "license": "Apache-2.0", "dependencies": { "codemirror": "^5.57.0", @@ -34,7 +34,7 @@ "@vue/cli-plugin-vuex": "^4.4.0", "@vue/cli-service": "^4.4.0", "@vue/eslint-config-standard": "^5.1.2", - "@vue/test-utils": "^1.0.3", + "@vue/test-utils": "^1.1.2", "babel-eslint": "^10.1.0", "chai": "^4.1.2", "eslint": "^6.7.2", @@ -2166,14 +2166,18 @@ } }, "node_modules/@vue/test-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.0.3.tgz", - "integrity": "sha512-mmsKXZSGfvd0bH05l4SNuczZ2MqlJH2DWhiul5wJXFxbf/gRRd2UL4QZgozEMQ30mRi9i4/+p4JJat8S4Js64Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.1.2.tgz", + "integrity": "sha512-utbIL7zn9c+SjhybPwh48lpWCiluFCbP1yyRNAy1fQsw/6hiNFioaWy05FoVAFIZXC5WwBf+5r4ypfM1j/nI4A==", "dev": true, "dependencies": { "dom-event-types": "^1.0.0", "lodash": "^4.17.15", "pretty": "^2.0.0" + }, + "peerDependencies": { + "vue": "2.x", + "vue-template-compiler": "^2.x" } }, "node_modules/@vue/web-component-wrapper": { @@ -24288,9 +24292,9 @@ "dev": true }, "@vue/test-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.0.3.tgz", - "integrity": "sha512-mmsKXZSGfvd0bH05l4SNuczZ2MqlJH2DWhiul5wJXFxbf/gRRd2UL4QZgozEMQ30mRi9i4/+p4JJat8S4Js64Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.1.2.tgz", + "integrity": "sha512-utbIL7zn9c+SjhybPwh48lpWCiluFCbP1yyRNAy1fQsw/6hiNFioaWy05FoVAFIZXC5WwBf+5r4ypfM1j/nI4A==", "dev": true, "requires": { "dom-event-types": "^1.0.0", diff --git a/package.json b/package.json index b011047..77dc061 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "serve": "vue-cli-service serve", "build": "NODE_OPTIONS=--max_old_space_size=4096 vue-cli-service build", - "test:unit": "vue-cli-service test:unit", + "test:unit": "vue-cli-service karma", "lint": "vue-cli-service lint" }, "dependencies": { @@ -35,7 +35,7 @@ "@vue/cli-plugin-vuex": "^4.4.0", "@vue/cli-service": "^4.4.0", "@vue/eslint-config-standard": "^5.1.2", - "@vue/test-utils": "^1.0.3", + "@vue/test-utils": "^1.1.2", "babel-eslint": "^10.1.0", "chai": "^4.1.2", "eslint": "^6.7.2", diff --git a/tests/unit/checkBox.spec.js b/tests/unit/checkBox.spec.js index ab1d098..94e1884 100644 --- a/tests/unit/checkBox.spec.js +++ b/tests/unit/checkBox.spec.js @@ -36,4 +36,4 @@ describe('CheckBox', () => { expect(wrapper.emitted().click).to.have.lengthOf(2) expect(wrapper.emitted().click[1]).to.eql([false]) }) -}) \ No newline at end of file +}) diff --git a/tests/unit/database.spec.js b/tests/unit/database.spec.js index 1c8c225..040cfba 100644 --- a/tests/unit/database.spec.js +++ b/tests/unit/database.spec.js @@ -2,15 +2,15 @@ import { expect } from 'chai' import initSqlJs from 'sql.js' import db from '@/database.js' const config = { - locateFile: filename => `js/sql-wasm.wasm` + locateFile: filename => 'js/sql-wasm.wasm' } describe('database.js', () => { it('creates schema', () => { return initSqlJs(config) - .then(SQL => { - const database = new SQL.Database() - database.run(` + .then(SQL => { + const database = new SQL.Database() + database.run(` CREATE TABLE test ( col1, col2 integer, @@ -19,53 +19,53 @@ describe('database.js', () => { ) `) - const data = database.export() - const buffer = new Blob([data]) - return db.loadDb(buffer) - }) - .then(({dbName, schema}) => { - expect(schema).to.have.lengthOf(1) - expect(schema[0].name).to.equal('test') - expect(schema[0].columns[0].name).to.equal('col1') - expect(schema[0].columns[0].type).to.equal('N/A') - expect(schema[0].columns[1].name).to.equal('col2') - expect(schema[0].columns[1].type).to.equal('integer') - expect(schema[0].columns[2].name).to.equal('col3') - expect(schema[0].columns[2].type).to.equal('decimal(5, 2)') - expect(schema[0].columns[3].name).to.equal('col4') - expect(schema[0].columns[3].type).to.equal('varchar(30)') - }) + const data = database.export() + const buffer = new Blob([data]) + return db.loadDb(buffer) + }) + .then(({ dbName, schema }) => { + expect(schema).to.have.lengthOf(1) + expect(schema[0].name).to.equal('test') + expect(schema[0].columns[0].name).to.equal('col1') + expect(schema[0].columns[0].type).to.equal('N/A') + expect(schema[0].columns[1].name).to.equal('col2') + expect(schema[0].columns[1].type).to.equal('integer') + expect(schema[0].columns[2].name).to.equal('col3') + expect(schema[0].columns[2].type).to.equal('decimal(5, 2)') + expect(schema[0].columns[3].name).to.equal('col4') + expect(schema[0].columns[3].type).to.equal('varchar(30)') + }) }) it('creates schema with virtual table', () => { return initSqlJs(config) - .then(SQL => { - const database = new SQL.Database() - database.run(` + .then(SQL => { + const database = new SQL.Database() + database.run(` CREATE VIRTUAL TABLE test_virtual USING fts4( col1, col2, notindexed=col1, notindexed=col2, tokenize=unicode61 "tokenchars=.+#") `) - const data = database.export() - const buffer = new Blob([data]) - return db.loadDb(buffer) - }) - .then(({dbName, schema}) => { - expect(schema[0].name).to.equal('test_virtual') - expect(schema[0].columns[0].name).to.equal('col1') - expect(schema[0].columns[0].type).to.equal('N/A') - expect(schema[0].columns[1].name).to.equal('col2') - expect(schema[0].columns[1].type).to.equal('N/A') - }) + const data = database.export() + const buffer = new Blob([data]) + return db.loadDb(buffer) + }) + .then(({ dbName, schema }) => { + expect(schema[0].name).to.equal('test_virtual') + expect(schema[0].columns[0].name).to.equal('col1') + expect(schema[0].columns[0].type).to.equal('N/A') + expect(schema[0].columns[1].name).to.equal('col2') + expect(schema[0].columns[1].type).to.equal('N/A') + }) }) it('returns a query result', () => { return initSqlJs(config) - .then(SQL => { - const database = new SQL.Database() - database.run(` + .then(SQL => { + const database = new SQL.Database() + database.run(` CREATE TABLE test ( id integer, name varchar(100), @@ -77,33 +77,33 @@ describe('database.js', () => { ( 2, 'Draco Malfoy', 'Slytherin'); `) - const data = database.export() - const buffer = new Blob([data]) - return db.loadDb(buffer) - }) - .then(({dbName, schema}) => { - return db.execute('SELECT * from test') - }) - .then(result => { - expect(result.columns).to.have.lengthOf(3) - expect(result.columns[0]).to.equal('id') - expect(result.columns[1]).to.equal('name') - expect(result.columns[2]).to.equal('faculty') - expect(result.values).to.have.lengthOf(2) - expect(result.values[0][0]).to.equal(1) - expect(result.values[0][1]).to.equal('Harry Potter') - expect(result.values[0][2]).to.equal('Griffindor') - expect(result.values[1][0]).to.equal(2) - expect(result.values[1][1]).to.equal('Draco Malfoy') - expect(result.values[1][2]).to.equal('Slytherin') - }) + const data = database.export() + const buffer = new Blob([data]) + return db.loadDb(buffer) + }) + .then(({ dbName, schema }) => { + return db.execute('SELECT * from test') + }) + .then(result => { + expect(result.columns).to.have.lengthOf(3) + expect(result.columns[0]).to.equal('id') + expect(result.columns[1]).to.equal('name') + expect(result.columns[2]).to.equal('faculty') + expect(result.values).to.have.lengthOf(2) + expect(result.values[0][0]).to.equal(1) + expect(result.values[0][1]).to.equal('Harry Potter') + expect(result.values[0][2]).to.equal('Griffindor') + expect(result.values[1][0]).to.equal(2) + expect(result.values[1][1]).to.equal('Draco Malfoy') + expect(result.values[1][2]).to.equal('Slytherin') + }) }) it('returns an error', () => { return initSqlJs(config) - .then(SQL => { - const database = new SQL.Database() - database.run(` + .then(SQL => { + const database = new SQL.Database() + database.run(` CREATE TABLE test ( id integer, name varchar(100), @@ -115,16 +115,15 @@ describe('database.js', () => { ( 2, 'Draco Malfoy', 'Slytherin'); `) - const data = database.export() - const buffer = new Blob([data]) - return db.loadDb(buffer) - }) - .then(() => { - return db.execute('SELECT * from foo') - }) - .catch(result => { - console.log(result) - expect(result).to.equal('no such table: foo') - }) + const data = database.export() + const buffer = new Blob([data]) + return db.loadDb(buffer) + }) + .then(() => { + return db.execute('SELECT * from foo') + }) + .catch(result => { + expect(result).to.equal('no such table: foo') + }) }) -}) \ No newline at end of file +}) diff --git a/tests/unit/store.spec.js b/tests/unit/store.spec.js index 067cb4b..c3e9fef 100644 --- a/tests/unit/store.spec.js +++ b/tests/unit/store.spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai' import { mutations, actions } from '@/store' -const { +const { saveSchema, updateTab, deleteTab, @@ -15,11 +15,14 @@ describe('mutations', () => { it('saveSchema', () => { // mock state const state = {} - + const schema = [ - { name: 'table1', columns: [ - { name: 'id', type: 'INTEGER' } - ]} + { + name: 'table1', + columns: [ + { name: 'id', type: 'INTEGER' } + ] + } ] saveSchema(state, { dbName: 'test', @@ -323,7 +326,7 @@ describe('mutations', () => { it('setCurrentTab', () => { // mock state const state = { - currentTab: { id: 1} + currentTab: { id: 1 } } setCurrentTab(state, { id: 2 }) @@ -372,7 +375,6 @@ describe('mutations', () => { }) }) - describe('actions', () => { it('addTab (new)', async () => { // mock state @@ -429,12 +431,12 @@ describe('actions', () => { // mock state const state = { - tabs: [ tab1, tab2 ], - untitledLastIndex: 0, + tabs: [tab1, tab2], + untitledLastIndex: 0 } await addTab({ state }, tab1) expect(state.tabs).to.have.lengthOf(2) expect(state.untitledLastIndex).to.equal(0) }) -}) \ No newline at end of file +}) diff --git a/tests/unit/storedQueries.spec.js b/tests/unit/storedQueries.spec.js index 03b87ae..a0bd20f 100644 --- a/tests/unit/storedQueries.spec.js +++ b/tests/unit/storedQueries.spec.js @@ -2,35 +2,35 @@ import { expect } from 'chai' import storedQueries from '@/storedQueries.js' describe('storedQueries.js', () => { - beforeEach(()=> { + beforeEach(() => { localStorage.removeItem('myQueries') }) it('getStoredQueries(empty storage)', () => { - const queries = storedQueries.getStoredQueries() + const queries = storedQueries.getStoredQueries() expect(queries).to.eql([]) - }) + }) it('getStoredQueries', () => { const data = [ { id: 1 }, - { id: 2 }, + { id: 2 } ] storedQueries.updateStorage(data) - const queries = storedQueries.getStoredQueries() + const queries = storedQueries.getStoredQueries() expect(queries).to.eql(data) - }) + }) it('duplicateQuery', () => { const now = new Date() - const nowPlusMinute = new Date(now.getTime() + 60*1000) + const nowPlusMinute = new Date(now.getTime() + 60 * 1000) const base = { id: 1, name: 'foo', query: 'SELECT * from foo', chart: [], createdAt: new Date(2021, 0, 1), - isPredefined: true + isPredefined: true } const copy = storedQueries.duplicateQuery(base) @@ -43,14 +43,14 @@ describe('storedQueries.js', () => { }) it('isTabNeedName returns false when the query has a name and is not predefined', () => { - let tab = { + const tab = { initName: 'foo' } expect(storedQueries.isTabNeedName(tab)).to.be.false }) it('isTabNeedName returns true when the query has no name and is not predefined', () => { - let tab = { + const tab = { initName: null, tempName: 'Untitled' } @@ -58,10 +58,10 @@ describe('storedQueries.js', () => { }) it('isTabNeedName returns true when the qiery is predefined', () => { - let tab = { + const tab = { initName: 'foo', isPredefined: true } expect(storedQueries.isTabNeedName(tab)).to.be.true }) -}) \ No newline at end of file +}) diff --git a/vue.config.js b/vue.config.js index 18e8343..0ac79d5 100644 --- a/vue.config.js +++ b/vue.config.js @@ -18,9 +18,9 @@ module.exports = { svgRule.uses.clear() svgRule .use('url-loader') - .loader('url-loader') - .options({ - limit: 10000 - }) + .loader('url-loader') + .options({ + limit: 10000 + }) } }