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

add tests for DbUploader #27

This commit is contained in:
lana-k
2021-04-22 14:04:52 +02:00
parent 803622f18f
commit 628e9cee62
4 changed files with 590 additions and 39 deletions

View File

@@ -44,7 +44,7 @@
<modal name="parse" classes="dialog" height="auto" width="60%" :clickToClose="false"> <modal name="parse" classes="dialog" height="auto" width="60%" :clickToClose="false">
<div class="dialog-header"> <div class="dialog-header">
Import CSV Import CSV
<close-icon @click="cancelCsvImport"/> <close-icon @click="cancelCsvImport" :disabled="disableDialog"/>
</div> </div>
<div class="dialog-body"> <div class="dialog-body">
<div class="chars"> <div class="chars">
@@ -99,6 +99,7 @@
class="secondary" class="secondary"
:disabled="disableDialog" :disabled="disableDialog"
@click="cancelCsvImport" @click="cancelCsvImport"
id="csv-cancel"
> >
Cancel Cancel
</button> </button>
@@ -107,6 +108,7 @@
class="primary" class="primary"
:disabled="disableDialog" :disabled="disableDialog"
@click="loadFromCsv(file)" @click="loadFromCsv(file)"
id="csv-import"
> >
Import Import
</button> </button>
@@ -115,6 +117,7 @@
class="primary" class="primary"
:disabled="disableDialog" :disabled="disableDialog"
@click="finish" @click="finish"
id="csv-finish"
> >
Finish Finish
</button> </button>
@@ -324,9 +327,6 @@ export default {
start = new Date() start = new Date()
this.schema = await this.newDb.createDb(file.name, parseResult.data, progressCounterId) this.schema = await this.newDb.createDb(file.name, parseResult.data, progressCounterId)
end = new Date() end = new Date()
if (this.schema.error) {
throw this.schema.error
}
// Inform about import success // Inform about import success
period = time.getPeriod(start, end) period = time.getPeriod(start, end)

View File

@@ -18,7 +18,7 @@
<script> <script>
export default { export default {
name: 'loadingIndicator', name: 'LoadingIndicator',
props: ['progress'], props: ['progress'],
computed: { computed: {
circleProgress () { circleProgress () {

View File

@@ -1,7 +1,7 @@
<template> <template>
<svg <svg
@click.stop="$emit('click')" @click.stop="$emit('click')"
class="icon" :class="['icon', {'disabled': disabled }]"
:width="size" :width="size"
:height="size" :height="size"
viewBox="0 0 14 14" viewBox="0 0 14 14"
@@ -23,6 +23,11 @@ export default {
type: Number, type: Number,
required: false, required: false,
default: 14 default: 14
},
disabled: {
type: Boolean,
required: false,
default: false
} }
} }
} }
@@ -32,6 +37,9 @@ export default {
.icon { .icon {
cursor: pointer; cursor: pointer;
} }
.disabled {
pointer-events: none;
}
.icon:hover path { .icon:hover path {
fill: var(--color-text-base); fill: var(--color-text-base);
} }

View File

@@ -4,14 +4,14 @@ import Vuex from 'vuex'
import { shallowMount, mount } from '@vue/test-utils' import { shallowMount, mount } from '@vue/test-utils'
import DbUploader from '@/components/DbUploader.vue' import DbUploader from '@/components/DbUploader.vue'
import fu from '@/file.utils' import fu from '@/file.utils'
import database from '@/database.js' import database from '@/database'
import csv from '@/csv' import csv from '@/csv'
describe('DbUploader.vue', () => {
let state = {} let state = {}
let mutations = {} let mutations = {}
let store = {} let store = {}
describe('DbUploader.vue', () => {
beforeEach(() => { beforeEach(() => {
// mock store state and mutations // mock store state and mutations
state = {} state = {}
@@ -113,16 +113,54 @@ describe('DbUploader.vue', () => {
await db.loadDb.returnValues[0] await db.loadDb.returnValues[0]
expect($router.push.called).to.equal(false) expect($router.push.called).to.equal(false)
}) })
})
it('shows parse dialog if gets csv file', async () => { describe('DbUploader.vue import CSV', () => {
// mock getting a file from user let state = {}
const file = { type: 'text/csv' } let mutations = {}
sinon.stub(fu, 'getFileFromUser').resolves(file) let actions = {}
const newTabId = 1
let store = {}
// mock router // mock router
const $router = { push: sinon.stub() } const $router = { }
const $route = { path: '/editor' } const $route = { path: '/' }
let clock
let wrapper
beforeEach(() => {
// mock getting a file from user
sinon.stub(fu, 'getFileFromUser').resolves({ type: 'text/csv' })
clock = sinon.useFakeTimers()
// mock store state and mutations
state = {}
mutations = {
saveSchema: sinon.stub(),
setDb: sinon.stub(),
setCurrentTabId: sinon.stub()
}
actions = {
addTab: sinon.stub().resolves(newTabId)
}
store = new Vuex.Store({ state, mutations, actions })
$router.push = sinon.stub()
// mount the component
wrapper = mount(DbUploader, {
store,
mocks: { $router, $route }
})
})
afterEach(() => {
sinon.restore()
})
it('shows parse dialog if gets csv file', async () => {
sinon.stub(csv, 'parse').resolves({ sinon.stub(csv, 'parse').resolves({
delimiter: '|', delimiter: '|',
data: { data: {
@@ -141,12 +179,6 @@ describe('DbUploader.vue', () => {
}] }]
}) })
// mount the component
const wrapper = mount(DbUploader, {
store,
mocks: { $router, $route }
})
await wrapper.find('.drop-area').trigger('click') await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0] await csv.parse.returnValues[0]
await wrapper.vm.animationPromise await wrapper.vm.animationPromise
@@ -166,17 +198,11 @@ describe('DbUploader.vue', () => {
.to.include('Information about row 0. Comma was used as a standart delimiter.') .to.include('Information about row 0. Comma was used as a standart delimiter.')
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text())
.to.include('Preview parsing is completed in') .to.include('Preview parsing is completed in')
expect(wrapper.find('#csv-finish').isVisible()).to.equal(false)
expect(wrapper.find('#csv-import').isVisible()).to.equal(true)
}) })
it('reparses when parameters changes', async () => { 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') const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({ parse.onCall(0).resolves({
delimiter: '|', delimiter: '|',
@@ -188,12 +214,6 @@ describe('DbUploader.vue', () => {
} }
}) })
// mount the component
const wrapper = mount(DbUploader, {
store,
mocks: { $router, $route }
})
await wrapper.find('.drop-area').trigger('click') await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0] await csv.parse.returnValues[0]
await wrapper.vm.animationPromise await wrapper.vm.animationPromise
@@ -290,4 +310,527 @@ describe('DbUploader.vue', () => {
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text())
.to.include('Preview parsing is completed in') .to.include('Preview parsing is completed in')
}) })
it('has proper state before parsing is complete', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
}
})
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
let resolveParsing
parse.onCall(1).returns(new Promise(resolve => {
resolveParsing = resolve
}))
await wrapper.find('#csv-import').trigger('click')
// "Parsing CSV..." in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg').at(1).text())
.to.equal('Parsing CSV...')
// After 1 second - loading indicator is shown
await clock.tick(1000)
expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
).to.equal(true)
// All the dialog controls are disabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true)
expect(wrapper.find('#csv-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#csv-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
expect(wrapper.find('#csv-finish').isVisible()).to.equal(false)
expect(wrapper.find('#csv-import').isVisible()).to.equal(true)
await resolveParsing()
await parse.returnValues[1]
// Loading indicator is not shown when parsing is compete
expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
).to.equal(false)
})
it('parsing is completed successfully', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
},
hasErrors: false,
messages: []
})
parse.onCall(1).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo'],
[2, 'bar']
]
},
hasErrors: false,
messages: []
})
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
await wrapper.vm.$nextTick()
// Parsing success in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg').at(1).text())
.to.include('2 rows are parsed successfully in')
// All the dialog controls are disabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true)
expect(wrapper.find('#csv-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#csv-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
expect(wrapper.find('#csv-finish').isVisible()).to.equal(false)
expect(wrapper.find('#csv-import').isVisible()).to.equal(true)
})
it('parsing is completed with notes', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
},
hasErrors: false,
messages: []
})
parse.onCall(1).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo'],
[2, 'bar']
]
},
hasErrors: false,
messages: [{
code: 'UndetectableDelimiter',
message: 'Comma was used as a standart delimiter',
type: 'info',
hint: undefined
}]
})
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
await wrapper.vm.$nextTick()
// Parsing success in the logs
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
expect(logs).to.have.lengthOf(4)
expect(logs.at(1).text()).to.include('2 rows are parsed in')
expect(logs.at(2).text()).to.equals('Comma was used as a standart delimiter.')
// All the dialog controls are disabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true)
expect(wrapper.find('#csv-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#csv-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
expect(wrapper.find('#csv-finish').isVisible()).to.equal(false)
expect(wrapper.find('#csv-import').isVisible()).to.equal(true)
})
it('parsing is completed with errors', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
},
hasErrors: false,
messages: []
})
parse.onCall(1).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo'],
[2, 'bar']
]
},
hasErrors: true,
messages: [{
code: 'Error',
message: 'Something is wrong',
type: 'error',
hint: undefined
}]
})
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
await wrapper.vm.$nextTick()
// Parsing success in the logs
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
expect(logs).to.have.lengthOf(3)
expect(logs.at(1).text()).to.include('Parsing ended with errors.')
expect(logs.at(2).text()).to.equals('Something is wrong.')
// All the dialog controls are enabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(false)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(false)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(false)
expect(wrapper.find('#csv-cancel').element.disabled).to.equal(false)
expect(wrapper.find('#csv-finish').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
expect(wrapper.find('#csv-finish').isVisible()).to.equal(false)
expect(wrapper.find('#csv-import').isVisible()).to.equal(true)
})
it('has proper state before import is completed', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
},
hasErrors: false,
messages: []
})
parse.onCall(1).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo'],
[2, 'bar']
]
},
hasErrors: false,
messages: []
})
let resolveImport = sinon.stub()
const newDb = {
createDb: sinon.stub().resolves(new Promise(resolve => { resolveImport = resolve })),
createProgressCounter: sinon.stub().returns(1)
}
sinon.stub(database, 'getNewDatabase').returns(newDb)
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
await wrapper.vm.$nextTick()
// Parsing success in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg').at(2).text())
.to.equal('Importing CSV into a SQLite database...')
// After 1 second - loading indicator is shown
await clock.tick(1000)
expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
).to.equal(true)
// All the dialog controls are disabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true)
expect(wrapper.find('#csv-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#csv-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
expect(wrapper.find('#csv-finish').isVisible()).to.equal(false)
expect(wrapper.find('#csv-import').isVisible()).to.equal(true)
// After resolving - loading indicator is not shown
await resolveImport()
await newDb.createDb.returnValues[0]
expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
).to.equal(false)
})
it('import success', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
},
hasErrors: false,
messages: []
})
parse.onCall(1).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo'],
[2, 'bar']
]
},
hasErrors: false,
messages: []
})
const schema = {}
const newDb = {
createDb: sinon.stub().resolves(schema),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub()
}
sinon.stub(database, 'getNewDatabase').returns(newDb)
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
await wrapper.vm.$nextTick()
// Import success in the logs
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
expect(logs).to.have.lengthOf(3)
expect(logs.at(2).text()).to.contain('Importing CSV into a SQLite database is completed in')
// All the dialog controls are enabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(false)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(false)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(false)
expect(wrapper.find('#csv-cancel').element.disabled).to.equal(false)
expect(wrapper.find('#csv-finish').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
expect(wrapper.find('#csv-finish').isVisible()).to.equal(true)
})
it('import fails', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
},
hasErrors: false,
messages: []
})
parse.onCall(1).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo'],
[2, 'bar']
]
},
hasErrors: false,
messages: []
})
const newDb = {
createDb: sinon.stub().rejects(new Error('fail')),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub()
}
sinon.stub(database, 'getNewDatabase').returns(newDb)
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
await wrapper.vm.$nextTick()
// Import success in the logs
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
expect(logs).to.have.lengthOf(4)
expect(logs.at(2).text()).to.contain('Importing CSV into a SQLite database...')
expect(logs.at(3).text()).to.equal('Error: fail.')
// All the dialog controls are enabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(false)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(false)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(false)
expect(wrapper.find('#csv-cancel').element.disabled).to.equal(false)
expect(wrapper.find('#csv-finish').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
expect(wrapper.find('#csv-finish').isVisible()).to.equal(false)
})
it('import final', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
},
hasErrors: false,
messages: []
})
parse.onCall(1).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo'],
[2, 'bar']
]
},
hasErrors: false,
messages: []
})
const schema = {}
const newDb = {
createDb: sinon.stub().resolves(schema),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub()
}
sinon.stub(database, 'getNewDatabase').returns(newDb)
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
await wrapper.vm.$nextTick()
await wrapper.find('#csv-finish').trigger('click')
expect(mutations.setDb.calledOnceWith(state, newDb)).to.equal(true)
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
expect(actions.addTab.calledOnce).to.equal(true)
await actions.addTab.returnValues[0]
expect(mutations.setCurrentTabId.calledOnceWith(state, newTabId)).to.equal(true)
expect($router.push.calledOnceWith('/editor')).to.equal(true)
expect(wrapper.find('[data-modal="parse"]').exists()).to.equal(false)
})
it('import cancel', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
},
hasErrors: false,
messages: []
})
parse.onCall(1).resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo'],
[2, 'bar']
]
},
hasErrors: false,
messages: []
})
const schema = {}
const newDb = {
createDb: sinon.stub().resolves(schema),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub()
}
sinon.stub(database, 'getNewDatabase').returns(newDb)
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
await wrapper.vm.$nextTick()
await wrapper.find('#csv-cancel').trigger('click')
expect(mutations.setDb.called).to.equal(false)
expect(mutations.saveSchema.called).to.equal(false)
expect(actions.addTab.called).to.equal(false)
expect(mutations.setCurrentTabId.called).to.equal(false)
expect($router.push.called).to.equal(false)
expect(wrapper.find('[data-modal="parse"]').exists()).to.equal(false)
})
}) })