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

11 Commits

Author SHA1 Message Date
lana-k
9b3dda6cff delete .nojekyll from master 2021-05-05 23:43:51 +02:00
lana-k
d94604ebfb Merge branch 'master' of github.com:lana-k/sqliteviz 2021-05-05 23:31:17 +02:00
lana-k
16868ef430 add clean-exclude 2021-05-05 23:30:33 +02:00
lana-k
b162c7043e Create .nojekyll 2021-05-05 23:20:46 +02:00
lana-k
8e856063b8 allow files with underscores on GitHub pages 2021-05-05 23:12:47 +02:00
lana-k
8684b4cef9 remove console.log 2021-05-05 21:46:29 +02:00
lana-k
bcaebd4840 Create an empty database #44 2021-05-05 21:44:44 +02:00
lana-k
4619461af8 change period format 2021-05-05 15:08:54 +02:00
lana-k
9fff1d699a update sql.js #43 2021-05-05 15:08:12 +02:00
lana-k
5ab19c3fae show hint in codemirror on Ctrl+Space 2021-05-04 16:33:37 +02:00
lana-k
cc483f4720 change code structure 2021-05-04 14:13:58 +02:00
78 changed files with 498 additions and 505 deletions

View File

@@ -32,9 +32,11 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.6.2
uses: JamesIves/github-pages-deploy-action@4.1.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: build # The branch the action should deploy to.
FOLDER: dist/ # The folder the action should deploy.
CLEAN: false # Automatically remove deleted files from the deploy branch
token: ${{ secrets.GITHUB_TOKEN }}
branch: build # The branch the action should deploy to.
folder: dist/ # The folder the action should deploy.
clean: true # Automatically remove deleted files from the deploy branch
clean-exclude: .nojekyll

View File

@@ -141,7 +141,7 @@ module.exports = function (config) {
]
},
{
test: /\.worker\.js$/,
test: /worker\.js$/,
loader: 'worker-loader'
},
{

14
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"react": "^16.13.1",
"react-chart-editor": "^0.42.0",
"react-dom": "^16.13.1",
"sql.js": "^1.3.0",
"sql.js": "^1.5.0",
"sqlite-parser": "^1.0.1",
"vue": "^2.6.11",
"vue-codemirror": "^4.0.6",
@@ -19633,9 +19633,9 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"node_modules/sql.js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.3.0.tgz",
"integrity": "sha512-bxrJ/9rqJ2SA6hpHnSodRjKBugZHewRvNTITTt74W1VZWmzODjdS68yQW0/J9oC0NWKylHEtV1ptkoTyOYO4Tw=="
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.5.0.tgz",
"integrity": "sha512-Qqr6HgX/hCDpLFWdN0BNoNpYQ2c1tOl1c3HGI0cshjaFSAWszKICuLZ9CyFUvRFPpEGW8RzHzwuXWWvXVGTKBg=="
},
"node_modules/sqlite-parser": {
"version": "1.0.1",
@@ -40491,9 +40491,9 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sql.js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.3.0.tgz",
"integrity": "sha512-bxrJ/9rqJ2SA6hpHnSodRjKBugZHewRvNTITTt74W1VZWmzODjdS68yQW0/J9oC0NWKylHEtV1ptkoTyOYO4Tw=="
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.5.0.tgz",
"integrity": "sha512-Qqr6HgX/hCDpLFWdN0BNoNpYQ2c1tOl1c3HGI0cshjaFSAWszKICuLZ9CyFUvRFPpEGW8RzHzwuXWWvXVGTKBg=="
},
"sqlite-parser": {
"version": "1.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "sqliteviz",
"version": "1.0.0",
"version": "0.11.0",
"license": "Apache-2.0",
"private": true,
"scripts": {
@@ -20,7 +20,7 @@
"react": "^16.13.1",
"react-chart-editor": "^0.42.0",
"react-dom": "^16.13.1",
"sql.js": "^1.3.0",
"sql.js": "^1.5.0",
"sqlite-parser": "^1.0.1",
"vue": "^2.6.11",
"vue-codemirror": "^4.0.6",

View File

@@ -60,4 +60,7 @@ button,
body {
margin: 0;
}
.CodeMirror-hints {
z-index: 999 !important;
}
</style>

View File

@@ -40,7 +40,7 @@
</template>
<script>
import ascii from '@/ascii'
import ascii from './ascii'
import DropDownChevron from '@/components/svg/dropDownChevron'
import ClearIcon from '@/components/svg/clear'

View File

@@ -1,7 +1,7 @@
<template>
<div class="db-uploader-container" :style="{ width }">
<change-db-icon v-if="type === 'small'" @click.native="browse"/>
<div v-if="['regular', 'illustrated'].includes(type)" class="drop-area-container">
<div v-if="type === 'illustrated'" class="drop-area-container">
<div
class="drop-area"
@dragover.prevent="state = 'dragover'"
@@ -127,17 +127,17 @@
</template>
<script>
import fu from '@/file.utils'
import csv from '@/csv'
import fu from '@/lib/utils/fileIo'
import csv from './csv'
import CloseIcon from '@/components/svg/close'
import TextField from '@/components/TextField'
import DelimiterSelector from '@/components/DelimiterSelector'
import DelimiterSelector from './DelimiterSelector'
import CheckBox from '@/components/CheckBox'
import SqlTable from '@/components/SqlTable'
import Logs from '@/components/Logs'
import ChangeDbIcon from '@/components/svg/changeDb'
import time from '@/time'
import database from '@/database'
import time from '@/lib/utils/time'
import database from '@/lib/database'
const csvMimeTypes = [
'text/csv',
@@ -154,9 +154,9 @@ export default {
type: {
type: String,
required: false,
default: 'regular',
default: 'small',
validator: (value) => {
return ['regular', 'illustrated', 'small'].includes(value)
return ['illustrated', 'small'].includes(value)
}
},
width: {
@@ -231,7 +231,14 @@ export default {
this.$store.commit('saveSchema', this.schema)
if (this.importCsvCompleted) {
this.$modal.hide('parse')
const tabId = await this.$store.dispatch('addTab', { query: 'select * from csv_import' })
const stmt = [
'/*',
' * Your CSV file has been imported into csv_import table.',
' * You can run this SQL query to make all CSV records available for charting.',
' */',
'SELECT * FROM csv_import'
].join('\n')
const tabId = await this.$store.dispatch('addTab', { query: stmt })
this.$store.commit('setCurrentTabId', tabId)
this.importCsvCompleted = false
}
@@ -341,7 +348,7 @@ export default {
// Create db with csv table and get schema
const name = file.name.replace(/\.[^.]+$/, '')
start = new Date()
this.schema = await this.newDb.createDb(name, parseResult.data, progressCounterId)
this.schema = await this.newDb.importDb(name, parseResult.data, progressCounterId)
end = new Date()
// Inform about import success
@@ -432,6 +439,7 @@ export default {
align-items: center;
justify-content: center;
height: 100%;
cursor: pointer;
}
#img-container {

View File

@@ -65,7 +65,7 @@
</template>
<script>
import splitter from '@/splitter'
import splitter from './splitter'
export default {
name: 'Splitpanes',

View File

@@ -48,7 +48,7 @@
</template>
<script>
import Pager from '@/components/Pager'
import Pager from './Pager'
export default {
name: 'SqlTable',

View File

@@ -16,13 +16,13 @@
/>
</svg>
<span class="icon-tooltip" :style="tooltipStyle">
Change database
Load another database or CSV
</span>
</div>
</template>
<script>
import tooltipMixin from '@/mixins/tooltips'
import tooltipMixin from '@/tooltipMixin'
export default {
name: 'changeDbIcon',

View File

@@ -23,7 +23,7 @@
</template>
<script>
import tooltipMixin from '@/mixins/tooltips'
import tooltipMixin from '@/tooltipMixin'
export default {
name: 'ExportIcon',

View File

@@ -20,7 +20,7 @@
</template>
<script>
import tooltipMixin from '@/mixins/tooltips'
import tooltipMixin from '@/tooltipMixin'
export default {
name: 'HintIcon',

View File

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

View File

@@ -1,5 +1,5 @@
import registerPromiseWorker from 'promise-worker/register'
import Sql from '@/sql'
import Sql from './_sql'
const sqlReady = Sql.build()

View File

@@ -1,8 +1,8 @@
import sqliteParser from 'sqlite-parser'
import fu from '@/file.utils'
import fu from '@/lib/utils/fileIo'
// We can import workers like so because of worker-loader:
// https://webpack.js.org/loaders/worker-loader/
import Worker from '@/db.worker.js'
import Worker from './_worker.js'
// Use promise-worker in order to turn worker into the promise based one:
// https://github.com/nolanlawson/promise-worker
@@ -50,7 +50,7 @@ class Database {
delete this.importProgresses[id]
}
async createDb (name, data, progressCounterId) {
async importDb (name, data, progressCounterId) {
const result = await this.pw.postMessage({
action: 'import',
columns: data.columns,
@@ -66,14 +66,15 @@ class Database {
}
async loadDb (file) {
const fileContent = await fu.readAsArrayBuffer(file)
const fileContent = file ? await fu.readAsArrayBuffer(file) : null
const res = await this.pw.postMessage({ action: 'open', buffer: fileContent })
if (res.error) {
throw new Error(res.error)
}
return this.getSchema(file.name.replace(/\.[^.]+$/, ''))
const dbName = file ? file.name.replace(/\.[^.]+$/, '') : 'database'
return this.getSchema(dbName)
}
async getSchema (name) {
@@ -85,12 +86,14 @@ class Database {
const result = await this.execute(getSchemaSql)
// Parse DDL statements to get column names and types
const parsedSchema = []
result.values.forEach(item => {
parsedSchema.push({
name: item[0],
columns: getColumns(item[1])
if (result && result.values) {
result.values.forEach(item => {
parsedSchema.push({
name: item[0],
columns: getColumns(item[1])
})
})
})
}
// Return db name and schema
return {

View File

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

7
src/lib/utils/time.js Normal file
View File

@@ -0,0 +1,7 @@
export default {
getPeriod (start, end) {
const diff = end.getTime() - start.getTime()
const seconds = diff / 1000
return seconds.toFixed(3) + 's'
}
}

View File

@@ -1,7 +1,7 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import App from '@/App.vue'
import router from '@/router'
import store from '@/store'
import { VuePlugin } from 'vuera'
import VModal from 'vue-js-modal'
@@ -13,7 +13,7 @@ import '@/assets/styles/tooltips.css'
import '@/assets/styles/messages.css'
if (!['localhost', '127.0.0.1'].includes(location.hostname)) {
import('../registerServiceWorker') // eslint-disable-line no-unused-expressions
import('./registerServiceWorker') // eslint-disable-line no-unused-expressions
}
Vue.use(VuePlugin)

View File

@@ -1,9 +1,9 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Editor from '@/views/Editor'
import MyQueries from '@/views/MyQueries'
import Home from '@/views/Home'
import MainView from '@/views/MainView'
import Editor from '@/views/Main/Editor'
import MyQueries from '@/views/Main/MyQueries'
import Welcome from '@/views/Welcome'
import Main from '@/views/Main'
Vue.use(VueRouter)
@@ -11,12 +11,12 @@ const routes = [
{
path: '/',
name: 'Welcome',
component: Home
component: Welcome
},
{
path: '/',
name: 'MainView',
component: MainView,
name: 'Main',
component: Main,
children: [
{
path: '/editor',

30
src/store/actions.js Normal file
View File

@@ -0,0 +1,30 @@
import { nanoid } from 'nanoid'
export default {
async addTab ({ state }, data) {
const tab = data ? JSON.parse(JSON.stringify(data)) : {}
// If no data then create a new blank one...
// No data.id means to create new tab, but not blank,
// e.g. with 'select * from csv_import' query after csv import
if (!data || !data.id) {
tab.id = nanoid()
tab.name = null
tab.tempName = state.untitledLastIndex
? `Untitled ${state.untitledLastIndex}`
: 'Untitled'
tab.isUnsaved = true
} else {
tab.isUnsaved = false
}
// add new tab only if was not already opened
if (!state.tabs.some(openedTab => openedTab.id === tab.id)) {
state.tabs.push(tab)
if (!tab.name) {
state.untitledLastIndex += 1
}
}
return tab.id
}
}

View File

@@ -1,112 +1,11 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { nanoid } from 'nanoid'
import state from '@/store/state'
import mutations from '@/store/mutations'
import actions from '@/store/actions'
Vue.use(Vuex)
export const state = {
schema: null,
dbFile: null,
dbName: null,
tabs: [],
currentTab: null,
currentTabId: null,
untitledLastIndex: 0,
predefinedQueries: [],
db: null
}
export const mutations = {
setDb (state, db) {
if (state.db) {
state.db.shutDown()
}
state.db = db
},
saveSchema (state, { dbName, schema }) {
state.dbName = dbName
state.schema = schema
},
updateTab (state, { index, name, id, query, chart, isUnsaved }) {
const tab = state.tabs[index]
const oldId = tab.id
if (id && state.currentTabId === oldId) {
state.currentTabId = id
}
if (id) { tab.id = id }
if (name) { tab.name = name }
if (query) { tab.query = query }
if (chart) { tab.chart = chart }
if (isUnsaved !== undefined) { tab.isUnsaved = isUnsaved }
if (!isUnsaved) {
// Saved query is not predefined
delete tab.isPredefined
}
Vue.set(state.tabs, index, tab)
},
deleteTab (state, index) {
// If closing tab is the current opened
if (state.tabs[index].id === state.currentTabId) {
if (index < state.tabs.length - 1) {
state.currentTabId = state.tabs[index + 1].id
} else if (index > 0) {
state.currentTabId = state.tabs[index - 1].id
} else {
state.currentTabId = null
state.currentTab = null
state.untitledLastIndex = 0
}
}
state.tabs.splice(index, 1)
},
setCurrentTabId (state, id) {
state.currentTabId = id
},
setCurrentTab (state, tab) {
state.currentTab = tab
},
updatePredefinedQueries (state, queries) {
if (Array.isArray(queries)) {
state.predefinedQueries = queries
} else {
state.predefinedQueries = [queries]
}
}
}
export const actions = {
async addTab ({ state }, data) {
const tab = data ? JSON.parse(JSON.stringify(data)) : {}
// If no data then create a new blank one...
// No data.id means to create new tab, but not blank,
// e.g. with 'select * from csv_import' query after csv import
if (!data || !data.id) {
tab.id = nanoid()
tab.name = null
tab.tempName = state.untitledLastIndex
? `Untitled ${state.untitledLastIndex}`
: 'Untitled'
tab.isUnsaved = true
} else {
tab.isUnsaved = false
}
// add new tab only if was not already opened
if (!state.tabs.some(openedTab => openedTab.id === tab.id)) {
state.tabs.push(tab)
if (!tab.name) {
state.untitledLastIndex += 1
}
}
return tab.id
}
}
export default new Vuex.Store({
state,
mutations,

63
src/store/mutations.js Normal file
View File

@@ -0,0 +1,63 @@
import Vue from 'vue'
export default {
setDb (state, db) {
if (state.db) {
state.db.shutDown()
}
state.db = db
},
saveSchema (state, { dbName, schema }) {
state.dbName = dbName
state.schema = schema
},
updateTab (state, { index, name, id, query, chart, isUnsaved }) {
const tab = state.tabs[index]
const oldId = tab.id
if (id && state.currentTabId === oldId) {
state.currentTabId = id
}
if (id) { tab.id = id }
if (name) { tab.name = name }
if (query) { tab.query = query }
if (chart) { tab.chart = chart }
if (isUnsaved !== undefined) { tab.isUnsaved = isUnsaved }
if (!isUnsaved) {
// Saved query is not predefined
delete tab.isPredefined
}
Vue.set(state.tabs, index, tab)
},
deleteTab (state, index) {
// If closing tab is the current opened
if (state.tabs[index].id === state.currentTabId) {
if (index < state.tabs.length - 1) {
state.currentTabId = state.tabs[index + 1].id
} else if (index > 0) {
state.currentTabId = state.tabs[index - 1].id
} else {
state.currentTabId = null
state.currentTab = null
state.untitledLastIndex = 0
}
}
state.tabs.splice(index, 1)
},
setCurrentTabId (state, id) {
state.currentTabId = id
},
setCurrentTab (state, tab) {
state.currentTab = tab
},
updatePredefinedQueries (state, queries) {
if (Array.isArray(queries)) {
state.predefinedQueries = queries
} else {
state.predefinedQueries = [queries]
}
}
}

11
src/store/state.js Normal file
View File

@@ -0,0 +1,11 @@
export default {
schema: null,
dbFile: null,
dbName: null,
tabs: [],
currentTab: null,
currentTabId: null,
untitledLastIndex: 0,
predefinedQueries: [],
db: null
}

View File

@@ -1,36 +0,0 @@
export default {
getPeriod (start, end) {
let diff = end.getTime() - start.getTime()
let result = ''
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
diff -= days * (1000 * 60 * 60 * 24)
if (days) {
result += days + ' d '
}
const hours = Math.floor(diff / (1000 * 60 * 60))
diff -= hours * (1000 * 60 * 60)
if (hours) {
result += hours + ' h '
}
const mins = Math.floor(diff / (1000 * 60))
diff -= mins * (1000 * 60)
if (mins) {
result += mins + ' m '
}
const seconds = Math.floor(diff / (1000))
diff -= seconds * (1000)
if (seconds) {
result += seconds + ' s '
}
if (diff) {
result += diff + ' ms '
}
return result.replace(/\s$/, '')
}
}

View File

@@ -1,74 +0,0 @@
<template>
<div>
<splitpanes
class="schema-tabs-splitter"
:before="{ size: 20, max: 30 }"
:after="{ size: 80, max: 100 }"
>
<template #left-pane>
<schema v-if="$store.state.schema"/>
<div v-else id="empty-schema-container">
<div class="warning">
Database is not loaded. Queries cant be run without database.
</div>
<db-uploader id="db-uploader" width="100%"/>
</div>
</template>
<template #right-pane>
<tabs />
</template>
</splitpanes>
</div>
</template>
<script>
import Splitpanes from '@/components/Splitpanes'
import Schema from '@/components/Schema'
import Tabs from '@/components/Tabs'
import DbUploader from '@/components/DbUploader'
export default {
name: 'Editor',
components: {
Schema,
Splitpanes,
Tabs,
DbUploader
}
}
</script>
<style scoped>
.schema-tabs-splitter {
height: 100%;
background-color: var(--color-white);
}
#empty-schema-container {
display: flex;
flex-direction: column;
align-items: center;
min-width: 200px;
height: 100%;
}
#db-uploader {
flex-grow: 1;
padding: 24px;
width: 100%;
box-sizing: border-box;
}
.warning {
padding: 12px 24px;
width: 100%;
box-sizing: border-box;
}
>>>.drop-area {
padding: 0 15px;
}
>>>.drop-area .text {
max-width: 200px;
}
</style>

View File

@@ -5,7 +5,7 @@
</div>
<div id="db">
<div @click="schemaVisible = !schemaVisible" class="db-name">
<tree-chevron :expanded="schemaVisible"/>
<tree-chevron v-show="schema.length > 0" :expanded="schemaVisible"/>
{{ dbName }}
</div>
<db-uploader id="db-edit" type="small" />
@@ -23,7 +23,7 @@
</template>
<script>
import TableDescription from '@/components/TableDescription'
import TableDescription from './TableDescription'
import TextField from '@/components/TextField'
import TreeChevron from '@/components/svg/treeChevron'
import DbUploader from '@/components/DbUploader'

View File

@@ -28,7 +28,7 @@ import plotly from 'plotly.js/dist/plotly'
import 'react-chart-editor/lib/react-chart-editor.min.css'
import PlotlyEditor from 'react-chart-editor'
import chart from '@/chart'
import chartHelper from './chartHelper'
import dereference from 'react-chart-editor/lib/lib/dereference'
export default {
@@ -49,10 +49,10 @@ export default {
},
computed: {
dataSources () {
return chart.getDataSourcesFromSqlResult(this.sqlResult)
return chartHelper.getDataSourcesFromSqlResult(this.sqlResult)
},
dataSourceOptions () {
return chart.getOptionsFromDataSources(this.dataSources)
return chartHelper.getOptionsFromDataSources(this.dataSources)
}
},
watch: {
@@ -71,7 +71,7 @@ export default {
this.$emit('update')
},
getChartStateForSave () {
return chart.getChartStateForSave(this.state, this.dataSources)
return chartHelper.getChartStateForSave(this.state, this.dataSources)
}
}
}

View File

@@ -2,7 +2,6 @@ import CM from 'codemirror'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/sql-hint.js'
import store from '@/store'
import { debounce } from 'debounce'
export function getHints (cm, options) {
const token = cm.getTokenAt(cm.getCursor()).string.toUpperCase()
@@ -27,23 +26,25 @@ const hintOptions = {
},
get defaultTable () {
const schema = store.state.schema
return schema.length === 1 ? schema[0].name : null
return schema && schema.length === 1 ? schema[0].name : null
},
completeSingle: false,
completeOnSingleClick: true,
alignWithWord: false
}
export default {
show: debounce(function (editor) {
// Don't show autocomplete after a space or semicolon or in string literals
const token = editor.getTokenAt(editor.getCursor())
const ch = token.string.slice(-1)
const tokenType = token.type
if (tokenType === 'string' || !ch || ch === ' ' || ch === ';') {
return
}
CM.showHint(editor, getHints, hintOptions)
}, 400)
export function showHintOnDemand (editor) {
CM.showHint(editor, getHints, hintOptions)
}
export default function showHint (editor) {
// Don't show autocomplete after a space or semicolon or in string literals
const token = editor.getTokenAt(editor.getCursor())
const ch = token.string.slice(-1)
const tokenType = token.type
if (tokenType === 'string' || !ch || ch === ' ' || ch === ';') {
return
}
CM.showHint(editor, getHints, hintOptions)
}

View File

@@ -5,7 +5,8 @@
</template>
<script>
import hint from '@/hint'
import showHint, { showHintOnDemand } from './hint'
import { debounce } from 'debounce'
import { codemirror } from 'vue-codemirror'
import 'codemirror/lib/codemirror.css'
import 'codemirror/mode/sql/sql.js'
@@ -28,7 +29,8 @@ export default {
lineNumbers: true,
line: true,
autofocus: true,
autoRefresh: true
autoRefresh: true,
extraKeys: { 'Ctrl-Space': showHintOnDemand }
}
}
},
@@ -38,7 +40,7 @@ export default {
}
},
methods: {
onChange: hint.show
onChange: debounce(showHint, 400)
}
}
</script>

View File

@@ -50,10 +50,10 @@
<script>
import SqlTable from '@/components/SqlTable'
import SqlEditor from '@/components/SqlEditor'
import Splitpanes from '@/components/Splitpanes'
import ViewSwitcher from '@/components/ViewSwitcher'
import Chart from '@/components/Chart'
import SqlEditor from './SqlEditor'
import ViewSwitcher from './ViewSwitcher'
import Chart from './Chart'
export default {
name: 'Tab',

View File

@@ -62,7 +62,7 @@
</template>
<script>
import Tab from '@/components/Tab'
import Tab from './Tab'
import CloseIcon from '@/components/svg/close'
export default {

View File

@@ -0,0 +1,68 @@
<template>
<div>
<splitpanes
class="schema-tabs-splitter"
:before="{ size: 20, max: 30 }"
:after="{ size: 80, max: 100 }"
>
<template #left-pane>
<schema/>
</template>
<template #right-pane>
<tabs />
</template>
</splitpanes>
</div>
</template>
<script>
import Splitpanes from '@/components/Splitpanes'
import Schema from './Schema'
import Tabs from './Tabs'
import database from '@/lib/database'
import store from '@/store'
export default {
name: 'Editor',
components: {
Schema,
Splitpanes,
Tabs
},
async beforeRouteEnter (to, from, next) {
if (!store.state.schema) {
const newDb = database.getNewDatabase()
const newSchema = await newDb.loadDb()
store.commit('setDb', newDb)
store.commit('saveSchema', newSchema)
const stmt = [
'/*',
' * Your database is empty. In order to start building charts',
' * you should create a table and insert data into it.',
' */',
'CREATE TABLE house',
'(',
' name TEXT,',
' points INTEGER',
');',
'INSERT INTO house VALUES',
"('Gryffindor', 100),",
"('Hufflepuff', 90),",
"('Ravenclaw', 95),",
"('Slytherin', 80);"
].join('\n')
const tabId = await store.dispatch('addTab', { query: stmt })
store.commit('setCurrentTabId', tabId)
}
next()
}
}
</script>
<style scoped>
.schema-tabs-splitter {
height: 100%;
background-color: var(--color-white);
}
</style>

View File

@@ -62,7 +62,7 @@
<script>
import TextField from '@/components/TextField'
import CloseIcon from '@/components/svg/close'
import storedQueries from '@/storedQueries'
import storedQueries from '@/lib/storedQueries'
export default {
name: 'MainMenu',

View File

@@ -141,16 +141,16 @@
</template>
<script>
import RenameIcon from '@/components/svg/rename'
import CopyIcon from '@/components/svg/copy'
import RenameIcon from './svg/rename'
import CopyIcon from './svg/copy'
import ExportIcon from '@/components/svg/export'
import DeleteIcon from '@/components/svg/delete'
import DeleteIcon from './svg/delete'
import CloseIcon from '@/components/svg/close'
import TextField from '@/components/TextField'
import CheckBox from '@/components/CheckBox'
import tooltipMixin from '@/mixins/tooltips'
import storedQueries from '@/storedQueries'
import fu from '@/file.utils'
import tooltipMixin from '@/tooltipMixin'
import storedQueries from '@/lib/storedQueries'
import fu from '@/lib/utils/fileIo'
export default {
name: 'MyQueries',

View File

@@ -23,7 +23,7 @@
</template>
<script>
import tooltipMixin from '@/mixins/tooltips'
import tooltipMixin from '@/tooltipMixin'
export default {
name: 'CopyIcon',

View File

@@ -23,7 +23,7 @@
</template>
<script>
import tooltipMixin from '@/mixins/tooltips'
import tooltipMixin from '@/tooltipMixin'
export default {
name: 'DeleteIcon',

View File

@@ -23,7 +23,7 @@
</template>
<script>
import tooltipMixin from '@/mixins/tooltips'
import tooltipMixin from '@/tooltipMixin'
export default {
name: 'RenameIcon',

View File

@@ -8,11 +8,11 @@
</template>
<script>
import MainMenu from '@/components/MainMenu'
import MainMenu from './MainMenu'
import '@/assets/styles/scrollbars.css'
export default {
name: 'MainView',
name: 'Main',
components: { MainMenu }
}
</script>

View File

@@ -4,8 +4,8 @@
<div id="note">
Sqliteviz is fully client-side. Your database never leaves your computer.
</div>
<button id ="skip" class="secondary" @click="$router.push('/editor')">
Skip database loading
<button id="skip" class="secondary" @click="$router.push('/editor')">
Create empty database
</button>
</div>
</template>
@@ -14,7 +14,7 @@
import DbUploader from '@/components/DbUploader'
export default {
name: 'Home',
name: 'Welcome',
components: { DbUploader }
}
</script>

View File

@@ -2,15 +2,16 @@ import { expect } from 'chai'
import sinon from 'sinon'
import Vuex from 'vuex'
import { shallowMount, mount } from '@vue/test-utils'
import DbUploader from '@/components/DbUploader.vue'
import fu from '@/file.utils'
import database from '@/database'
import csv from '@/csv'
import DbUploader from '@/components/DbUploader'
import fu from '@/lib/utils/fileIo'
import database from '@/lib/database'
import csv from '@/components/DbUploader/csv'
describe('DbUploader.vue', () => {
let state = {}
let mutations = {}
let store = {}
let place
beforeEach(() => {
// mock store state and mutations
@@ -20,10 +21,14 @@ describe('DbUploader.vue', () => {
setDb: sinon.stub()
}
store = new Vuex.Store({ state, mutations })
place = document.createElement('div')
document.body.appendChild(place)
})
afterEach(() => {
sinon.restore()
place.remove()
})
it('loads db on click and redirects to /editor', async () => {
@@ -44,15 +49,22 @@ describe('DbUploader.vue', () => {
// mount the component
const wrapper = shallowMount(DbUploader, {
attachTo: place,
store,
mocks: { $router, $route }
mocks: { $router, $route },
propsData: {
type: 'illustrated'
}
})
await wrapper.find('.drop-area').trigger('click')
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
await db.loadDb.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
expect($router.push.calledOnceWith('/editor')).to.equal(true)
wrapper.destroy()
})
it('loads db on drop and redirects to /editor', async () => {
@@ -69,8 +81,12 @@ describe('DbUploader.vue', () => {
// mount the component
const wrapper = shallowMount(DbUploader, {
attachTo: place,
store,
mocks: { $router, $route }
mocks: { $router, $route },
propsData: {
type: 'illustrated'
}
})
// mock a file dropped by a user
@@ -84,8 +100,11 @@ describe('DbUploader.vue', () => {
await wrapper.find('.drop-area').trigger('drop', dropData)
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
await db.loadDb.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
expect($router.push.calledOnceWith('/editor')).to.equal(true)
wrapper.destroy()
})
it("doesn't redirect if already on /editor", async () => {
@@ -106,13 +125,20 @@ describe('DbUploader.vue', () => {
// mount the component
const wrapper = shallowMount(DbUploader, {
attachTo: place,
store,
mocks: { $router, $route }
mocks: { $router, $route },
propsData: {
type: 'illustrated'
}
})
await wrapper.find('.drop-area').trigger('click')
await db.loadDb.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
expect($router.push.called).to.equal(false)
wrapper.destroy()
})
})
@@ -122,6 +148,7 @@ describe('DbUploader.vue import CSV', () => {
let actions = {}
const newTabId = 1
let store = {}
let place
// mock router
const $router = { }
@@ -150,15 +177,24 @@ describe('DbUploader.vue import CSV', () => {
$router.push = sinon.stub()
place = document.createElement('div')
document.body.appendChild(place)
// mount the component
wrapper = mount(DbUploader, {
attachTo: place,
store,
mocks: { $router, $route }
mocks: { $router, $route },
propsData: {
type: 'illustrated'
}
})
})
afterEach(() => {
sinon.restore()
wrapper.destroy()
place.remove()
})
it('shows parse dialog if gets csv file', async () => {
@@ -184,6 +220,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
expect(wrapper.find('[data-modal="parse"]').exists()).to.equal(true)
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.value).to.equal('|')
expect(wrapper.find('#quote-char input').element.value).to.equal('"')
@@ -219,6 +256,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
parse.onCall(1).resolves({
delimiter: ',',
@@ -328,6 +366,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
let resolveParsing
parse.onCall(1).returns(new Promise(resolve => {
@@ -395,6 +434,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
@@ -452,6 +492,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
@@ -511,6 +552,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
@@ -563,7 +605,7 @@ describe('DbUploader.vue import CSV', () => {
let resolveImport = sinon.stub()
const newDb = {
createDb: sinon.stub().resolves(new Promise(resolve => { resolveImport = resolve })),
importDb: sinon.stub().resolves(new Promise(resolve => { resolveImport = resolve })),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub()
}
@@ -573,6 +615,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
@@ -598,11 +641,11 @@ describe('DbUploader.vue import CSV', () => {
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)
expect(newDb.createDb.getCall(0).args[0]).to.equal('foo') // file name
expect(newDb.importDb.getCall(0).args[0]).to.equal('foo') // file name
// After resolving - loading indicator is not shown
await resolveImport()
await newDb.createDb.returnValues[0]
await newDb.importDb.returnValues[0]
expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
).to.equal(false)
@@ -621,7 +664,7 @@ describe('DbUploader.vue import CSV', () => {
hasErrors: false,
messages: []
})
// we need to separate calles because messages will mutate
parse.onCall(1).resolves({
delimiter: '|',
data: {
@@ -637,7 +680,7 @@ describe('DbUploader.vue import CSV', () => {
const schema = {}
const newDb = {
createDb: sinon.stub().resolves(schema),
importDb: sinon.stub().resolves(schema),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub()
}
@@ -647,6 +690,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
@@ -681,7 +725,7 @@ describe('DbUploader.vue import CSV', () => {
hasErrors: false,
messages: []
})
// we need to separate calles because messages will mutate
parse.onCall(1).resolves({
delimiter: '|',
data: {
@@ -696,7 +740,7 @@ describe('DbUploader.vue import CSV', () => {
})
const newDb = {
createDb: sinon.stub().rejects(new Error('fail')),
importDb: sinon.stub().rejects(new Error('fail')),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub()
}
@@ -706,6 +750,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
@@ -729,8 +774,7 @@ describe('DbUploader.vue import CSV', () => {
})
it('import final', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
sinon.stub(csv, 'parse').resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
@@ -742,22 +786,9 @@ describe('DbUploader.vue import CSV', () => {
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),
importDb: sinon.stub().resolves(schema),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub()
}
@@ -767,6 +798,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
@@ -784,8 +816,7 @@ describe('DbUploader.vue import CSV', () => {
})
it('import cancel', async () => {
const parse = sinon.stub(csv, 'parse')
parse.onCall(0).resolves({
sinon.stub(csv, 'parse').resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
@@ -797,22 +828,9 @@ describe('DbUploader.vue import CSV', () => {
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),
importDb: sinon.stub().resolves(schema),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub(),
shutDown: sinon.stub()
@@ -823,6 +841,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
@@ -856,7 +875,7 @@ describe('DbUploader.vue import CSV', () => {
const schema = {}
const newDb = {
createDb: sinon.stub().resolves(schema),
importDb: sinon.stub().resolves(schema),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub(),
loadDb: sinon.stub().resolves()
@@ -867,6 +886,7 @@ describe('DbUploader.vue import CSV', () => {
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { mount, shallowMount } from '@vue/test-utils'
import DelimiterSelector from '@/components/DelimiterSelector'
import DelimiterSelector from '@/components/DbUploader/DelimiterSelector'
describe('DelimiterSelector', async () => {
it('shows the name of value', async () => {

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai'
import sinon from 'sinon'
import csv from '@/csv'
import csv from '@/components/DbUploader/csv'
import Papa from 'papaparse'
describe('csv.js', () => {

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import LoadingIndicator from '@/components/LoadingIndicator.vue'
import LoadingIndicator from '@/components/LoadingIndicator'
describe('LoadingIndicator.vue', () => {
it('Calculates animation class', async () => {

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import Logs from '@/components/Logs.vue'
import Logs from '@/components/Logs'
let place
describe('Logs.vue', () => {

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import Splitpanes from '@/components/Splitpanes.vue'
import Splitpanes from '@/components/Splitpanes'
describe('Splitpanes.vue', () => {
it('renders correctly - vertical', () => {

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai'
import sinon from 'sinon'
import splitter from '@/splitter'
import splitter from '@/components/Splitpanes/splitter'
describe('splitter.js', () => {
afterEach(() => {

View File

@@ -1,7 +1,7 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { mount } from '@vue/test-utils'
import Pager from '@/components/Pager.vue'
import Pager from '@/components/SqlTable/Pager'
describe('Pager.vue', () => {
afterEach(() => {

View File

@@ -2,14 +2,14 @@ import chai from 'chai'
import sinon from 'sinon'
import chaiAsPromised from 'chai-as-promised'
import initSqlJs from 'sql.js'
import Sql from '@/sql'
import Sql from '@/lib/database/_sql'
chai.use(chaiAsPromised)
const expect = chai.expect
chai.should()
const getSQL = initSqlJs()
describe('sql.js', () => {
describe('_sql.js', () => {
afterEach(() => {
sinon.restore()
})

View File

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

View File

@@ -2,8 +2,8 @@ import chai from 'chai'
import sinon from 'sinon'
import chaiAsPromised from 'chai-as-promised'
import initSqlJs from 'sql.js'
import database from '@/database'
import fu from '@/file.utils'
import database from '@/lib/database'
import fu from '@/lib/utils/fileIo'
chai.use(chaiAsPromised)
const expect = chai.expect
@@ -146,7 +146,7 @@ describe('database.js', () => {
}
const progressHandler = sinon.spy()
const progressCounterId = db.createProgressCounter(progressHandler)
const { dbName, schema } = await db.createDb('foo', data, progressCounterId)
const { dbName, schema } = await db.importDb('foo', data, progressCounterId)
expect(dbName).to.equal('foo')
expect(schema).to.have.lengthOf(1)
expect(schema[0].name).to.equal('csv_import')
@@ -164,7 +164,7 @@ describe('database.js', () => {
expect(progressHandler.secondCall.calledWith(100)).to.equal(true)
})
it('createDb throws errors', async () => {
it('importDb throws errors', async () => {
const data = {
columns: ['id', 'name'],
values: [
@@ -174,7 +174,7 @@ describe('database.js', () => {
}
const progressHandler = sinon.stub()
const progressCounterId = db.createProgressCounter(progressHandler)
await expect(db.createDb('foo', data, progressCounterId))
await expect(db.importDb('foo', data, progressCounterId))
.to.be.rejectedWith('column index out of range')
})

View File

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

View File

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

View File

@@ -1,23 +1,23 @@
import { expect } from 'chai'
import time from '@/time'
import time from '@/lib/utils/time'
describe('time.js', () => {
it('getPeriod', () => {
// 1.01.2021 13:00:00 000
let start = new Date(2021, 0, 1, 13, 0, 0, 0)
// 3.01.2021 22:15:20 500
let end = new Date(2021, 0, 3, 22, 15, 20, 500)
// 1.01.2021 13:01:00 500
let end = new Date(2021, 0, 1, 13, 1, 0, 500)
expect(time.getPeriod(start, end)).to.equal('2 d 9 h 15 m 20 s 500 ms')
expect(time.getPeriod(start, end)).to.equal('60.500s')
// 1.01.2021 13:00:00 000
start = new Date(2021, 0, 1, 13, 0, 0, 0)
// 1.01.2021 22:00:20 000
end = new Date(2021, 0, 1, 22, 0, 20, 0)
// 1.01.2021 13:00:20 500
end = new Date(2021, 0, 1, 13, 0, 20, 500)
expect(time.getPeriod(start, end)).to.equal('9 h 20 s')
expect(time.getPeriod(start, end)).to.equal('20.500s')
// 1.01.2021 13:00:00 000
start = new Date(2021, 0, 1, 13, 0, 0, 0)
@@ -25,6 +25,6 @@ describe('time.js', () => {
// 1.01.2021 13:00:00 45
end = new Date(2021, 0, 1, 13, 0, 0, 45)
expect(time.getPeriod(start, end)).to.equal('45 ms')
expect(time.getPeriod(start, end)).to.equal('0.045s')
})
})

View File

@@ -0,0 +1,67 @@
import { expect } from 'chai'
import actions from '@/store/actions'
const { addTab } = actions
describe('actions', () => {
it('addTab adds new blank tab', async () => {
const state = {
tabs: [],
untitledLastIndex: 0
}
const id = await addTab({ state })
expect(state.tabs[0].id).to.eql(id)
expect(state.tabs[0].name).to.eql(null)
expect(state.tabs[0].tempName).to.eql('Untitled')
expect(state.tabs[0].isUnsaved).to.eql(true)
expect(state.untitledLastIndex).to.equal(1)
})
it('addTab adds tab from saved queries', async () => {
const state = {
tabs: [],
untitledLastIndex: 0
}
const tab = {
id: 1,
name: 'test',
tempName: null,
query: 'SELECT * from foo',
chart: {},
isUnsaved: false
}
await addTab({ state }, tab)
expect(state.tabs[0]).to.eql(tab)
expect(state.untitledLastIndex).to.equal(0)
})
it("addTab doesn't add anything when the query is already opened", async () => {
const tab1 = {
id: 1,
name: 'test',
tempName: null,
query: 'SELECT * from foo',
chart: {},
isUnsaved: false
}
const tab2 = {
id: 2,
name: 'bar',
tempName: null,
query: 'SELECT * from bar',
chart: {},
isUnsaved: false
}
const state = {
tabs: [tab1, tab2],
untitledLastIndex: 0
}
await addTab({ state }, tab1)
expect(state.tabs).to.have.lengthOf(2)
expect(state.untitledLastIndex).to.equal(0)
})
})

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { mutations, actions } from '@/store'
import mutations from '@/store/mutations'
const {
saveSchema,
updateTab,
@@ -11,8 +11,6 @@ const {
setDb
} = mutations
const { addTab } = actions
describe('mutations', () => {
it('setDb', () => {
const state = {
@@ -376,66 +374,3 @@ describe('mutations', () => {
expect(state.predefinedQueries).to.eql(queries)
})
})
describe('actions', () => {
it('addTab adds new blank tab', async () => {
const state = {
tabs: [],
untitledLastIndex: 0
}
const id = await addTab({ state })
expect(state.tabs[0].id).to.eql(id)
expect(state.tabs[0].name).to.eql(null)
expect(state.tabs[0].tempName).to.eql('Untitled')
expect(state.tabs[0].isUnsaved).to.eql(true)
expect(state.untitledLastIndex).to.equal(1)
})
it('addTab adds tab from saved queries', async () => {
const state = {
tabs: [],
untitledLastIndex: 0
}
const tab = {
id: 1,
name: 'test',
tempName: null,
query: 'SELECT * from foo',
chart: {},
isUnsaved: false
}
await addTab({ state }, tab)
expect(state.tabs[0]).to.eql(tab)
expect(state.untitledLastIndex).to.equal(0)
})
it("addTab doesn't add anything when the query is already opened", async () => {
const tab1 = {
id: 1,
name: 'test',
tempName: null,
query: 'SELECT * from foo',
chart: {},
isUnsaved: false
}
const tab2 = {
id: 2,
name: 'bar',
tempName: null,
query: 'SELECT * from bar',
chart: {},
isUnsaved: false
}
const state = {
tabs: [tab1, tab2],
untitledLastIndex: 0
}
await addTab({ state }, tab1)
expect(state.tabs).to.have.lengthOf(2)
expect(state.untitledLastIndex).to.equal(0)
})
})

View File

@@ -1,8 +1,8 @@
import { expect } from 'chai'
import { mount } from '@vue/test-utils'
import tooltipMixin from '@/mixins/tooltips.js'
import tooltipMixin from '@/tooltipMixin'
describe('tooltips.js', () => {
describe('tooltipMixin.js', () => {
it('tooltip is hidden in initial', () => {
const component = {
template: '<div :style="tooltipStyle"></div>',

View File

@@ -2,8 +2,8 @@ import { expect } from 'chai'
import sinon from 'sinon'
import { mount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Schema from '@/components/Schema.vue'
import TableDescription from '@/components/TableDescription.vue'
import Schema from '@/views/Main/Editor/Schema'
import TableDescription from '@/views/Main/Editor/Schema/TableDescription'
const localVue = createLocalVue()
localVue.use(Vuex)

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import TableDescription from '@/components/TableDescription.vue'
import TableDescription from '@/views/Main/Editor/Schema/TableDescription'
describe('TableDescription.vue', () => {
it('Initially the columns are hidden and table name is rendered', () => {

View File

@@ -1,8 +1,8 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { mount, shallowMount } from '@vue/test-utils'
import Chart from '@/components/Chart.vue'
import chart from '@/chart.js'
import Chart from '@/views/Main/Editor/Tabs/Tab/Chart'
import chartHelper from '@/views/Main/Editor/Tabs/Tab/Chart/chartHelper'
import * as dereference from 'react-chart-editor/lib/lib/dereference'
describe('Chart.vue', () => {
@@ -14,7 +14,7 @@ describe('Chart.vue', () => {
// mount the component
const wrapper = shallowMount(Chart)
const vm = wrapper.vm
const stub = sinon.stub(chart, 'getChartStateForSave').returns('result')
const stub = sinon.stub(chartHelper, 'getChartStateForSave').returns('result')
const chartData = vm.getChartStateForSave()
expect(stub.calledOnceWith(vm.state, vm.dataSources)).to.equal(true)
expect(chartData).to.equal('result')

View File

@@ -1,9 +1,9 @@
import { expect } from 'chai'
import sinon from 'sinon'
import * as chart from '@/chart'
import * as chartHelper from '@/views/Main/Editor/Tabs/Tab/Chart/chartHelper'
import * as dereference from 'react-chart-editor/lib/lib/dereference'
describe('chart.js', () => {
describe('chartHelper.js', () => {
afterEach(() => {
sinon.restore()
})
@@ -17,7 +17,7 @@ describe('chart.js', () => {
]
}
const ds = chart.getDataSourcesFromSqlResult(sqlResult)
const ds = chartHelper.getDataSourcesFromSqlResult(sqlResult)
expect(ds).to.eql({
id: [1, 2],
name: ['foo', 'bar']
@@ -30,7 +30,7 @@ describe('chart.js', () => {
name: ['foo', 'bar']
}
const ds = chart.getOptionsFromDataSources(dataSources)
const ds = chartHelper.getOptionsFromDataSources(dataSources)
expect(ds).to.eql([
{ value: 'id', label: 'id' },
{ value: 'name', label: 'name' }
@@ -53,7 +53,7 @@ describe('chart.js', () => {
sinon.stub(dereference, 'default')
sinon.spy(JSON, 'parse')
const ds = chart.getChartStateForSave(state, dataSources)
const ds = chartHelper.getChartStateForSave(state, dataSources)
expect(dereference.default.calledOnce).to.equal(true)

View File

@@ -1,11 +1,12 @@
import { expect } from 'chai'
import { mount } from '@vue/test-utils'
import SqlEditor from '@/components/SqlEditor.vue'
import SqlEditor from '@/views/Main/Editor/Tabs/Tab/SqlEditor'
describe('SqlEditor.vue', () => {
it('Emits input event when a query is changed', async () => {
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'
})
})

View File

@@ -1,7 +1,7 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { state } from '@/store'
import hint, { getHints } from '@/hint'
import state from '@/store/state'
import showHint, { getHints } from '@/views/Main/Editor/Tabs/Tab/SqlEditor/hint'
import CM from 'codemirror'
describe('hint.js', () => {
@@ -40,9 +40,7 @@ describe('hint.js', () => {
getCursor: sinon.stub()
}
const clock = sinon.useFakeTimers()
hint.show(editor)
clock.tick(500)
showHint(editor)
expect(CM.showHint.called).to.equal(true)
expect(CM.showHint.firstCall.args[2].tables).to.eql({
@@ -77,10 +75,7 @@ describe('hint.js', () => {
getCursor: sinon.stub()
}
const clock = sinon.useFakeTimers()
hint.show(editor)
clock.tick(500)
showHint(editor)
expect(CM.showHint.firstCall.args[2].defaultTable).to.equal('foo')
})
@@ -97,10 +92,7 @@ describe('hint.js', () => {
getCursor: sinon.stub()
}
const clock = sinon.useFakeTimers()
hint.show(editor)
clock.tick(500)
showHint(editor)
expect(CM.showHint.called).to.equal(false)
})
@@ -117,10 +109,7 @@ describe('hint.js', () => {
getCursor: sinon.stub()
}
const clock = sinon.useFakeTimers()
hint.show(editor)
clock.tick(500)
showHint(editor)
expect(CM.showHint.called).to.equal(false)
})
@@ -137,10 +126,7 @@ describe('hint.js', () => {
getCursor: sinon.stub()
}
const clock = sinon.useFakeTimers()
hint.show(editor)
clock.tick(500)
showHint(editor)
expect(CM.showHint.called).to.equal(false)
})
@@ -218,10 +204,7 @@ describe('hint.js', () => {
getCursor: sinon.stub()
}
const clock = sinon.useFakeTimers()
hint.show(editor)
clock.tick(500)
showHint(editor)
expect(CM.showHint.called).to.equal(true)
expect(CM.showHint.firstCall.args[2].tables).to.eql({})
})

View File

@@ -1,9 +1,9 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { mount } from '@vue/test-utils'
import { mutations } from '@/store'
import mutations from '@/store/mutations'
import Vuex from 'vuex'
import Tab from '@/components/Tab.vue'
import Tab from '@/views/Main/Editor/Tabs/Tab'
describe('Tab.vue', () => {
afterEach(() => {

View File

@@ -1,9 +1,9 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { shallowMount, mount, createWrapper } from '@vue/test-utils'
import { mutations } from '@/store'
import mutations from '@/store/mutations'
import Vuex from 'vuex'
import Tabs from '@/components/Tabs.vue'
import Tabs from '@/views/Main/Editor/Tabs'
describe('Tabs.vue', () => {
afterEach(() => {

View File

@@ -2,8 +2,8 @@ import { expect } from 'chai'
import sinon from 'sinon'
import { mount, shallowMount, createWrapper } from '@vue/test-utils'
import Vuex from 'vuex'
import MainMenu from '@/components/MainMenu.vue'
import storedQueries from '@/storedQueries.js'
import MainMenu from '@/views/Main/MainMenu'
import storedQueries from '@/lib/storedQueries'
let wrapper = null

View File

@@ -2,10 +2,10 @@ import { expect } from 'chai'
import sinon from 'sinon'
import { mount, shallowMount } from '@vue/test-utils'
import Vuex from 'vuex'
import MyQueries from '@/views/MyQueries.vue'
import storedQueries from '@/storedQueries'
import { mutations } from '@/store'
import fu from '@/file.utils'
import MyQueries from '@/views/Main/MyQueries'
import storedQueries from '@/lib/storedQueries'
import mutations from '@/store/mutations'
import fu from '@/lib/utils/fileIo'
describe('MyQueries.vue', () => {
afterEach(() => {

View File

@@ -31,11 +31,11 @@ module.exports = {
config.module
.rule('worker')
.test(/\.worker\.js$/)
.test(/worker\.js$/)
.use('worker-loader')
.loader('worker-loader')
.end()
config.module.rule('js').exclude.add(/\.worker\.js$/)
config.module.rule('js').exclude.add(/worker\.js$/)
}
}