diff --git a/package-lock.json b/package-lock.json
index beeab25..6f9f51c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -50,6 +50,7 @@
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
+ "flush-promises": "^1.0.2",
"karma": "^3.1.4",
"karma-firefox-launcher": "^2.1.0",
"karma-webpack": "^4.0.2",
@@ -10090,6 +10091,12 @@
"resolved": "https://registry.npmjs.org/flip-pixels/-/flip-pixels-1.0.2.tgz",
"integrity": "sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA=="
},
+ "node_modules/flush-promises": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/flush-promises/-/flush-promises-1.0.2.tgz",
+ "integrity": "sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==",
+ "dev": true
+ },
"node_modules/flush-write-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
@@ -33658,6 +33665,12 @@
"resolved": "https://registry.npmjs.org/flip-pixels/-/flip-pixels-1.0.2.tgz",
"integrity": "sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA=="
},
+ "flush-promises": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/flush-promises/-/flush-promises-1.0.2.tgz",
+ "integrity": "sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==",
+ "dev": true
+ },
"flush-write-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
diff --git a/package.json b/package.json
index a473704..f7c0f8d 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
+ "flush-promises": "^1.0.2",
"karma": "^3.1.4",
"karma-firefox-launcher": "^2.1.0",
"karma-webpack": "^4.0.2",
diff --git a/src/components/Splitpanes/index.vue b/src/components/Splitpanes/index.vue
index 1f30746..1b3baeb 100644
--- a/src/components/Splitpanes/index.vue
+++ b/src/components/Splitpanes/index.vue
@@ -75,14 +75,23 @@ export default {
props: {
horizontal: { type: Boolean, default: false },
before: { type: Object },
- after: { type: Object }
+ after: { type: Object },
+ default: {
+ type: Object,
+ default: () => {
+ return {
+ before: 50,
+ after: 50
+ }
+ }
+ }
},
data () {
return {
container: null,
paneBefore: this.before,
paneAfter: this.after,
- beforeMinimising: {
+ beforeMinimising: !this.after.size || !this.before.size ? this.default : {
before: this.before.size,
after: this.after.size
},
diff --git a/src/lib/database/index.js b/src/lib/database/index.js
index bb12456..4232ed1 100644
--- a/src/lib/database/index.js
+++ b/src/lib/database/index.js
@@ -77,7 +77,7 @@ class Database {
}
this.dbName = file ? fu.getFileName(file) : 'database'
- this.refreshSchema()
+ await this.refreshSchema()
events.send('database.import', file ? file.size : 0, {
from: file ? 'sqlite' : 'none',
diff --git a/src/lib/tab.js b/src/lib/tab.js
index f1ab8ee..154695b 100644
--- a/src/lib/tab.js
+++ b/src/lib/tab.js
@@ -22,6 +22,7 @@ export default class Tab {
table: 'bottom',
dataView: 'hidden'
}
+ this.maximize = inquiry.maximize
this.isSaved = !!inquiry.id
this.state = state
diff --git a/src/router.js b/src/router.js
index b2f4c25..4832d23 100644
--- a/src/router.js
+++ b/src/router.js
@@ -4,6 +4,7 @@ import Workspace from '@/views/Main/Workspace'
import Inquiries from '@/views/Main/Inquiries'
import Welcome from '@/views/Welcome'
import Main from '@/views/Main'
+import LoadView from '@/views/LoadView'
import store from '@/store'
import database from '@/lib/database'
@@ -31,6 +32,11 @@ const routes = [
component: Inquiries
}
]
+ },
+ {
+ path: '/load',
+ name: 'Load',
+ component: LoadView
}
]
@@ -39,7 +45,7 @@ const router = new VueRouter({
})
router.beforeEach(async (to, from, next) => {
- if (!store.state.db) {
+ if (!store.state.db && to.name !== 'Load') {
const newDb = database.getNewDatabase()
await newDb.loadDb()
store.commit('setDb', newDb)
diff --git a/src/views/LoadView.vue b/src/views/LoadView.vue
new file mode 100644
index 0000000..872ed0c
--- /dev/null
+++ b/src/views/LoadView.vue
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/Main/Workspace/Tabs/Tab/index.vue b/src/views/Main/Workspace/Tabs/Tab/index.vue
index 333e469..2a98dac 100644
--- a/src/views/Main/Workspace/Tabs/Tab/index.vue
+++ b/src/views/Main/Workspace/Tabs/Tab/index.vue
@@ -3,8 +3,9 @@
@@ -70,6 +71,13 @@ export default {
Splitpanes,
Teleport
},
+ data () {
+ return {
+ topPaneSize: this.tab.maximize
+ ? this.tab.layout[this.tab.maximize] === 'above' ? 100 : 0
+ : 50
+ }
+ },
computed: {
isActive () {
return this.tab.id === this.$store.state.currentTabId
diff --git a/src/views/Main/Workspace/index.vue b/src/views/Main/Workspace/index.vue
index 0d543e8..5ee54c1 100644
--- a/src/views/Main/Workspace/index.vue
+++ b/src/views/Main/Workspace/index.vue
@@ -2,8 +2,9 @@
@@ -28,6 +29,11 @@ export default {
Splitpanes,
Tabs
},
+ data () {
+ return {
+ schemaWidth: this.$route.query.hide_schema === '1' ? 0 : 20
+ }
+ },
async beforeCreate () {
const schema = this.$store.state.db.schema
if (!schema || schema.length === 0) {
diff --git a/tests/components/Splitpanes/Splitpanes.spec.js b/tests/components/Splitpanes/Splitpanes.spec.js
index 94524ea..8fb4580 100644
--- a/tests/components/Splitpanes/Splitpanes.spec.js
+++ b/tests/components/Splitpanes/Splitpanes.spec.js
@@ -40,7 +40,7 @@ describe('Splitpanes.vue', () => {
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.height).to.equal('40%')
})
- it('toggles correctly', async () => {
+ it('toggles correctly - no maximized initially', async () => {
// mount the component
const wrapper = shallowMount(Splitpanes, {
slots: {
@@ -70,6 +70,64 @@ describe('Splitpanes.vue', () => {
expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('40%')
})
+ it('toggles correctly - with maximized initially', async () => {
+ // mount the component
+ let wrapper = shallowMount(Splitpanes, {
+ slots: {
+ leftPane: '',
+ rightPane: ''
+ },
+ propsData: {
+ before: { size: 0, max: 100 },
+ after: { size: 100, max: 100 },
+ default: { before: 20, after: 80 }
+ }
+ })
+
+ await wrapper.find('.toggle-btn').trigger('click')
+ expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('20%')
+ expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('80%')
+
+ await wrapper.findAll('.toggle-btn').at(0).trigger('click')
+ expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('0%')
+ expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('100%')
+
+ await wrapper.find('.toggle-btn').trigger('click')
+ expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('20%')
+ expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('80%')
+
+ await wrapper.findAll('.toggle-btn').at(1).trigger('click')
+ expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('100%')
+ expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('0%')
+
+ wrapper = shallowMount(Splitpanes, {
+ slots: {
+ leftPane: '',
+ rightPane: ''
+ },
+ propsData: {
+ before: { size: 100, max: 100 },
+ after: { size: 0, max: 100 }
+ }
+ })
+
+ await wrapper.find('.toggle-btn').trigger('click')
+ expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('50%')
+ expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('50%')
+
+ await wrapper.findAll('.toggle-btn').at(0).trigger('click')
+ expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('0%')
+ expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('100%')
+
+ await wrapper.find('.toggle-btn').trigger('click')
+ expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('50%')
+ expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('50%')
+
+ await wrapper.findAll('.toggle-btn').at(1).trigger('click')
+ expect(wrapper.findAll('.splitpanes-pane').at(0).element.style.width).to.equal('100%')
+ expect(wrapper.findAll('.splitpanes-pane').at(1).element.style.width).to.equal('0%')
+ })
+
it('drag - vertical', async () => {
const root = document.createElement('div')
const place = document.createElement('div')
diff --git a/tests/store/mutations.spec.js b/tests/store/mutations.spec.js
index 4300730..cf05502 100644
--- a/tests/store/mutations.spec.js
+++ b/tests/store/mutations.spec.js
@@ -283,6 +283,7 @@ describe('mutations', () => {
it('setCurrentTabId', () => {
const state = {
+ tabs: [{ id: 1 }, { id: 2 }],
currentTabId: 1
}
diff --git a/tests/views/LoadView.spec.js b/tests/views/LoadView.spec.js
new file mode 100644
index 0000000..4e52a49
--- /dev/null
+++ b/tests/views/LoadView.spec.js
@@ -0,0 +1,147 @@
+import { expect } from 'chai'
+import sinon from 'sinon'
+import { mount } from '@vue/test-utils'
+import Vuex from 'vuex'
+import LoadView from '@/views/LoadView'
+import fu from '@/lib/utils/fileIo'
+import database from '@/lib/database'
+import realMutations from '@/store/mutations'
+import realActions from '@/store/actions'
+import flushPromises from 'flush-promises'
+import Tab from '@/lib/tab'
+
+describe('LoadView.vue', () => {
+ afterEach(() => {
+ sinon.restore()
+ })
+
+ it('Loads db and inquiries and redirects to workspace if no errors', async () => {
+ const state = {
+ tabs: []
+ }
+ const mutations = {
+ setCurrentTabId: sinon.stub().callsFake(realMutations.setCurrentTabId),
+ setDb: sinon.stub().callsFake(realMutations.setDb)
+ }
+ const actions = {
+ addTab: sinon.stub().callsFake(realActions.addTab)
+ }
+ const store = new Vuex.Store({ state, mutations, actions })
+ const $route = {
+ path: '/workspace',
+ query: {
+ data_url: 'https://my-url/test.db',
+ data_format: 'sqlite',
+ inquiry_url: 'https://my-url/test_inquiries.json',
+ inquiry_id: [1],
+ maximize: 'dataView'
+ }
+ }
+
+ const $router = { push: sinon.stub() }
+
+ const readFile = sinon.stub(fu, 'readFile')
+ const dataRes = new Response()
+ dataRes.blob = sinon.stub().resolves({})
+ readFile.onCall(0).returns(Promise.resolve(dataRes))
+
+ const inquiriesRes = new Response()
+ inquiriesRes.json = sinon.stub().resolves({
+ version: 2,
+ inquiries: [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }]
+ })
+ readFile.onCall(1).returns(Promise.resolve(inquiriesRes))
+ const db = {
+ loadDb: sinon.stub().resolves()
+ }
+ sinon.stub(database, 'getNewDatabase').returns(db)
+ Tab.prototype.execute = sinon.stub()
+
+ const wrapper = mount(LoadView, {
+ store,
+ mocks: { $route, $router },
+ stubs: ['router-link']
+ })
+
+ await flushPromises()
+
+ // DB file is read
+ expect(fu.readFile.firstCall.args[0]).to.equal('https://my-url/test.db')
+
+ // Db is loaded
+ expect(db.loadDb.firstCall.args[0]).to.equal(await dataRes.blob.returnValues[0])
+
+ // Inquiries file is read
+ expect(fu.readFile.secondCall.args[0])
+ .to.equal('https://my-url/test_inquiries.json')
+
+ // Tab for inquiry is created
+ expect(actions.addTab.calledOnce).to.equal(true)
+ expect(actions.addTab.firstCall.args[1]).eql({
+ id: undefined,
+ name: 'foo',
+ layout: {
+ dataView: 'bottom',
+ sqlEditor: 'hidden',
+ table: 'above'
+ },
+ maximize: 'dataView'
+ })
+ const executedTab = Tab.prototype.execute.firstCall.thisValue
+ expect(executedTab.tempName).to.equal('foo')
+ expect(wrapper.find('#open-workspace-btn').exists()).to.equal(false)
+ expect($router.push.called).to.equal(true)
+ })
+
+ it('Doesn\'t redirect and show the button if there is an error', async () => {
+ const state = {
+ tabs: []
+ }
+ const mutations = {
+ setCurrentTabId: sinon.stub().callsFake(realMutations.setCurrentTabId),
+ setDb: sinon.stub().callsFake(realMutations.setDb)
+ }
+ const actions = {
+ addTab: sinon.stub().callsFake(realActions.addTab)
+ }
+ const store = new Vuex.Store({ state, mutations, actions })
+ const $route = {
+ path: '/workspace',
+ query: {
+ data_url: 'https://my-url/test.db',
+ data_format: 'sqlite',
+ inquiry_url: 'https://my-url/test_inquiries.json',
+ inquiry_id: [1],
+ maximize: 'dataView'
+ }
+ }
+
+ const $router = { push: sinon.stub() }
+
+ const readFile = sinon.stub(fu, 'readFile')
+ const dataRes = new Response()
+ dataRes.blob = sinon.stub().rejects(new Error('Something is wrong'))
+ readFile.onCall(0).returns(Promise.resolve(dataRes))
+
+ const inquiriesRes = new Response()
+ inquiriesRes.json = sinon.stub().resolves({
+ version: 2,
+ inquiries: [{ id: 1 }]
+ })
+ readFile.onCall(1).returns(Promise.resolve(inquiriesRes))
+ sinon.stub(database, 'getNewDatabase').returns({
+ loadDb: sinon.stub().resolves()
+ })
+
+ const wrapper = mount(LoadView, {
+ store,
+ mocks: { $route, $router },
+ stubs: ['router-link']
+ })
+
+ await flushPromises()
+ expect(wrapper.find('#open-workspace-btn').exists()).to.equal(true)
+ expect($router.push.called).to.equal(false)
+ expect(wrapper.find('#logs').text()).to.include('Something is wrong')
+ })
+})
diff --git a/tests/views/Main/Inquiries/Inquiries.spec.js b/tests/views/Main/Inquiries/Inquiries.spec.js
index 5cbb6f8..570d350 100644
--- a/tests/views/Main/Inquiries/Inquiries.spec.js
+++ b/tests/views/Main/Inquiries/Inquiries.spec.js
@@ -19,7 +19,9 @@ describe('Inquiries.vue', () => {
predefinedInquiries: []
}
const mutations = {
- updatePredefinedInquiries: sinon.stub()
+ setPredefinedInquiriesLoaded: sinon.stub(),
+ updatePredefinedInquiries: sinon.stub(),
+ setLoadingPredefinedInquiries: sinon.stub()
}
const store = new Vuex.Store({ state, mutations })
const wrapper = shallowMount(Inquiries, { store })
@@ -327,6 +329,7 @@ describe('Inquiries.vue', () => {
sinon.stub(storedInquiries, 'getStoredInquiries').returns([inquiryInStorage])
const state = {
+ tabs: [],
predefinedInquiries: []
}
const actions = { addTab: sinon.stub().resolves(1) }
diff --git a/tests/views/Main/Workspace/Tabs/Tab/Tab.spec.js b/tests/views/Main/Workspace/Tabs/Tab/Tab.spec.js
index d533031..2709523 100644
--- a/tests/views/Main/Workspace/Tabs/Tab/Tab.spec.js
+++ b/tests/views/Main/Workspace/Tabs/Tab/Tab.spec.js
@@ -406,4 +406,119 @@ describe('Tab.vue', () => {
expect(wrapper.find('.above .sql-editor-panel').exists()).to.equal(true)
expect(wrapper.find('.bottomPane .run-result-panel').exists()).to.equal(true)
})
+
+ it('Maximize top panel if maximized panel is above', () => {
+ const state = {
+ currentTabId: 1
+ }
+ const store = new Vuex.Store({ state, mutations })
+ const tab = {
+ id: 1,
+ name: 'foo',
+ query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
+ viewType: 'chart',
+ viewOptions: {},
+ layout: {
+ sqlEditor: 'above',
+ table: 'bottom',
+ dataView: 'hidden'
+ },
+ maximize: 'sqlEditor',
+ isPredefined: false,
+ result: null,
+ isGettingResults: false,
+ error: null,
+ time: 0,
+ isSaved: true
+ }
+
+ const wrapper = mount(Tab, {
+ attachTo: place,
+ store,
+ stubs: ['chart'],
+ propsData: {
+ tab
+ }
+ })
+
+ expect(wrapper.find('.above').element.parentElement.style.height)
+ .to.equal('100%')
+ })
+
+ it('Maximize bottom panel if maximized panel is below', () => {
+ const state = {
+ currentTabId: 1
+ }
+ const store = new Vuex.Store({ state, mutations })
+ const tab = {
+ id: 1,
+ name: 'foo',
+ query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
+ viewType: 'chart',
+ viewOptions: {},
+ layout: {
+ sqlEditor: 'above',
+ table: 'bottom',
+ dataView: 'hidden'
+ },
+ maximize: 'table',
+ isPredefined: false,
+ result: null,
+ isGettingResults: false,
+ error: null,
+ time: 0,
+ isSaved: true
+ }
+
+ const wrapper = mount(Tab, {
+ attachTo: place,
+ store,
+ stubs: ['chart'],
+ propsData: {
+ tab
+ }
+ })
+
+ expect(wrapper.find('.bottomPane').element.parentElement.style.height)
+ .to.equal('100%')
+ })
+
+ it('Panel size is 50 is nothing to maximize', () => {
+ const state = {
+ currentTabId: 1
+ }
+ const store = new Vuex.Store({ state, mutations })
+ const tab = {
+ id: 1,
+ name: 'foo',
+ query: 'SELECT * FROM foo; CREATE TABLE bar(a,b);',
+ viewType: 'chart',
+ viewOptions: {},
+ layout: {
+ sqlEditor: 'above',
+ table: 'bottom',
+ dataView: 'hidden'
+ },
+ isPredefined: false,
+ result: null,
+ isGettingResults: false,
+ error: null,
+ time: 0,
+ isSaved: true
+ }
+
+ const wrapper = mount(Tab, {
+ attachTo: place,
+ store,
+ stubs: ['chart'],
+ propsData: {
+ tab
+ }
+ })
+
+ expect(wrapper.find('.above').element.parentElement.style.height)
+ .to.equal('50%')
+ expect(wrapper.find('.bottomPane').element.parentElement.style.height)
+ .to.equal('50%')
+ })
})
diff --git a/tests/views/Main/Workspace/Workspace.spec.js b/tests/views/Main/Workspace/Workspace.spec.js
index 0668f0f..a281eeb 100644
--- a/tests/views/Main/Workspace/Workspace.spec.js
+++ b/tests/views/Main/Workspace/Workspace.spec.js
@@ -12,9 +12,11 @@ describe('Workspace.vue', () => {
tabs: []
}
const store = new Vuex.Store({ state, actions, mutations })
+ const $route = { path: '/workspace', query: {} }
mount(Workspace, {
store,
- stubs: ['router-link']
+ stubs: ['router-link'],
+ mocks: { $route }
})
expect(state.tabs[0].query).to.include('Your database is empty.')
@@ -24,4 +26,20 @@ describe('Workspace.vue', () => {
expect(state.tabs[0].viewOptions).to.equal(undefined)
expect(state.tabs[0].isSaved).to.equal(false)
})
+
+ it('Collapse schema if hide_schema is 1', () => {
+ const state = {
+ db: {},
+ tabs: []
+ }
+ const store = new Vuex.Store({ state, actions, mutations })
+ const $route = { path: '/workspace', query: { hide_schema: '1' } }
+ const vm = mount(Workspace, {
+ store,
+ stubs: ['router-link'],
+ mocks: { $route }
+ })
+
+ expect(vm.find('#schema-container').element.offsetWidth).to.equal(0)
+ })
})