mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-07 02:28:54 +08:00
Compare commits
3 Commits
3893a66f4e
...
0.25.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
244ba9eb08 | ||
|
|
53e5194295 | ||
|
|
04274ef19a |
@@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
# sqliteviz
|
# sqliteviz
|
||||||
|
|
||||||
Sqliteviz is a single-page offline-first PWA for fully client-side visualisation of SQLite databases or CSV files.
|
Sqliteviz is a single-page offline-first PWA for fully client-side visualisation
|
||||||
|
of SQLite databases, CSV, JSON or NDJSON files.
|
||||||
|
|
||||||
With sqliteviz you can:
|
With sqliteviz you can:
|
||||||
- run SQL queries against a SQLite database and create [Plotly][11] charts and pivot tables based on the result sets
|
- run SQL queries against a SQLite database and create [Plotly][11] charts and pivot tables based on the result sets
|
||||||
- import a CSV file into a SQLite database and visualize imported data
|
- import a CSV/JSON/NDJSON file into a SQLite database and visualize imported data
|
||||||
- export result set to CSV file
|
- export result set to CSV file
|
||||||
- manage inquiries and run them against different databases
|
- manage inquiries and run them against different databases
|
||||||
- import/export inquiries from/to a JSON file
|
- import/export inquiries from/to a JSON file
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"background_color": "white",
|
"background_color": "white",
|
||||||
"description": "Sqliteviz is a single-page application for fully client-side visualisation of SQLite databases or CSV.",
|
"description": "Sqliteviz is a single-page application for fully client-side visualisation of SQLite databases, CSV, JSON or NDJSON.",
|
||||||
"display": "fullscreen",
|
"display": "fullscreen",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -348,7 +348,8 @@ export default {
|
|||||||
this.addedTable = this.tableName
|
this.addedTable = this.tableName
|
||||||
// Inform about import success
|
// Inform about import success
|
||||||
period = time.getPeriod(start, end)
|
period = time.getPeriod(start, end)
|
||||||
importMsg.message = `Importing ${this.typeName} into a SQLite database is completed in ${period}.`
|
importMsg.message = `Importing ${this.typeName} ` +
|
||||||
|
`into a SQLite database is completed in ${period}.`
|
||||||
importMsg.type = 'success'
|
importMsg.type = 'success'
|
||||||
|
|
||||||
// Loading indicator for import is not needed anymore
|
// Loading indicator for import is not needed anymore
|
||||||
@@ -408,7 +409,8 @@ export default {
|
|||||||
return [
|
return [
|
||||||
'/*',
|
'/*',
|
||||||
` * Your NDJSON file has been imported into ${this.addedTable} table.`,
|
` * Your NDJSON file has been imported into ${this.addedTable} table.`,
|
||||||
` * Run this SQL query to get values of property ${firstKey} and make them available for charting.`,
|
` * Run this SQL query to get values of property ${firstKey} ` +
|
||||||
|
'and make them available for charting.',
|
||||||
' */',
|
' */',
|
||||||
`SELECT doc->>'${firstKey}'`,
|
`SELECT doc->>'${firstKey}'`,
|
||||||
`FROM "${this.addedTable}"`
|
`FROM "${this.addedTable}"`
|
||||||
@@ -431,7 +433,8 @@ export default {
|
|||||||
return [
|
return [
|
||||||
'/*',
|
'/*',
|
||||||
` * Your JSON file has been imported into ${this.addedTable} table.`,
|
` * Your JSON file has been imported into ${this.addedTable} table.`,
|
||||||
` * Run this SQL query to get values of property ${firstKey} and make them available for charting.`,
|
` * Run this SQL query to get values of property ${firstKey} ` +
|
||||||
|
'and make them available for charting.',
|
||||||
' */',
|
' */',
|
||||||
'SELECT *',
|
'SELECT *',
|
||||||
`FROM "${this.addedTable}"`,
|
`FROM "${this.addedTable}"`,
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
@click="browse"
|
@click="browse"
|
||||||
>
|
>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
Drop the database or CSV file here or click to choose a file from your computer.
|
Drop the database, CSV, JSON or NDJSON file here
|
||||||
|
or click to choose a file from your computer.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export default {
|
|||||||
components: { Pager },
|
components: { Pager },
|
||||||
props: {
|
props: {
|
||||||
dataSet: Object,
|
dataSet: Object,
|
||||||
time: String,
|
time: [String, Number],
|
||||||
pageSize: {
|
pageSize: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 20
|
default: 20
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="icon-tooltip" :style="tooltipStyle" ref="tooltip">
|
<span class="icon-tooltip" :style="tooltipStyle" ref="tooltip">
|
||||||
Add new table from CSV
|
Add new table from CSV, JSON or NDJSON
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="icon-tooltip" :style="tooltipStyle" ref="tooltip">
|
<span class="icon-tooltip" :style="tooltipStyle" ref="tooltip">
|
||||||
Load another database or CSV
|
Load another database, CSV, JSON or NDJSON
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('CsvJsonImport.vue', () => {
|
|||||||
let clock
|
let clock
|
||||||
let wrapper
|
let wrapper
|
||||||
const newTabId = 1
|
const newTabId = 1
|
||||||
const file = { name: 'my data.csv' }
|
const file = new File([], 'my data.csv')
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clock = sinon.useFakeTimers()
|
clock = sinon.useFakeTimers()
|
||||||
@@ -78,6 +78,7 @@ describe('CsvJsonImport.vue', () => {
|
|||||||
await wrapper.vm.open()
|
await wrapper.vm.open()
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
expect(wrapper.find('[data-modal="addCsvJson"]').exists()).to.equal(true)
|
expect(wrapper.find('[data-modal="addCsvJson"]').exists()).to.equal(true)
|
||||||
|
expect(wrapper.find('.dialog-header').text()).to.equal('CSV import')
|
||||||
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data')
|
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data')
|
||||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.value).to.equal('|')
|
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.value).to.equal('|')
|
||||||
expect(wrapper.find('#quote-char input').element.value).to.equal('"')
|
expect(wrapper.find('#quote-char input').element.value).to.equal('"')
|
||||||
@@ -95,6 +96,34 @@ describe('CsvJsonImport.vue', () => {
|
|||||||
.to.include('Preview parsing is completed in')
|
.to.include('Preview parsing is completed in')
|
||||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-start').attributes().disabled).to.equal(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables import if no rows found', async () => {
|
||||||
|
sinon.stub(csv, 'parse').resolves({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['col2', 'col1'],
|
||||||
|
values: {
|
||||||
|
col1: [],
|
||||||
|
col2: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 0,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.vm.preview()
|
||||||
|
await wrapper.vm.open()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
const rows = wrapper.findAll('tbody tr')
|
||||||
|
expect(rows).to.have.lengthOf(0)
|
||||||
|
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||||
|
.to.include('No rows to import.')
|
||||||
|
expect(wrapper.find('.no-data').isVisible()).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-start').attributes().disabled).to.equal('disabled')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('reparses when parameters changes', async () => {
|
it('reparses when parameters changes', async () => {
|
||||||
@@ -231,7 +260,8 @@ describe('CsvJsonImport.vue', () => {
|
|||||||
col2: ['foo']
|
col2: ['foo']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rowCount: 1
|
rowCount: 1,
|
||||||
|
messages: []
|
||||||
})
|
})
|
||||||
|
|
||||||
wrapper.vm.preview()
|
wrapper.vm.preview()
|
||||||
@@ -240,7 +270,18 @@ describe('CsvJsonImport.vue', () => {
|
|||||||
|
|
||||||
let resolveParsing
|
let resolveParsing
|
||||||
parse.onCall(1).returns(new Promise(resolve => {
|
parse.onCall(1).returns(new Promise(resolve => {
|
||||||
resolveParsing = resolve
|
resolveParsing = () => resolve({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['col1', 'col2'],
|
||||||
|
values: {
|
||||||
|
col1: [1],
|
||||||
|
col2: ['foo']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 1,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||||
@@ -715,7 +756,11 @@ describe('CsvJsonImport.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('checks table name', async () => {
|
it('checks table name', async () => {
|
||||||
sinon.stub(csv, 'parse').resolves()
|
sinon.stub(csv, 'parse').resolves({
|
||||||
|
data: {},
|
||||||
|
hasErrors: false,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
await wrapper.vm.preview()
|
await wrapper.vm.preview()
|
||||||
await wrapper.vm.open()
|
await wrapper.vm.open()
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
@@ -743,3 +788,477 @@ describe('CsvJsonImport.vue', () => {
|
|||||||
expect(wrapper.vm.db.addTableFromCsv.called).to.equal(false)
|
expect(wrapper.vm.db.addTableFromCsv.called).to.equal(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('CsvJsonImport.vue - json', () => {
|
||||||
|
let state = {}
|
||||||
|
let actions = {}
|
||||||
|
let mutations = {}
|
||||||
|
let store = {}
|
||||||
|
let clock
|
||||||
|
let wrapper
|
||||||
|
const newTabId = 1
|
||||||
|
const file = new File(
|
||||||
|
[new Blob(
|
||||||
|
[JSON.stringify({ foo: [1, 2, 3] }, null, 2)],
|
||||||
|
{ type: 'application/json' }
|
||||||
|
)],
|
||||||
|
'my data.json',
|
||||||
|
{ type: 'application/json' })
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
clock = sinon.useFakeTimers()
|
||||||
|
|
||||||
|
// mock store state and mutations
|
||||||
|
state = {}
|
||||||
|
mutations = {
|
||||||
|
setDb: sinon.stub(),
|
||||||
|
setCurrentTabId: sinon.stub()
|
||||||
|
}
|
||||||
|
actions = {
|
||||||
|
addTab: sinon.stub().resolves(newTabId)
|
||||||
|
}
|
||||||
|
store = new Vuex.Store({ state, mutations, actions })
|
||||||
|
|
||||||
|
const db = {
|
||||||
|
sanitizeTableName: sinon.stub().returns('my_data'),
|
||||||
|
addTableFromCsv: sinon.stub().resolves(),
|
||||||
|
createProgressCounter: sinon.stub().returns(1),
|
||||||
|
deleteProgressCounter: sinon.stub(),
|
||||||
|
validateTableName: sinon.stub().resolves(),
|
||||||
|
execute: sinon.stub().resolves(),
|
||||||
|
refreshSchema: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
|
||||||
|
// mount the component
|
||||||
|
wrapper = mount(CsvJsonImport, {
|
||||||
|
store,
|
||||||
|
propsData: {
|
||||||
|
file,
|
||||||
|
dialogName: 'addCsvJson',
|
||||||
|
db
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('previews', async () => {
|
||||||
|
await wrapper.vm.preview()
|
||||||
|
await wrapper.vm.open()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(wrapper.find('[data-modal="addCsvJson"]').exists()).to.equal(true)
|
||||||
|
expect(wrapper.find('.dialog-header').text()).to.equal('JSON import')
|
||||||
|
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data')
|
||||||
|
expect(wrapper.findComponent({ name: 'delimiter-selector' }).exists()).to.equal(false)
|
||||||
|
expect(wrapper.find('#quote-char input').exists()).to.equal(false)
|
||||||
|
expect(wrapper.find('#escape-char input').exists()).to.equal(false)
|
||||||
|
expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(false)
|
||||||
|
const rows = wrapper.findAll('tbody tr')
|
||||||
|
expect(rows).to.have.lengthOf(1)
|
||||||
|
expect(rows.at(0).findAll('td').at(0).text()).to.equal([
|
||||||
|
'{',
|
||||||
|
' "foo": [',
|
||||||
|
' 1,',
|
||||||
|
' 2,',
|
||||||
|
' 3',
|
||||||
|
' ]',
|
||||||
|
'}'
|
||||||
|
].join('\n')
|
||||||
|
)
|
||||||
|
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||||
|
.to.include('Preview parsing is completed in')
|
||||||
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has proper state before parsing is complete', async () => {
|
||||||
|
const getJsonParseResult = sinon.stub(wrapper.vm, 'getJsonParseResult')
|
||||||
|
getJsonParseResult.onCall(0).returns({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['doc'],
|
||||||
|
values: {
|
||||||
|
doc: ['{ "foo": [ 1, 2, 3 ] }']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 1,
|
||||||
|
hasErrors: false,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
|
||||||
|
let resolveParsing
|
||||||
|
getJsonParseResult.onCall(1).returns(new Promise(resolve => {
|
||||||
|
resolveParsing = () => resolve({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['doc'],
|
||||||
|
values: {
|
||||||
|
doc: ['{ "foo": [ 1, 2, 3 ] }']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 1,
|
||||||
|
hasErrors: false,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
await wrapper.vm.preview()
|
||||||
|
await wrapper.vm.open()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||||
|
await wrapper.find('#import-start').trigger('click')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
// "Parsing JSON..." in the logs
|
||||||
|
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg').at(1).text())
|
||||||
|
.to.equal('Parsing JSON...')
|
||||||
|
|
||||||
|
// 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.find('#import-cancel').element.disabled).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||||
|
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||||
|
await resolveParsing()
|
||||||
|
await getJsonParseResult.returnValues[1]
|
||||||
|
|
||||||
|
// Loading indicator is not shown when parsing is compete
|
||||||
|
expect(
|
||||||
|
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||||
|
).to.equal(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has proper state before import is completed', async () => {
|
||||||
|
const getJsonParseResult = sinon.spy(wrapper.vm, 'getJsonParseResult')
|
||||||
|
|
||||||
|
let resolveImport = sinon.stub()
|
||||||
|
wrapper.vm.db.addTableFromCsv = sinon.stub()
|
||||||
|
.resolves(new Promise(resolve => { resolveImport = resolve }))
|
||||||
|
|
||||||
|
await wrapper.vm.preview()
|
||||||
|
await wrapper.vm.open()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||||
|
await wrapper.find('#import-start').trigger('click')
|
||||||
|
await getJsonParseResult.returnValues[1]
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
// Parsing success in the logs
|
||||||
|
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg').at(2).text())
|
||||||
|
.to.equal('Importing JSON 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.find('#import-cancel').element.disabled).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||||
|
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||||
|
expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name
|
||||||
|
|
||||||
|
// After resolving - loading indicator is not shown
|
||||||
|
await resolveImport()
|
||||||
|
await wrapper.vm.db.addTableFromCsv.returnValues[0]
|
||||||
|
expect(
|
||||||
|
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||||
|
).to.equal(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('import success', async () => {
|
||||||
|
const getJsonParseResult = sinon.spy(wrapper.vm, 'getJsonParseResult')
|
||||||
|
|
||||||
|
await wrapper.vm.preview()
|
||||||
|
await wrapper.vm.open()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||||
|
await wrapper.find('#import-start').trigger('click')
|
||||||
|
await getJsonParseResult.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 JSON into a SQLite database is completed in')
|
||||||
|
|
||||||
|
// All the dialog controls are enabled
|
||||||
|
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
|
||||||
|
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('CsvJsonImport.vue - ndjson', () => {
|
||||||
|
let state = {}
|
||||||
|
let actions = {}
|
||||||
|
let mutations = {}
|
||||||
|
let store = {}
|
||||||
|
let clock
|
||||||
|
let wrapper
|
||||||
|
const newTabId = 1
|
||||||
|
const file = new File([], 'my data.ndjson')
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
clock = sinon.useFakeTimers()
|
||||||
|
|
||||||
|
// mock store state and mutations
|
||||||
|
state = {}
|
||||||
|
mutations = {
|
||||||
|
setDb: sinon.stub(),
|
||||||
|
setCurrentTabId: sinon.stub()
|
||||||
|
}
|
||||||
|
actions = {
|
||||||
|
addTab: sinon.stub().resolves(newTabId)
|
||||||
|
}
|
||||||
|
store = new Vuex.Store({ state, mutations, actions })
|
||||||
|
|
||||||
|
const db = {
|
||||||
|
sanitizeTableName: sinon.stub().returns('my_data'),
|
||||||
|
addTableFromCsv: sinon.stub().resolves(),
|
||||||
|
createProgressCounter: sinon.stub().returns(1),
|
||||||
|
deleteProgressCounter: sinon.stub(),
|
||||||
|
validateTableName: sinon.stub().resolves(),
|
||||||
|
execute: sinon.stub().resolves(),
|
||||||
|
refreshSchema: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
|
||||||
|
// mount the component
|
||||||
|
wrapper = mount(CsvJsonImport, {
|
||||||
|
store,
|
||||||
|
propsData: {
|
||||||
|
file,
|
||||||
|
dialogName: 'addCsvJson',
|
||||||
|
db
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('previews', async () => {
|
||||||
|
sinon.stub(csv, 'parse').resolves({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['doc'],
|
||||||
|
values: {
|
||||||
|
doc: ['{ "foo": [ 1, 2, 3 ] }']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 1,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
|
||||||
|
wrapper.vm.preview()
|
||||||
|
await wrapper.vm.open()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(wrapper.find('[data-modal="addCsvJson"]').exists()).to.equal(true)
|
||||||
|
expect(wrapper.find('.dialog-header').text()).to.equal('JSON import')
|
||||||
|
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data')
|
||||||
|
expect(wrapper.findComponent({ name: 'delimiter-selector' }).exists()).to.equal(false)
|
||||||
|
expect(wrapper.find('#quote-char input').exists()).to.equal(false)
|
||||||
|
expect(wrapper.find('#escape-char input').exists()).to.equal(false)
|
||||||
|
expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(false)
|
||||||
|
const rows = wrapper.findAll('tbody tr')
|
||||||
|
expect(rows).to.have.lengthOf(1)
|
||||||
|
expect(rows.at(0).findAll('td').at(0).text()).to.equal('{ "foo": [ 1, 2, 3 ] }')
|
||||||
|
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||||
|
.to.include('Preview parsing is completed in')
|
||||||
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has proper state before parsing is complete', async () => {
|
||||||
|
const parse = sinon.stub(csv, 'parse')
|
||||||
|
parse.onCall(0).resolves({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['doc'],
|
||||||
|
values: {
|
||||||
|
doc: ['{ "foo": [ 1, 2, 3 ] }']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
wrapper.vm.preview()
|
||||||
|
wrapper.vm.open()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
let resolveParsing
|
||||||
|
parse.onCall(1).returns(new Promise(resolve => {
|
||||||
|
resolveParsing = () => resolve({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['doc'],
|
||||||
|
values: {
|
||||||
|
doc: ['{ "foo": [ 1, 2, 3 ] }']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 1,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||||
|
await wrapper.find('#import-start').trigger('click')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
// "Parsing JSON..." in the logs
|
||||||
|
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg').at(1).text())
|
||||||
|
.to.equal('Parsing JSON...')
|
||||||
|
|
||||||
|
// 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.find('#import-cancel').element.disabled).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||||
|
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-start').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('has proper state before import is completed', async () => {
|
||||||
|
const parse = sinon.stub(csv, 'parse')
|
||||||
|
parse.onCall(0).resolves({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['doc'],
|
||||||
|
values: {
|
||||||
|
doc: ['{ "foo": [ 1, 2, 3 ] }']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 1,
|
||||||
|
hasErrors: false,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
|
||||||
|
parse.onCall(1).resolves({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['doc'],
|
||||||
|
values: {
|
||||||
|
doc: ['{ "foo": [ 1, 2, 3 ] }']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 1,
|
||||||
|
hasErrors: false,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
|
||||||
|
let resolveImport = sinon.stub()
|
||||||
|
wrapper.vm.db.addTableFromCsv = sinon.stub()
|
||||||
|
.resolves(new Promise(resolve => { resolveImport = resolve }))
|
||||||
|
|
||||||
|
wrapper.vm.preview()
|
||||||
|
wrapper.vm.open()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||||
|
await wrapper.find('#import-start').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 JSON 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.find('#import-cancel').element.disabled).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||||
|
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||||
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||||
|
expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name
|
||||||
|
|
||||||
|
// After resolving - loading indicator is not shown
|
||||||
|
await resolveImport()
|
||||||
|
await wrapper.vm.db.addTableFromCsv.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: ['doc'],
|
||||||
|
values: {
|
||||||
|
doc: ['{ "foo": [ 1, 2, 3 ] }']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 1,
|
||||||
|
hasErrors: false,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
// we need to separate calles because messages will mutate
|
||||||
|
parse.onCall(1).resolves({
|
||||||
|
delimiter: '|',
|
||||||
|
data: {
|
||||||
|
columns: ['doc'],
|
||||||
|
values: {
|
||||||
|
doc: ['{ "foo": [ 1, 2, 3 ] }']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCount: 2,
|
||||||
|
hasErrors: false,
|
||||||
|
messages: []
|
||||||
|
})
|
||||||
|
|
||||||
|
wrapper.vm.preview()
|
||||||
|
wrapper.vm.open()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||||
|
await wrapper.find('#import-start').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 JSON into a SQLite database is completed in')
|
||||||
|
|
||||||
|
// All the dialog controls are enabled
|
||||||
|
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
|
||||||
|
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
|
||||||
|
expect(wrapper.find('#import-finish').isVisible()).to.equal(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe('DbUploader.vue', () => {
|
|||||||
|
|
||||||
it('loads db on click and redirects to /workspace', async () => {
|
it('loads db on click and redirects to /workspace', async () => {
|
||||||
// mock getting a file from user
|
// mock getting a file from user
|
||||||
const file = { name: 'test.db' }
|
const file = new File([], 'test.db')
|
||||||
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
||||||
|
|
||||||
// mock db loading
|
// mock db loading
|
||||||
@@ -85,7 +85,7 @@ describe('DbUploader.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// mock a file dropped by a user
|
// mock a file dropped by a user
|
||||||
const file = { name: 'test.db' }
|
const file = new File([], 'test.db')
|
||||||
const dropData = { dataTransfer: new DataTransfer() }
|
const dropData = { dataTransfer: new DataTransfer() }
|
||||||
Object.defineProperty(dropData.dataTransfer, 'files', {
|
Object.defineProperty(dropData.dataTransfer, 'files', {
|
||||||
value: [file],
|
value: [file],
|
||||||
@@ -103,7 +103,7 @@ describe('DbUploader.vue', () => {
|
|||||||
|
|
||||||
it("doesn't redirect if already on /workspace", async () => {
|
it("doesn't redirect if already on /workspace", async () => {
|
||||||
// mock getting a file from user
|
// mock getting a file from user
|
||||||
const file = { name: 'test.db' }
|
const file = new File([], 'test.db')
|
||||||
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
||||||
|
|
||||||
// mock db loading
|
// mock db loading
|
||||||
@@ -136,7 +136,7 @@ describe('DbUploader.vue', () => {
|
|||||||
|
|
||||||
it('shows parse dialog if gets csv file', async () => {
|
it('shows parse dialog if gets csv file', async () => {
|
||||||
// mock getting a file from user
|
// mock getting a file from user
|
||||||
const file = { name: 'test.csv' }
|
const file = new File([], 'test.csv')
|
||||||
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
||||||
|
|
||||||
// mock router
|
// mock router
|
||||||
@@ -168,9 +168,77 @@ describe('DbUploader.vue', () => {
|
|||||||
wrapper.destroy()
|
wrapper.destroy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('deletes temporary db if CSV import is canceled', async () => {
|
it('shows parse dialog if gets json file', async () => {
|
||||||
// mock getting a file from user
|
// mock getting a file from user
|
||||||
const file = { name: 'test.csv' }
|
const file = new File([], 'test.json', { type: 'application/json' })
|
||||||
|
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
||||||
|
|
||||||
|
// mock router
|
||||||
|
const $router = { push: sinon.stub() }
|
||||||
|
const $route = { path: '/workspace' }
|
||||||
|
|
||||||
|
// mount the component
|
||||||
|
const wrapper = mount(DbUploader, {
|
||||||
|
attachTo: place,
|
||||||
|
store,
|
||||||
|
mocks: { $router, $route },
|
||||||
|
propsData: {
|
||||||
|
type: 'illustrated'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const JsonImport = wrapper.vm.$refs.addCsvJson
|
||||||
|
sinon.stub(JsonImport, 'reset')
|
||||||
|
sinon.stub(JsonImport, 'preview').resolves()
|
||||||
|
sinon.stub(JsonImport, 'open')
|
||||||
|
|
||||||
|
await wrapper.find('.drop-area').trigger('click')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(JsonImport.reset.calledOnce).to.equal(true)
|
||||||
|
await wrapper.vm.animationPromise
|
||||||
|
expect(JsonImport.preview.calledOnce).to.equal(true)
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(JsonImport.open.calledOnce).to.equal(true)
|
||||||
|
wrapper.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows parse dialog if gets ndjson file', async () => {
|
||||||
|
// mock getting a file from user
|
||||||
|
const file = new File([], 'test.ndjson')
|
||||||
|
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
||||||
|
|
||||||
|
// mock router
|
||||||
|
const $router = { push: sinon.stub() }
|
||||||
|
const $route = { path: '/workspace' }
|
||||||
|
|
||||||
|
// mount the component
|
||||||
|
const wrapper = mount(DbUploader, {
|
||||||
|
attachTo: place,
|
||||||
|
store,
|
||||||
|
mocks: { $router, $route },
|
||||||
|
propsData: {
|
||||||
|
type: 'illustrated'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const JsonImport = wrapper.vm.$refs.addCsvJson
|
||||||
|
sinon.stub(JsonImport, 'reset')
|
||||||
|
sinon.stub(JsonImport, 'preview').resolves()
|
||||||
|
sinon.stub(JsonImport, 'open')
|
||||||
|
|
||||||
|
await wrapper.find('.drop-area').trigger('click')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(JsonImport.reset.calledOnce).to.equal(true)
|
||||||
|
await wrapper.vm.animationPromise
|
||||||
|
expect(JsonImport.preview.calledOnce).to.equal(true)
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(JsonImport.open.calledOnce).to.equal(true)
|
||||||
|
wrapper.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deletes temporary db if import is canceled', async () => {
|
||||||
|
// mock getting a file from user
|
||||||
|
const file = new File([], 'test.csv')
|
||||||
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
||||||
|
|
||||||
// mock router
|
// mock router
|
||||||
|
|||||||
@@ -28,7 +28,26 @@ describe('csv.js', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('getResult without fields', () => {
|
it('getResult without fields but with columns', () => {
|
||||||
|
const source = {
|
||||||
|
data: [
|
||||||
|
[1, 'foo', new Date('2021-06-30T14:10:24.717Z')],
|
||||||
|
[2, 'bar', new Date('2021-07-30T14:10:15.717Z')]
|
||||||
|
],
|
||||||
|
meta: {}
|
||||||
|
}
|
||||||
|
const columns = ['id', 'name', 'date']
|
||||||
|
expect(csv.getResult(source, columns)).to.eql({
|
||||||
|
columns: ['id', 'name', 'date'],
|
||||||
|
values: {
|
||||||
|
id: [1, 2],
|
||||||
|
name: ['foo', 'bar'],
|
||||||
|
date: ['2021-06-30T14:10:24.717Z', '2021-07-30T14:10:15.717Z']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getResult without fields and columns', () => {
|
||||||
const source = {
|
const source = {
|
||||||
data: [
|
data: [
|
||||||
[1, 'foo', new Date('2021-06-30T14:10:24.717Z')],
|
[1, 'foo', new Date('2021-06-30T14:10:24.717Z')],
|
||||||
|
|||||||
@@ -106,10 +106,65 @@ describe('fileIo.js', () => {
|
|||||||
await expect(fIo.readAsArrayBuffer(blob)).to.be.rejectedWith('Problem parsing input file.')
|
await expect(fIo.readAsArrayBuffer(blob)).to.be.rejectedWith('Problem parsing input file.')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('isJSON', () => {
|
||||||
|
let file = { type: 'application/json' }
|
||||||
|
expect(fIo.isJSON(file)).to.equal(true)
|
||||||
|
|
||||||
|
file = { type: 'application/x-sqlite3' }
|
||||||
|
expect(fIo.isJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.db' }
|
||||||
|
expect(fIo.isJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.sqlite' }
|
||||||
|
expect(fIo.isJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.sqlite3' }
|
||||||
|
expect(fIo.isJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.csv' }
|
||||||
|
expect(fIo.isJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.ndjson' }
|
||||||
|
expect(fIo.isJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: 'text', name: 'test.db' }
|
||||||
|
expect(fIo.isJSON(file)).to.equal(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('isNDJSON', () => {
|
||||||
|
let file = { type: 'application/json', name: 'test.json' }
|
||||||
|
expect(fIo.isNDJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: 'application/x-sqlite3', name: 'test.sqlite3' }
|
||||||
|
expect(fIo.isNDJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.db' }
|
||||||
|
expect(fIo.isNDJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.sqlite' }
|
||||||
|
expect(fIo.isNDJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.sqlite3' }
|
||||||
|
expect(fIo.isNDJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.csv' }
|
||||||
|
expect(fIo.isNDJSON(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.ndjson' }
|
||||||
|
expect(fIo.isNDJSON(file)).to.equal(true)
|
||||||
|
|
||||||
|
file = { type: 'text', name: 'test.db' }
|
||||||
|
expect(fIo.isNDJSON(file)).to.equal(false)
|
||||||
|
})
|
||||||
|
|
||||||
it('isDatabase', () => {
|
it('isDatabase', () => {
|
||||||
let file = { type: 'application/vnd.sqlite3' }
|
let file = { type: 'application/vnd.sqlite3' }
|
||||||
expect(fIo.isDatabase(file)).to.equal(true)
|
expect(fIo.isDatabase(file)).to.equal(true)
|
||||||
|
|
||||||
|
file = { type: 'application/json' }
|
||||||
|
expect(fIo.isDatabase(file)).to.equal(false)
|
||||||
|
|
||||||
file = { type: 'application/x-sqlite3' }
|
file = { type: 'application/x-sqlite3' }
|
||||||
expect(fIo.isDatabase(file)).to.equal(true)
|
expect(fIo.isDatabase(file)).to.equal(true)
|
||||||
|
|
||||||
@@ -125,6 +180,9 @@ describe('fileIo.js', () => {
|
|||||||
file = { type: '', name: 'test.csv' }
|
file = { type: '', name: 'test.csv' }
|
||||||
expect(fIo.isDatabase(file)).to.equal(false)
|
expect(fIo.isDatabase(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.ndjson' }
|
||||||
|
expect(fIo.isDatabase(file)).to.equal(false)
|
||||||
|
|
||||||
file = { type: 'text', name: 'test.db' }
|
file = { type: 'text', name: 'test.db' }
|
||||||
expect(fIo.isDatabase(file)).to.equal(false)
|
expect(fIo.isDatabase(file)).to.equal(false)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ describe('Schema.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('adds table', async () => {
|
it('adds table', async () => {
|
||||||
const file = { name: 'test.csv' }
|
const file = new File([], 'test.csv')
|
||||||
sinon.stub(fIo, 'getFileFromUser').resolves(file)
|
sinon.stub(fIo, 'getFileFromUser').resolves(file)
|
||||||
|
|
||||||
sinon.stub(csv, 'parse').resolves({
|
sinon.stub(csv, 'parse').resolves({
|
||||||
|
|||||||
Reference in New Issue
Block a user