1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2025-12-07 02:28:54 +08:00

move tests to tests folder

rename util modules
rename DbUpload to DbUploader
add tests for DbUploader component #27
This commit is contained in:
lana-k
2021-04-21 11:05:56 +02:00
parent edd45b317a
commit 803622f18f
44 changed files with 333 additions and 157 deletions

View File

@@ -19,7 +19,7 @@ module.exports = {
{ {
files: [ files: [
'**/__tests__/*.{j,t}s?(x)', '**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)' '**/tests/**/*.spec.{j,t}s?(x)'
], ],
env: { env: {
mocha: true mocha: true

View File

@@ -27,4 +27,4 @@ jobs:
run: npm run lint run: npm run lint
- name: Run karma tests - name: Run karma tests
run: npm run test:unit run: npm run test

View File

@@ -7,7 +7,7 @@ Vue.use(VModal)
Vue.config.productionTip = false Vue.config.productionTip = false
// require all test files (files that ends with .spec.js) // require all test files (files that ends with .spec.js)
const testsContext = require.context('./tests/unit', true, /\.spec.js$/) const testsContext = require.context('./tests', true, /\.spec.js$/)
// Read more about why we need to call testContext: // Read more about why we need to call testContext:
// https://www.npmjs.com/package/require-context#context-api // https://www.npmjs.com/package/require-context#context-api

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "NODE_OPTIONS=--max_old_space_size=4096 vue-cli-service build", "build": "NODE_OPTIONS=--max_old_space_size=4096 vue-cli-service build",
"test:unit": "vue-cli-service karma", "test": "vue-cli-service karma",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {

View File

@@ -20,7 +20,7 @@
<script> <script>
export default { export default {
name: 'checkBox', name: 'CheckBox',
props: { props: {
theme: { theme: {
type: String, type: String,

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="db-upload-container"> <div class="db-uploader-container">
<change-db-icon v-if="type === 'small'" @click.native="browse"/> <change-db-icon v-if="type === 'small'" @click.native="browse"/>
<div v-if="['regular', 'illustrated'].includes(type)" class="drop-area-container"> <div v-if="['regular', 'illustrated'].includes(type)" class="drop-area-container">
<div <div
@@ -62,6 +62,7 @@
width="93px" width="93px"
:disabled="disableDialog" :disabled="disableDialog"
class="char-input" class="char-input"
id="quote-char"
/> />
<text-field <text-field
label="Escape char" label="Escape char"
@@ -71,6 +72,7 @@
width="93px" width="93px"
:disabled="disableDialog" :disabled="disableDialog"
class="char-input" class="char-input"
id="escape-char"
/> />
</div> </div>
<check-box <check-box
@@ -122,7 +124,7 @@
</template> </template>
<script> <script>
import fu from '@/fileUtils' import fu from '@/file.utils'
import csv from '@/csv' import csv from '@/csv'
import CloseIcon from '@/components/svg/close' import CloseIcon from '@/components/svg/close'
import TextField from '@/components/TextField' import TextField from '@/components/TextField'
@@ -135,7 +137,7 @@ import time from '@/time'
import database from '@/database' import database from '@/database'
export default { export default {
name: 'DbUpload', name: 'DbUploader',
props: { props: {
type: { type: {
type: String, type: String,
@@ -164,7 +166,7 @@ export default {
delimiter: '', delimiter: '',
quoteChar: '"', quoteChar: '"',
escapeChar: '"', escapeChar: '"',
header: false, header: true,
previewData: null, previewData: null,
importCsvMessages: [], importCsvMessages: [],
disableDialog: false, disableDialog: false,
@@ -237,9 +239,9 @@ export default {
this.delimiter = parseResult.delimiter this.delimiter = parseResult.delimiter
// In parseResult.messages we can get parse errors // In parseResult.messages we can get parse errors
this.importCsvMessages = parseResult.messages this.importCsvMessages = parseResult.messages || []
if (parseResult.messages.length === 0) { if (!parseResult.hasErrors) {
this.importCsvMessages.push({ this.importCsvMessages.push({
message: `Preview parsing is completed in ${time.getPeriod(start, end)}.`, message: `Preview parsing is completed in ${time.getPeriod(start, end)}.`,
type: 'success' type: 'success'
@@ -390,7 +392,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.db-upload-container { .db-uploader-container {
position: relative; position: relative;
} }
.drop-area-container { .drop-area-container {

View File

@@ -35,7 +35,11 @@ export default {
serializeMessage (msg) { serializeMessage (msg) {
let result = '' let result = ''
if (msg.row !== null && msg.row !== undefined) { if (msg.row !== null && msg.row !== undefined) {
if (msg.type === 'error') {
result += `Error in row ${msg.row}. ` result += `Error in row ${msg.row}. `
} else {
result += `Information about row ${msg.row}. `
}
} }
result += msg.message result += msg.message

View File

@@ -8,7 +8,7 @@
<tree-chevron :expanded="schemaVisible"/> <tree-chevron :expanded="schemaVisible"/>
{{ dbName }} {{ dbName }}
</div> </div>
<db-upload id="db-edit" type="small" /> <db-uploader id="db-edit" type="small" />
</div> </div>
<div v-show="schemaVisible" class="schema"> <div v-show="schemaVisible" class="schema">
<table-description <table-description
@@ -25,7 +25,7 @@
import TableDescription from '@/components/TableDescription' import TableDescription from '@/components/TableDescription'
import TextField from '@/components/TextField' import TextField from '@/components/TextField'
import TreeChevron from '@/components/svg/treeChevron' import TreeChevron from '@/components/svg/treeChevron'
import dbUpload from '@/components/DbUpload' import DbUploader from '@/components/DbUploader'
export default { export default {
name: 'Schema', name: 'Schema',
@@ -33,7 +33,7 @@ export default {
TableDescription, TableDescription,
TextField, TextField,
TreeChevron, TreeChevron,
dbUpload DbUploader
}, },
data () { data () {
return { return {

View File

@@ -1,5 +1,5 @@
import sqliteParser from 'sqlite-parser' import sqliteParser from 'sqlite-parser'
import fu from '@/fileUtils' import fu from '@/file.utils'
// We can import workers like so because of worker-loader: // We can import workers like so because of worker-loader:
// https://webpack.js.org/loaders/worker-loader/ // https://webpack.js.org/loaders/worker-loader/
import Worker from '@/db.worker.js' import Worker from '@/db.worker.js'

View File

@@ -1,5 +1,5 @@
import initSqlJs from 'sql.js/dist/sql-wasm.js' import initSqlJs from 'sql.js/dist/sql-wasm.js'
import dbUtils from '@/dbUtils' import dbUtils from '@/db.utils'
let SQL = null let SQL = null
const sqlModuleReady = initSqlJs().then(sqlModule => { SQL = sqlModule }) const sqlModuleReady = initSqlJs().then(sqlModule => { SQL = sqlModule })

View File

@@ -1,5 +1,5 @@
import { nanoid } from 'nanoid' import { nanoid } from 'nanoid'
import fu from '@/fileUtils' import fu from '@/file.utils'
export default { export default {
getStoredQueries () { getStoredQueries () {

View File

@@ -11,7 +11,7 @@
<div class="warning"> <div class="warning">
Database is not loaded. Queries cant be run without database. Database is not loaded. Queries cant be run without database.
</div> </div>
<db-upload id="db-uploader"/> <db-uploader id="db-uploader"/>
</div> </div>
</template> </template>
<template #right-pane> <template #right-pane>
@@ -25,7 +25,7 @@
import Splitpanes from '@/components/Splitpanes' import Splitpanes from '@/components/Splitpanes'
import Schema from '@/components/Schema' import Schema from '@/components/Schema'
import Tabs from '@/components/Tabs' import Tabs from '@/components/Tabs'
import dbUpload from '@/components/DbUpload' import DbUploader from '@/components/DbUploader'
export default { export default {
name: 'Editor', name: 'Editor',
@@ -33,7 +33,7 @@ export default {
Schema, Schema,
Splitpanes, Splitpanes,
Tabs, Tabs,
dbUpload DbUploader
} }
} }
</script> </script>
@@ -64,7 +64,7 @@ export default {
box-sizing: border-box; box-sizing: border-box;
} }
>>> .db-upload-container { >>> .db-uploader-container {
width: 100%; width: 100%;
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<div id="dbloader-container"> <div id="dbloader-container">
<db-upload type="illustrated" /> <db-uploader type="illustrated" />
<div id="note"> <div id="note">
Sqliteviz is fully client-side. Your database never leaves your computer. Sqliteviz is fully client-side. Your database never leaves your computer.
</div> </div>
@@ -11,11 +11,11 @@
</template> </template>
<script> <script>
import dbUpload from '@/components/DbUpload' import DbUploader from '@/components/DbUploader'
export default { export default {
name: 'Home', name: 'Home',
components: { dbUpload } components: { DbUploader }
} }
</script> </script>

View File

@@ -147,7 +147,7 @@ import TextField from '@/components/TextField'
import CheckBox from '@/components/CheckBox' import CheckBox from '@/components/CheckBox'
import tooltipMixin from '@/mixins/tooltips' import tooltipMixin from '@/mixins/tooltips'
import storedQueries from '@/storedQueries' import storedQueries from '@/storedQueries'
import fu from '@/fileUtils' import fu from '@/file.utils'
export default { export default {
name: 'MyQueries', name: 'MyQueries',

View File

@@ -0,0 +1,293 @@
import { expect } from 'chai'
import sinon from 'sinon'
import Vuex from 'vuex'
import { shallowMount, mount } from '@vue/test-utils'
import DbUploader from '@/components/DbUploader.vue'
import fu from '@/file.utils'
import database from '@/database.js'
import csv from '@/csv'
let state = {}
let mutations = {}
let store = {}
describe('DbUploader.vue', () => {
beforeEach(() => {
// mock store state and mutations
state = {}
mutations = {
saveSchema: sinon.stub()
}
store = new Vuex.Store({ state, mutations })
})
afterEach(() => {
sinon.restore()
})
it('loads db on click and redirects to /editor', async () => {
// mock getting a file from user
const file = {}
sinon.stub(fu, 'getFileFromUser').resolves(file)
// mock db loading
const schema = {}
const db = {
loadDb: sinon.stub().resolves(schema)
}
sinon.stub(database, 'getNewDatabase').returns(db)
// mock router
const $router = { push: sinon.stub() }
const $route = { path: '/' }
// mount the component
const wrapper = shallowMount(DbUploader, {
store,
mocks: { $router, $route }
})
await wrapper.find('.drop-area').trigger('click')
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
await db.loadDb.returnValues[0]
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
expect($router.push.calledOnceWith('/editor')).to.equal(true)
})
it('loads db on drop and redirects to /editor', async () => {
// mock db loading
const schema = {}
const db = {
loadDb: sinon.stub().resolves(schema)
}
sinon.stub(database, 'getNewDatabase').returns(db)
// mock router
const $router = { push: sinon.stub() }
const $route = { path: '/' }
// mount the component
const wrapper = shallowMount(DbUploader, {
store,
mocks: { $router, $route }
})
// mock a file dropped by a user
const file = {}
const dropData = { dataTransfer: new DataTransfer() }
Object.defineProperty(dropData.dataTransfer, 'files', {
value: [file],
writable: false
})
await wrapper.find('.drop-area').trigger('drop', dropData)
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
await db.loadDb.returnValues[0]
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
expect($router.push.calledOnceWith('/editor')).to.equal(true)
})
it("doesn't redirect if already on /editor", async () => {
// mock getting a file from user
const file = {}
sinon.stub(fu, 'getFileFromUser').resolves(file)
// mock db loading
const schema = {}
const db = {
loadDb: sinon.stub().resolves(schema)
}
sinon.stub(database, 'getNewDatabase').returns(db)
// mock router
const $router = { push: sinon.stub() }
const $route = { path: '/editor' }
// mount the component
const wrapper = shallowMount(DbUploader, {
store,
mocks: { $router, $route }
})
await wrapper.find('.drop-area').trigger('click')
await db.loadDb.returnValues[0]
expect($router.push.called).to.equal(false)
})
it('shows parse dialog if gets csv file', async () => {
// mock getting a file from user
const file = { type: 'text/csv' }
sinon.stub(fu, 'getFileFromUser').resolves(file)
// mock router
const $router = { push: sinon.stub() }
const $route = { path: '/editor' }
sinon.stub(csv, 'parse').resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo'],
[2, 'bar']
]
},
messages: [{
code: 'UndetectableDelimiter',
message: 'Comma was used as a standart delimiter',
row: 0,
type: 'info',
hint: undefined
}]
})
// mount the component
const wrapper = mount(DbUploader, {
store,
mocks: { $router, $route }
})
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
expect(wrapper.find('[data-modal="parse"]').exists()).to.equal(true)
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.value).to.equal('|')
expect(wrapper.find('#quote-char input').element.value).to.equal('"')
expect(wrapper.find('#escape-char input').element.value).to.equal('"')
expect(wrapper.findComponent({ name: 'check-box' }).vm.checked).to.equal(true)
const rows = wrapper.findAll('tbody tr')
expect(rows).to.have.lengthOf(2)
expect(rows.at(0).findAll('td').at(0).text()).to.equal('1')
expect(rows.at(0).findAll('td').at(1).text()).to.equal('foo')
expect(rows.at(1).findAll('td').at(0).text()).to.equal('2')
expect(rows.at(1).findAll('td').at(1).text()).to.equal('bar')
expect(wrapper.findComponent({ name: 'logs' }).text())
.to.include('Information about row 0. Comma was used as a standart delimiter.')
expect(wrapper.findComponent({ name: 'logs' }).text())
.to.include('Preview parsing is completed in')
})
it('reparses when parameters changes', async () => {
// mock getting a file from user
const file = { type: 'text/csv' }
sinon.stub(fu, 'getFileFromUser').resolves(file)
// mock router
const $router = { push: sinon.stub() }
const $route = { path: '/editor' }
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
}
})
// mount the component
const wrapper = mount(DbUploader, {
store,
mocks: { $router, $route }
})
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
parse.onCall(1).resolves({
delimiter: ',',
data: {
columns: ['col1', 'col2'],
values: [
[2, 'bar']
]
},
hasErrors: false
})
await wrapper.find('.delimiter-selector-container input').setValue(',')
expect(parse.callCount).to.equal(2)
await csv.parse.returnValues[1]
let rows = wrapper.findAll('tbody tr')
expect(rows).to.have.lengthOf(1)
expect(rows.at(0).findAll('td').at(0).text()).to.equal('2')
expect(rows.at(0).findAll('td').at(1).text()).to.equal('bar')
expect(wrapper.findComponent({ name: 'logs' }).text())
.to.include('Preview parsing is completed in')
parse.onCall(2).resolves({
delimiter: ',',
data: {
columns: ['col1', 'col2'],
values: [
[3, 'baz']
]
},
hasErrors: true,
messages: [{
code: 'MissingQuotes',
message: 'Quote is missed',
row: 0,
type: 'error',
hint: 'Edit your CSV so that the field has a closing quote char.'
}]
})
await wrapper.find('#quote-char input').setValue("'")
expect(parse.callCount).to.equal(3)
await csv.parse.returnValues[2]
rows = wrapper.findAll('tbody tr')
expect(rows).to.have.lengthOf(1)
expect(rows.at(0).findAll('td').at(0).text()).to.equal('3')
expect(rows.at(0).findAll('td').at(1).text()).to.equal('baz')
expect(wrapper.findComponent({ name: 'logs' }).text())
.to.contain('Error in row 0. Quote is missed. Edit your CSV so that the field has a closing quote char.')
expect(wrapper.findComponent({ name: 'logs' }).text())
.to.not.contain('Preview parsing is completed in')
parse.onCall(3).resolves({
delimiter: ',',
data: {
columns: ['col1', 'col2'],
values: [
[4, 'qux']
]
},
hasErrors: false
})
await wrapper.find('#escape-char input').setValue("'")
expect(parse.callCount).to.equal(4)
await csv.parse.returnValues[3]
rows = wrapper.findAll('tbody tr')
expect(rows).to.have.lengthOf(1)
expect(rows.at(0).findAll('td').at(0).text()).to.equal('4')
expect(rows.at(0).findAll('td').at(1).text()).to.equal('qux')
expect(wrapper.findComponent({ name: 'logs' }).text())
.to.contain('Preview parsing is completed in')
parse.onCall(4).resolves({
delimiter: ',',
data: {
columns: ['col1', 'col2'],
values: [
[5, 'corge']
]
},
hasErrors: false
})
await wrapper.findComponent({ name: 'check-box' }).trigger('click')
expect(parse.callCount).to.equal(5)
await csv.parse.returnValues[4]
rows = wrapper.findAll('tbody tr')
expect(rows).to.have.lengthOf(1)
expect(rows.at(0).findAll('td').at(0).text()).to.equal('5')
expect(rows.at(0).findAll('td').at(1).text()).to.equal('corge')
expect(wrapper.findComponent({ name: 'logs' }).text())
.to.include('Preview parsing is completed in')
})
})

View File

@@ -1,7 +1,7 @@
import { expect } from 'chai' import { expect } from 'chai'
import dbUtils from '@/dbUtils' import dbUtils from '@/db.utils'
describe('dbUtils.js', () => { describe('db.utils.js', () => {
it('generateChunks', () => { it('generateChunks', () => {
const arr = ['1', '2', '3', '4', '5'] const arr = ['1', '2', '3', '4', '5']
const size = 2 const size = 2

View File

@@ -1,8 +1,8 @@
import { expect } from 'chai' import { expect } from 'chai'
import fu from '@/fileUtils.js' import fu from '@/file.utils'
import sinon from 'sinon' import sinon from 'sinon'
describe('fileUtils.js', () => { describe('file.utils.js', () => {
afterEach(() => { afterEach(() => {
sinon.restore() sinon.restore()
}) })

View File

@@ -1,7 +1,7 @@
import { expect } from 'chai' import { expect } from 'chai'
import sinon from 'sinon' import sinon from 'sinon'
import storedQueries from '@/storedQueries.js' import storedQueries from '@/storedQueries.js'
import fu from '@/fileUtils' import fu from '@/file.utils'
describe('storedQueries.js', () => { describe('storedQueries.js', () => {
beforeEach(() => { beforeEach(() => {

View File

@@ -1,123 +0,0 @@
import { expect } from 'chai'
import sinon from 'sinon'
import Vuex from 'vuex'
import { shallowMount } from '@vue/test-utils'
import DbUpload from '@/components/DbUpload.vue'
import fu from '@/fileUtils'
import database from '@/database.js'
describe('DbUploader.vue', () => {
afterEach(() => {
sinon.restore()
})
it('loads db on click and redirects to /editor', async () => {
// mock store state and mutations
const state = {}
const mutations = {
saveSchema: sinon.stub()
}
const store = new Vuex.Store({ state, mutations })
// mock getting a file from user
const file = {}
sinon.stub(fu, 'getFileFromUser').resolves(file)
// mock db loading
const schema = {}
const db = {
loadDb: sinon.stub().resolves(schema)
}
sinon.stub(database, 'getNewDatabase').returns(db)
// mock router
const $router = { push: sinon.stub() }
const $route = { path: '/' }
// mount the component
const wrapper = shallowMount(DbUpload, {
store,
mocks: { $router, $route }
})
await wrapper.find('.drop-area').trigger('click')
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
await db.loadDb.returnValues[0]
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
expect($router.push.calledOnceWith('/editor')).to.equal(true)
})
it('loads db on drop and redirects to /editor', async () => {
// mock store state and mutations
const state = {}
const mutations = {
saveSchema: sinon.stub()
}
const store = new Vuex.Store({ state, mutations })
// mock db loading
const schema = {}
const db = {
loadDb: sinon.stub().resolves(schema)
}
sinon.stub(database, 'getNewDatabase').returns(db)
// mock router
const $router = { push: sinon.stub() }
const $route = { path: '/' }
// mount the component
const wrapper = shallowMount(DbUpload, {
store,
mocks: { $router, $route }
})
// mock a file dropped by a user
const file = {}
const dropData = { dataTransfer: new DataTransfer() }
Object.defineProperty(dropData.dataTransfer, 'files', {
value: [file],
writable: false
})
await wrapper.find('.drop-area').trigger('drop', dropData)
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
await db.loadDb.returnValues[0]
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
expect($router.push.calledOnceWith('/editor')).to.equal(true)
})
it("doesn't redirect if already on /editor", async () => {
// mock store state and mutations
const state = {}
const mutations = {
saveSchema: sinon.stub()
}
const store = new Vuex.Store({ state, mutations })
// mock getting a file from user
const file = {}
sinon.stub(fu, 'getFileFromUser').resolves(file)
// mock db loading
const schema = {}
const db = {
loadDb: sinon.stub().resolves(schema)
}
sinon.stub(database, 'getNewDatabase').returns(db)
// mock router
const $router = { push: sinon.stub() }
const $route = { path: '/editor' }
// mount the component
const wrapper = shallowMount(DbUpload, {
store,
mocks: { $router, $route }
})
await wrapper.find('.drop-area').trigger('click')
await db.loadDb.returnValues[0]
expect($router.push.called).to.equal(false)
})
})

View File

@@ -3,9 +3,9 @@ import sinon from 'sinon'
import { mount, shallowMount } from '@vue/test-utils' import { mount, shallowMount } from '@vue/test-utils'
import Vuex from 'vuex' import Vuex from 'vuex'
import MyQueries from '@/views/MyQueries.vue' import MyQueries from '@/views/MyQueries.vue'
import storedQueries from '@/storedQueries.js' import storedQueries from '@/storedQueries'
import { mutations } from '@/store' import { mutations } from '@/store'
import fu from '@/fileUtils.js' import fu from '@/file.utils'
describe('MyQueries.vue', () => { describe('MyQueries.vue', () => {
afterEach(() => { afterEach(() => {