mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 18:18:53 +08:00
CSV import as a table and db connection rework
- Add csv to existing db #32 - [RFE] Simplify working with temporary tables #53
This commit is contained in:
@@ -4,6 +4,9 @@ import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Vuex from 'vuex'
|
||||
import Schema from '@/views/Main/Editor/Schema'
|
||||
import TableDescription from '@/views/Main/Editor/Schema/TableDescription'
|
||||
import database from '@/lib/database'
|
||||
import fIo from '@/lib/utils/fileIo'
|
||||
import csv from '@/components/CsvImport/csv'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
@@ -16,7 +19,9 @@ describe('Schema.vue', () => {
|
||||
it('Renders DB name on initial', () => {
|
||||
// mock store state
|
||||
const state = {
|
||||
dbName: 'fooDB'
|
||||
db: {
|
||||
dbName: 'fooDB'
|
||||
}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
|
||||
@@ -31,7 +36,9 @@ describe('Schema.vue', () => {
|
||||
it('Schema visibility is toggled when click on DB name', async () => {
|
||||
// mock store state
|
||||
const state = {
|
||||
dbName: 'fooDB'
|
||||
db: {
|
||||
dbName: 'fooDB'
|
||||
}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
|
||||
@@ -48,30 +55,32 @@ describe('Schema.vue', () => {
|
||||
it('Schema filter', async () => {
|
||||
// mock store state
|
||||
const state = {
|
||||
dbName: 'fooDB',
|
||||
schema: [
|
||||
{
|
||||
name: 'foo',
|
||||
columns: [
|
||||
{ name: 'id', type: 'INTEGER' },
|
||||
{ name: 'title', type: 'NVARCHAR(24)' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
columns: [
|
||||
{ name: 'id', type: 'INTEGER' },
|
||||
{ name: 'price', type: 'INTEGER' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'foobar',
|
||||
columns: [
|
||||
{ name: 'id', type: 'INTEGER' },
|
||||
{ name: 'price', type: 'INTEGER' }
|
||||
]
|
||||
}
|
||||
]
|
||||
db: {
|
||||
dbName: 'fooDB',
|
||||
schema: [
|
||||
{
|
||||
name: 'foo',
|
||||
columns: [
|
||||
{ name: 'id', type: 'INTEGER' },
|
||||
{ name: 'title', type: 'NVARCHAR(24)' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
columns: [
|
||||
{ name: 'id', type: 'INTEGER' },
|
||||
{ name: 'price', type: 'INTEGER' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'foobar',
|
||||
columns: [
|
||||
{ name: 'id', type: 'INTEGER' },
|
||||
{ name: 'price', type: 'INTEGER' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
|
||||
@@ -101,15 +110,69 @@ describe('Schema.vue', () => {
|
||||
|
||||
it('exports db', async () => {
|
||||
const state = {
|
||||
dbName: 'fooDB',
|
||||
db: {
|
||||
dbName: 'fooDB',
|
||||
export: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
const wrapper = mount(Schema, { store, localVue })
|
||||
|
||||
await wrapper.findComponent({ name: 'export-icon' }).trigger('click')
|
||||
await wrapper.findComponent({ name: 'export-icon' }).find('svg').trigger('click')
|
||||
expect(state.db.export.calledOnceWith('fooDB'))
|
||||
})
|
||||
|
||||
it('adds table', async () => {
|
||||
const file = { name: 'test.csv' }
|
||||
sinon.stub(fIo, 'getFileFromUser').resolves(file)
|
||||
|
||||
sinon.stub(csv, 'parse').resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: [
|
||||
[1, 'foo']
|
||||
]
|
||||
},
|
||||
hasErrors: false,
|
||||
messages: []
|
||||
})
|
||||
|
||||
const state = {
|
||||
db: database.getNewDatabase()
|
||||
}
|
||||
state.db.dbName = 'db'
|
||||
state.db.execute('CREATE TABLE foo(id)')
|
||||
state.db.refreshSchema()
|
||||
sinon.spy(state.db, 'refreshSchema')
|
||||
|
||||
const store = new Vuex.Store({ state })
|
||||
const wrapper = mount(Schema, { store, localVue })
|
||||
sinon.spy(wrapper.vm.$refs.addCsv, 'previewCsv')
|
||||
sinon.spy(wrapper.vm, 'addCsv')
|
||||
sinon.spy(wrapper.vm.$refs.addCsv, 'loadFromCsv')
|
||||
|
||||
await wrapper.findComponent({ name: 'add-table-icon' }).find('svg').trigger('click')
|
||||
await wrapper.vm.$refs.addCsv.previewCsv.returnValues[0]
|
||||
await wrapper.vm.addCsv.returnValues[0]
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('[data-modal="addCsv"]').exists()).to.equal(true)
|
||||
await wrapper.find('#csv-import').trigger('click')
|
||||
await wrapper.vm.$refs.addCsv.loadFromCsv.returnValues[0]
|
||||
await wrapper.find('#csv-finish').trigger('click')
|
||||
expect(wrapper.find('[data-modal="addCsv"]').exists()).to.equal(false)
|
||||
await state.db.refreshSchema.returnValues[0]
|
||||
|
||||
expect(wrapper.vm.$store.state.db.schema).to.eql([
|
||||
{ name: 'test', columns: [{ name: 'col1', type: 'real' }, { name: 'col2', type: 'text' }] },
|
||||
{ name: 'foo', columns: [{ name: 'id', type: 'N/A' }] }
|
||||
])
|
||||
|
||||
const res = await wrapper.vm.$store.state.db.execute('select * from test')
|
||||
expect(res).to.eql({
|
||||
columns: ['col1', 'col2'],
|
||||
values: [[1, 'foo']]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,6 +7,5 @@ describe('SqlEditor.vue', () => {
|
||||
const wrapper = mount(SqlEditor)
|
||||
await wrapper.findComponent({ name: 'codemirror' }).vm.$emit('input', 'SELECT * FROM foo')
|
||||
expect(wrapper.emitted('input')[0]).to.eql(['SELECT * FROM foo'])
|
||||
// Take a pause to keep proper state in debounced '@/views/Main/Editor/Tabs/Tab/SqlEditor/hint'
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,22 +11,24 @@ describe('hint.js', () => {
|
||||
|
||||
it('Calculates table list for hint', () => {
|
||||
// mock store state
|
||||
const schema = [
|
||||
{
|
||||
name: 'foo',
|
||||
columns: [
|
||||
{ name: 'fooId', type: 'INTEGER' },
|
||||
{ name: 'name', type: 'NVARCHAR(20)' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
columns: [
|
||||
{ name: 'barId', type: 'INTEGER' }
|
||||
]
|
||||
}
|
||||
]
|
||||
sinon.stub(state, 'schema').value(schema)
|
||||
const db = {
|
||||
schema: [
|
||||
{
|
||||
name: 'foo',
|
||||
columns: [
|
||||
{ name: 'fooId', type: 'INTEGER' },
|
||||
{ name: 'name', type: 'NVARCHAR(20)' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
columns: [
|
||||
{ name: 'barId', type: 'INTEGER' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
sinon.stub(state, 'db').value(db)
|
||||
|
||||
// mock showHint and editor
|
||||
sinon.stub(CM, 'showHint')
|
||||
@@ -52,16 +54,18 @@ describe('hint.js', () => {
|
||||
|
||||
it('Add default table if there is only one table in schema', () => {
|
||||
// mock store state
|
||||
const schema = [
|
||||
{
|
||||
name: 'foo',
|
||||
columns: [
|
||||
{ name: 'fooId', type: 'INTEGER' },
|
||||
{ name: 'name', type: 'NVARCHAR(20)' }
|
||||
]
|
||||
}
|
||||
]
|
||||
sinon.stub(state, 'schema').value(schema)
|
||||
const db = {
|
||||
schema: [
|
||||
{
|
||||
name: 'foo',
|
||||
columns: [
|
||||
{ name: 'fooId', type: 'INTEGER' },
|
||||
{ name: 'name', type: 'NVARCHAR(20)' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
sinon.stub(state, 'db').value(db)
|
||||
|
||||
// mock showHint and editor
|
||||
sinon.stub(CM, 'showHint')
|
||||
@@ -190,7 +194,7 @@ describe('hint.js', () => {
|
||||
|
||||
it('tables is empty object when schema is null', () => {
|
||||
// mock store state
|
||||
sinon.stub(state, 'schema').value(null)
|
||||
sinon.stub(state, 'db').value({ schema: null })
|
||||
|
||||
// mock showHint and editor
|
||||
sinon.stub(CM, 'showHint')
|
||||
|
||||
@@ -182,7 +182,8 @@ describe('Tab.vue', () => {
|
||||
const state = {
|
||||
currentTabId: 1,
|
||||
db: {
|
||||
execute: sinon.stub().rejects(new Error('There is no table foo'))
|
||||
execute: sinon.stub().rejects(new Error('There is no table foo')),
|
||||
refreshSchema: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +222,7 @@ describe('Tab.vue', () => {
|
||||
currentTabId: 1,
|
||||
db: {
|
||||
execute: sinon.stub().resolves(result),
|
||||
getSchema: sinon.stub().resolves({ dbName: '', schema: [] })
|
||||
refreshSchema: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,36 +254,17 @@ describe('Tab.vue', () => {
|
||||
columns: ['id', 'name'],
|
||||
values: []
|
||||
}
|
||||
const newSchema = {
|
||||
dbName: 'fooDb',
|
||||
schema: [
|
||||
{
|
||||
name: 'foo',
|
||||
columns: [
|
||||
{ name: 'id', type: 'INTEGER' },
|
||||
{ name: 'title', type: 'NVARCHAR(30)' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
columns: [
|
||||
{ name: 'a', type: 'N/A' },
|
||||
{ name: 'b', type: 'N/A' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// mock store state
|
||||
const state = {
|
||||
currentTabId: 1,
|
||||
dbName: 'fooDb',
|
||||
db: {
|
||||
execute: sinon.stub().resolves(result),
|
||||
getSchema: sinon.stub().resolves(newSchema)
|
||||
refreshSchema: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
|
||||
sinon.spy(mutations, 'saveSchema')
|
||||
const store = new Vuex.Store({ state, mutations })
|
||||
|
||||
// mount the component
|
||||
@@ -300,7 +282,6 @@ describe('Tab.vue', () => {
|
||||
})
|
||||
|
||||
await wrapper.vm.execute()
|
||||
expect(state.db.getSchema.calledOnceWith('fooDb')).to.equal(true)
|
||||
expect(mutations.saveSchema.calledOnceWith(state, newSchema)).to.equal(true)
|
||||
expect(state.db.refreshSchema.calledOnce).to.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('MainMenu.vue', () => {
|
||||
const state = {
|
||||
currentTab: { query: '', execute: sinon.stub() },
|
||||
tabs: [{}],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
const $route = { path: '/editor' }
|
||||
@@ -49,7 +49,7 @@ describe('MainMenu.vue', () => {
|
||||
const state = {
|
||||
currentTab: null,
|
||||
tabs: [{}],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
const $route = { path: '/editor' }
|
||||
@@ -65,11 +65,11 @@ describe('MainMenu.vue', () => {
|
||||
expect(wrapper.find('#create-btn').isVisible()).to.equal(true)
|
||||
})
|
||||
|
||||
it('Run is disabled if there is no schema or no query', async () => {
|
||||
it('Run is disabled if there is no db or no query', async () => {
|
||||
const state = {
|
||||
currentTab: { query: 'SELECT * FROM foo', execute: sinon.stub() },
|
||||
tabs: [{}],
|
||||
schema: null
|
||||
db: null
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
const $route = { path: '/editor' }
|
||||
@@ -82,7 +82,7 @@ describe('MainMenu.vue', () => {
|
||||
const vm = wrapper.vm
|
||||
expect(wrapper.find('#run-btn').element.disabled).to.equal(true)
|
||||
|
||||
await vm.$set(state, 'schema', [])
|
||||
await vm.$set(state, 'db', {})
|
||||
expect(wrapper.find('#run-btn').element.disabled).to.equal(false)
|
||||
|
||||
await vm.$set(state.currentTab, 'query', '')
|
||||
@@ -97,7 +97,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ isUnsaved: true }],
|
||||
schema: null
|
||||
db: {}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
const $route = { path: '/editor' }
|
||||
@@ -122,7 +122,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ isUnsaved: true }],
|
||||
schema: null
|
||||
db: {}
|
||||
}
|
||||
const newQueryId = 1
|
||||
const actions = {
|
||||
@@ -156,7 +156,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ isUnsaved: true }],
|
||||
schema: null
|
||||
db: {}
|
||||
}
|
||||
const newQueryId = 1
|
||||
const actions = {
|
||||
@@ -191,7 +191,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ isUnsaved: true }],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
const $route = { path: '/editor' }
|
||||
@@ -212,14 +212,14 @@ describe('MainMenu.vue', () => {
|
||||
expect(state.currentTab.execute.calledTwice).to.equal(true)
|
||||
|
||||
// Running is disabled and route path is editor
|
||||
await wrapper.vm.$set(state, 'schema', null)
|
||||
await wrapper.vm.$set(state, 'db', null)
|
||||
document.dispatchEvent(ctrlR)
|
||||
expect(state.currentTab.execute.calledTwice).to.equal(true)
|
||||
document.dispatchEvent(metaR)
|
||||
expect(state.currentTab.execute.calledTwice).to.equal(true)
|
||||
|
||||
// Running is enabled and route path is not editor
|
||||
await wrapper.vm.$set(state, 'schema', [])
|
||||
await wrapper.vm.$set(state, 'db', {})
|
||||
await wrapper.vm.$set($route, 'path', '/my-queries')
|
||||
document.dispatchEvent(ctrlR)
|
||||
expect(state.currentTab.execute.calledTwice).to.equal(true)
|
||||
@@ -236,7 +236,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ isUnsaved: true }],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
const $route = { path: '/editor' }
|
||||
@@ -257,14 +257,14 @@ describe('MainMenu.vue', () => {
|
||||
expect(state.currentTab.execute.calledTwice).to.equal(true)
|
||||
|
||||
// Running is disabled and route path is editor
|
||||
await wrapper.vm.$set(state, 'schema', null)
|
||||
await wrapper.vm.$set(state, 'db', null)
|
||||
document.dispatchEvent(ctrlEnter)
|
||||
expect(state.currentTab.execute.calledTwice).to.equal(true)
|
||||
document.dispatchEvent(metaEnter)
|
||||
expect(state.currentTab.execute.calledTwice).to.equal(true)
|
||||
|
||||
// Running is enabled and route path is not editor
|
||||
await wrapper.vm.$set(state, 'schema', [])
|
||||
await wrapper.vm.$set(state, 'db', {})
|
||||
await wrapper.vm.$set($route, 'path', '/my-queries')
|
||||
document.dispatchEvent(ctrlEnter)
|
||||
expect(state.currentTab.execute.calledTwice).to.equal(true)
|
||||
@@ -280,7 +280,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ isUnsaved: true }],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
const $route = { path: '/editor' }
|
||||
@@ -315,7 +315,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ isUnsaved: true }],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const store = new Vuex.Store({ state })
|
||||
const $route = { path: '/editor' }
|
||||
@@ -360,7 +360,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ id: 1, name: 'foo', isUnsaved: true }],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const mutations = {
|
||||
updateTab: sinon.stub()
|
||||
@@ -411,7 +411,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ id: 1, name: null, tempName: 'Untitled', isUnsaved: true }],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const mutations = {
|
||||
updateTab: sinon.stub()
|
||||
@@ -456,7 +456,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ id: 1, name: null, tempName: 'Untitled', isUnsaved: true }],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const mutations = {
|
||||
updateTab: sinon.stub()
|
||||
@@ -528,7 +528,7 @@ describe('MainMenu.vue', () => {
|
||||
view: 'chart'
|
||||
},
|
||||
tabs: [{ id: 1, name: 'foo', isUnsaved: true, isPredefined: true }],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const mutations = {
|
||||
updateTab: sinon.stub()
|
||||
@@ -607,7 +607,7 @@ describe('MainMenu.vue', () => {
|
||||
tabIndex: 0
|
||||
},
|
||||
tabs: [{ id: 1, name: null, tempName: 'Untitled', isUnsaved: true }],
|
||||
schema: []
|
||||
db: {}
|
||||
}
|
||||
const mutations = {
|
||||
updateTab: sinon.stub()
|
||||
|
||||
Reference in New Issue
Block a user