mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-07 02:28:54 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a9f4b3c0a | ||
|
|
77468d34ae | ||
|
|
a0577ec0ce | ||
|
|
e7d1398546 | ||
|
|
aa52048d51 | ||
|
|
33913f8f5c | ||
|
|
51eb7a543c | ||
|
|
a3fb38b23c | ||
|
|
3bb40b4eb7 | ||
|
|
6864bf84f8 | ||
|
|
9f1b3823f6 | ||
|
|
7574f529c3 | ||
|
|
653f8eff7b | ||
|
|
9b3dda6cff | ||
|
|
d94604ebfb | ||
|
|
16868ef430 | ||
|
|
b162c7043e | ||
|
|
8e856063b8 | ||
|
|
8684b4cef9 | ||
|
|
bcaebd4840 | ||
|
|
4619461af8 | ||
|
|
9fff1d699a | ||
|
|
5ab19c3fae | ||
|
|
cc483f4720 |
17
.github/workflows/config.grenrc.js
vendored
Normal file
17
.github/workflows/config.grenrc.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
dataSource: 'milestones',
|
||||||
|
ignoreIssuesWith: [
|
||||||
|
'wontfix',
|
||||||
|
'duplicate'
|
||||||
|
],
|
||||||
|
milestoneMatch: 'v{{tag_name}}',
|
||||||
|
template: {
|
||||||
|
issue: '- {{name}} [{{text}}]({{url}})',
|
||||||
|
changelogTitle: "## Release notes\n\n",
|
||||||
|
release: "{{body}}",
|
||||||
|
},
|
||||||
|
groupBy: {
|
||||||
|
'Enhancements:': ["enhancement", "internal"],
|
||||||
|
'Bug fixes:': ["bug"]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
.github/workflows/main.yml
vendored
20
.github/workflows/main.yml
vendored
@@ -25,16 +25,26 @@ jobs:
|
|||||||
cd dist
|
cd dist
|
||||||
zip -9 -r dist.zip . -x "js/*.map"
|
zip -9 -r dist.zip . -x "js/*.map"
|
||||||
|
|
||||||
|
- name: Create Release Notes
|
||||||
|
run: |
|
||||||
|
npm install github-release-notes@0.16.0 -g
|
||||||
|
gren changelog --generate --config="/.github/workflows/config.grenrc.js"
|
||||||
|
env:
|
||||||
|
GREN_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
artifacts: "dist/dist.zip"
|
artifacts: "dist/dist.zip"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
bodyFile: "CHANGELOG.md"
|
||||||
|
|
||||||
- name: Deploy 🚀
|
- name: Deploy 🚀
|
||||||
uses: JamesIves/github-pages-deploy-action@3.6.2
|
uses: JamesIves/github-pages-deploy-action@4.1.1
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
BRANCH: build # The branch the action should deploy to.
|
branch: build # The branch the action should deploy to.
|
||||||
FOLDER: dist/ # The folder the action should deploy.
|
folder: dist/ # The folder the action should deploy.
|
||||||
CLEAN: false # Automatically remove deleted files from the deploy branch
|
clean: true # Automatically remove deleted files from the deploy branch
|
||||||
|
clean-exclude: .nojekyll
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ With sqliteviz you can:
|
|||||||
- export a modified SQLite database
|
- export a modified SQLite database
|
||||||
- use it offline from your OS application menu like any other desktop app
|
- use it offline from your OS application menu like any other desktop app
|
||||||
|
|
||||||
|
https://user-images.githubusercontent.com/24638357/117355518-fa332680-aeb2-11eb-8a69-fbcea4f7aeb0.mp4
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
The latest release of sqliteviz is deployed on GitHub Pages at [lana-k.github.io/sqliteviz][6].
|
The latest release of sqliteviz is deployed on GitHub Pages at [lana-k.github.io/sqliteviz][6].
|
||||||
|
|
||||||
@@ -36,4 +38,4 @@ It is built on top of [react-chart-editor][3], [sql.js][4] and [Vue-Codemirror][
|
|||||||
[8]: https://github.com/surmon-china/vue-codemirror#readme
|
[8]: https://github.com/surmon-china/vue-codemirror#readme
|
||||||
[9]: https://www.papaparse.com/
|
[9]: https://www.papaparse.com/
|
||||||
[10]: https://github.com/lana-k/sqliteviz/wiki/Predefined-queries
|
[10]: https://github.com/lana-k/sqliteviz/wiki/Predefined-queries
|
||||||
[11]: https://github.com/plotly/plotly.js
|
[11]: https://github.com/plotly/plotly.js
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ module.exports = function (config) {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.worker\.js$/,
|
test: /worker\.js$/,
|
||||||
loader: 'worker-loader'
|
loader: 'worker-loader'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -19,7 +19,7 @@
|
|||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-chart-editor": "^0.42.0",
|
"react-chart-editor": "^0.42.0",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"sql.js": "^1.3.0",
|
"sql.js": "^1.5.0",
|
||||||
"sqlite-parser": "^1.0.1",
|
"sqlite-parser": "^1.0.1",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-codemirror": "^4.0.6",
|
"vue-codemirror": "^4.0.6",
|
||||||
@@ -19633,9 +19633,9 @@
|
|||||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||||
},
|
},
|
||||||
"node_modules/sql.js": {
|
"node_modules/sql.js": {
|
||||||
"version": "1.3.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.5.0.tgz",
|
||||||
"integrity": "sha512-bxrJ/9rqJ2SA6hpHnSodRjKBugZHewRvNTITTt74W1VZWmzODjdS68yQW0/J9oC0NWKylHEtV1ptkoTyOYO4Tw=="
|
"integrity": "sha512-Qqr6HgX/hCDpLFWdN0BNoNpYQ2c1tOl1c3HGI0cshjaFSAWszKICuLZ9CyFUvRFPpEGW8RzHzwuXWWvXVGTKBg=="
|
||||||
},
|
},
|
||||||
"node_modules/sqlite-parser": {
|
"node_modules/sqlite-parser": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@@ -40491,9 +40491,9 @@
|
|||||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||||
},
|
},
|
||||||
"sql.js": {
|
"sql.js": {
|
||||||
"version": "1.3.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.5.0.tgz",
|
||||||
"integrity": "sha512-bxrJ/9rqJ2SA6hpHnSodRjKBugZHewRvNTITTt74W1VZWmzODjdS68yQW0/J9oC0NWKylHEtV1ptkoTyOYO4Tw=="
|
"integrity": "sha512-Qqr6HgX/hCDpLFWdN0BNoNpYQ2c1tOl1c3HGI0cshjaFSAWszKICuLZ9CyFUvRFPpEGW8RzHzwuXWWvXVGTKBg=="
|
||||||
},
|
},
|
||||||
"sqlite-parser": {
|
"sqlite-parser": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sqliteviz",
|
"name": "sqliteviz",
|
||||||
"version": "1.0.0",
|
"version": "0.11.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-chart-editor": "^0.42.0",
|
"react-chart-editor": "^0.42.0",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"sql.js": "^1.3.0",
|
"sql.js": "^1.5.0",
|
||||||
"sqlite-parser": "^1.0.1",
|
"sqlite-parser": "^1.0.1",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-codemirror": "^4.0.6",
|
"vue-codemirror": "^4.0.6",
|
||||||
|
|||||||
@@ -60,4 +60,7 @@ button,
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
.CodeMirror-hints {
|
||||||
|
z-index: 999 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ascii from '@/ascii'
|
import ascii from './ascii'
|
||||||
import DropDownChevron from '@/components/svg/dropDownChevron'
|
import DropDownChevron from '@/components/svg/dropDownChevron'
|
||||||
import ClearIcon from '@/components/svg/clear'
|
import ClearIcon from '@/components/svg/clear'
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="db-uploader-container" :style="{ width }">
|
<div class="db-uploader-container" :style="{ width }">
|
||||||
<change-db-icon v-if="type === 'small'" @click.native="browse"/>
|
<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
|
<div
|
||||||
class="drop-area"
|
class="drop-area"
|
||||||
@dragover.prevent="state = 'dragover'"
|
@dragover.prevent="state = 'dragover'"
|
||||||
@@ -26,7 +26,8 @@
|
|||||||
ref="fileImg"
|
ref="fileImg"
|
||||||
:class="{
|
:class="{
|
||||||
'swing': state === 'dragover',
|
'swing': state === 'dragover',
|
||||||
'fly': state === 'drop'
|
'fly': state === 'dropping',
|
||||||
|
'hidden': state === 'dropped'
|
||||||
}"
|
}"
|
||||||
:src="require('@/assets/images/file.png')"
|
:src="require('@/assets/images/file.png')"
|
||||||
/>
|
/>
|
||||||
@@ -127,26 +128,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import fu from '@/file.utils'
|
import fIo from '@/lib/utils/fileIo'
|
||||||
import csv from '@/csv'
|
import csv from './csv'
|
||||||
import CloseIcon from '@/components/svg/close'
|
import CloseIcon from '@/components/svg/close'
|
||||||
import TextField from '@/components/TextField'
|
import TextField from '@/components/TextField'
|
||||||
import DelimiterSelector from '@/components/DelimiterSelector'
|
import DelimiterSelector from './DelimiterSelector'
|
||||||
import CheckBox from '@/components/CheckBox'
|
import CheckBox from '@/components/CheckBox'
|
||||||
import SqlTable from '@/components/SqlTable'
|
import SqlTable from '@/components/SqlTable'
|
||||||
import Logs from '@/components/Logs'
|
import Logs from '@/components/Logs'
|
||||||
import ChangeDbIcon from '@/components/svg/changeDb'
|
import ChangeDbIcon from '@/components/svg/changeDb'
|
||||||
import time from '@/time'
|
import time from '@/lib/utils/time'
|
||||||
import database from '@/database'
|
import database from '@/lib/database'
|
||||||
|
|
||||||
const csvMimeTypes = [
|
|
||||||
'text/csv',
|
|
||||||
'text/x-csv',
|
|
||||||
'application/x-csv',
|
|
||||||
'application/csv',
|
|
||||||
'text/x-comma-separated-values',
|
|
||||||
'text/comma-separated-values'
|
|
||||||
]
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DbUploader',
|
name: 'DbUploader',
|
||||||
@@ -154,9 +146,9 @@ export default {
|
|||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: 'regular',
|
default: 'small',
|
||||||
validator: (value) => {
|
validator: (value) => {
|
||||||
return ['regular', 'illustrated', 'small'].includes(value)
|
return ['illustrated', 'small'].includes(value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
@@ -196,6 +188,7 @@ export default {
|
|||||||
this.animationPromise = new Promise((resolve) => {
|
this.animationPromise = new Promise((resolve) => {
|
||||||
this.$refs.fileImg.addEventListener('animationend', event => {
|
this.$refs.fileImg.addEventListener('animationend', event => {
|
||||||
if (event.animationName.startsWith('fly')) {
|
if (event.animationName.startsWith('fly')) {
|
||||||
|
this.state = 'dropped'
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -231,7 +224,14 @@ export default {
|
|||||||
this.$store.commit('saveSchema', this.schema)
|
this.$store.commit('saveSchema', this.schema)
|
||||||
if (this.importCsvCompleted) {
|
if (this.importCsvCompleted) {
|
||||||
this.$modal.hide('parse')
|
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.$store.commit('setCurrentTabId', tabId)
|
||||||
this.importCsvCompleted = false
|
this.importCsvCompleted = false
|
||||||
}
|
}
|
||||||
@@ -341,7 +341,7 @@ export default {
|
|||||||
// Create db with csv table and get schema
|
// Create db with csv table and get schema
|
||||||
const name = file.name.replace(/\.[^.]+$/, '')
|
const name = file.name.replace(/\.[^.]+$/, '')
|
||||||
start = new Date()
|
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()
|
end = new Date()
|
||||||
|
|
||||||
// Inform about import success
|
// Inform about import success
|
||||||
@@ -380,8 +380,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async checkFile (file) {
|
async checkFile (file) {
|
||||||
this.state = 'drop'
|
this.state = 'dropping'
|
||||||
if (csvMimeTypes.includes(file.type)) {
|
if (fIo.isDatabase(file)) {
|
||||||
|
this.loadDb(file)
|
||||||
|
} else {
|
||||||
this.file = file
|
this.file = file
|
||||||
this.header = true
|
this.header = true
|
||||||
this.quoteChar = '"'
|
this.quoteChar = '"'
|
||||||
@@ -391,12 +393,10 @@ export default {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
this.$modal.show('parse')
|
this.$modal.show('parse')
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
this.loadDb(file)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
browse () {
|
browse () {
|
||||||
fu.getFileFromUser('.db,.sqlite,.sqlite3,.csv')
|
fIo.getFileFromUser('.db,.sqlite,.sqlite3,.csv')
|
||||||
.then(this.checkFile)
|
.then(this.checkFile)
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -432,6 +432,7 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#img-container {
|
#img-container {
|
||||||
@@ -503,13 +504,19 @@ export default {
|
|||||||
#file-img.fly {
|
#file-img.fly {
|
||||||
animation: fly ease-in-out 1s 1 normal;
|
animation: fly ease-in-out 1s 1 normal;
|
||||||
transform-origin: center center;
|
transform-origin: center center;
|
||||||
top: 183px;
|
|
||||||
left: 225px;
|
|
||||||
transition: top 1s ease-in-out, left 1s ease-in-out;
|
|
||||||
}
|
}
|
||||||
@keyframes fly {
|
@keyframes fly {
|
||||||
100% { transform: rotate(360deg) scale(0.5); }
|
100% {
|
||||||
|
transform: rotate(360deg) scale(0.5);
|
||||||
|
top: 183px;
|
||||||
|
left: 225px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#file-img.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Parse CSV dialog */
|
/* Parse CSV dialog */
|
||||||
.chars {
|
.chars {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg :class="animationClass" height="20" width="20" viewBox="0 0 20 20">
|
<svg :class="animationClass" :height="size" :width="size" :viewBox="`0 0 ${size} ${size}`">
|
||||||
<circle
|
<circle
|
||||||
class="loader-svg bg"
|
class="loader-svg bg"
|
||||||
cx="10"
|
:style="{ strokeWidth }"
|
||||||
cy="10"
|
:cx="size / 2"
|
||||||
r="8"
|
:cy="size / 2"
|
||||||
|
:r="radius"
|
||||||
/>
|
/>
|
||||||
<circle
|
<circle
|
||||||
class="loader-svg front"
|
class="loader-svg front"
|
||||||
:style="{ strokeDasharray: circleProgress }"
|
:style="{ strokeDasharray: circleProgress, strokeDashoffset: offset, strokeWidth }"
|
||||||
cx="10"
|
:cx="size / 2"
|
||||||
cy="10"
|
:cy="size / 2"
|
||||||
r="8"
|
:r="radius"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
@@ -19,15 +20,35 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'LoadingIndicator',
|
name: 'LoadingIndicator',
|
||||||
props: ['progress'],
|
props: {
|
||||||
|
progress: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 20
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
circleProgress () {
|
circleProgress () {
|
||||||
const dash = (50.24 * this.progress) / 100
|
const circle = this.radius * 3.14 * 2
|
||||||
const space = 50.24 - dash
|
const dash = this.progress ? (circle * this.progress) / 100 : circle * 1 / 3
|
||||||
|
const space = circle - dash
|
||||||
return `${dash}px, ${space}px`
|
return `${dash}px, ${space}px`
|
||||||
},
|
},
|
||||||
animationClass () {
|
animationClass () {
|
||||||
return this.progress === undefined ? 'loading' : 'progress'
|
return this.progress === undefined ? 'loading' : 'progress'
|
||||||
|
},
|
||||||
|
radius () {
|
||||||
|
return this.size / 2 - this.strokeWidth
|
||||||
|
},
|
||||||
|
offset () {
|
||||||
|
return this.radius * 3.14 / 2
|
||||||
|
},
|
||||||
|
strokeWidth () {
|
||||||
|
return this.size / 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +59,6 @@ export default {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0; right: 0; top: 0; bottom: 0;
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke-width: 2px;
|
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
stroke: var(--color-accent);
|
stroke: var(--color-accent);
|
||||||
}
|
}
|
||||||
@@ -48,27 +68,30 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.loading .loader-svg.front {
|
.loading .loader-svg.front {
|
||||||
stroke-dasharray: 40.24px;
|
will-change: transform;
|
||||||
animation: fill-animation-loading 1s cubic-bezier(1,1,1,1) 0s infinite;
|
animation: fill-animation-loading 1s cubic-bezier(1,1,1,1) 0s infinite;
|
||||||
|
transform-origin: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We can't change anything in loading animation except transform, opacity and filter. Because in
|
||||||
|
our case the Main Thread can be busy and animation will be frozen (e. g. getting a result set
|
||||||
|
from the web-worker after query execution).
|
||||||
|
But transform, opacity and filter trigger changes only in the Composite Layer stage in rendering
|
||||||
|
waterfall. Hence they can be processed only with Compositor Thread while the Main Thread
|
||||||
|
processes something else.
|
||||||
|
https://www.viget.com/articles/animation-performance-101-browser-under-the-hood/
|
||||||
|
*/
|
||||||
@keyframes fill-animation-loading {
|
@keyframes fill-animation-loading {
|
||||||
0% {
|
0% {
|
||||||
stroke-dasharray: 10px 40.24px;
|
transform: rotate(0deg);
|
||||||
stroke-dashoffset: 0;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
stroke-dasharray: 25.12px;
|
|
||||||
stroke-dashoffset: 25.12px;
|
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
stroke-dasharray: 10px 40.24px;
|
transform: rotate(360deg);
|
||||||
stroke-dashoffset: 50.24px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress .loader-svg.front {
|
.progress .loader-svg.front {
|
||||||
stroke-dashoffset: 12.56;
|
|
||||||
transition: stroke-dasharray 0.2s;
|
transition: stroke-dasharray 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export default {
|
|||||||
border: 1px solid var(--color-border-light);
|
border: 1px solid var(--color-border-light);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
color: var(--color-text-base);
|
||||||
}
|
}
|
||||||
.msg {
|
.msg {
|
||||||
padding: 16px 7px;
|
padding: 16px 7px;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import splitter from '@/splitter'
|
import splitter from './splitter'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Splitpanes',
|
name: 'Splitpanes',
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
<div class="table-footer-count">
|
<div class="table-footer-count">
|
||||||
{{ dataSet.values.length}} {{dataSet.values.length === 1 ? 'row' : 'rows'}} retrieved
|
{{ dataSet.values.length}} {{dataSet.values.length === 1 ? 'row' : 'rows'}} retrieved
|
||||||
<span v-if="preview">for preview</span>
|
<span v-if="preview">for preview</span>
|
||||||
|
<span v-if="time">in {{ time }}</span>
|
||||||
</div>
|
</div>
|
||||||
<pager v-show="pageCount > 1" :page-count="pageCount" v-model="currentPage" />
|
<pager v-show="pageCount > 1" :page-count="pageCount" v-model="currentPage" />
|
||||||
</div>
|
</div>
|
||||||
@@ -48,12 +49,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Pager from '@/components/Pager'
|
import Pager from './Pager'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SqlTable',
|
name: 'SqlTable',
|
||||||
components: { Pager },
|
components: { Pager },
|
||||||
props: ['dataSet', 'height', 'preview'],
|
props: ['dataSet', 'time', 'height', 'preview'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
header: null,
|
header: null,
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="icon-tooltip" :style="tooltipStyle">
|
<span class="icon-tooltip" :style="tooltipStyle">
|
||||||
Change database
|
Load another database or CSV
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import tooltipMixin from '@/mixins/tooltips'
|
import tooltipMixin from '@/tooltipMixin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'changeDbIcon',
|
name: 'changeDbIcon',
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import tooltipMixin from '@/mixins/tooltips'
|
import tooltipMixin from '@/tooltipMixin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ExportIcon',
|
name: 'ExportIcon',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import tooltipMixin from '@/mixins/tooltips'
|
import tooltipMixin from '@/tooltipMixin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HintIcon',
|
name: 'HintIcon',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import initSqlJs from 'sql.js/dist/sql-wasm.js'
|
import initSqlJs from 'sql.js/dist/sql-wasm.js'
|
||||||
import dbUtils from '@/db.utils'
|
import dbUtils from './_statements'
|
||||||
|
|
||||||
let SQL = null
|
let SQL = null
|
||||||
const sqlModuleReady = initSqlJs().then(sqlModule => { SQL = sqlModule })
|
const sqlModuleReady = initSqlJs().then(sqlModule => { SQL = sqlModule })
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import registerPromiseWorker from 'promise-worker/register'
|
import registerPromiseWorker from 'promise-worker/register'
|
||||||
import Sql from '@/sql'
|
import Sql from './_sql'
|
||||||
|
|
||||||
const sqlReady = Sql.build()
|
const sqlReady = Sql.build()
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import sqliteParser from 'sqlite-parser'
|
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:
|
// We can import workers like so because of worker-loader:
|
||||||
// https://webpack.js.org/loaders/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:
|
// Use promise-worker in order to turn worker into the promise based one:
|
||||||
// https://github.com/nolanlawson/promise-worker
|
// https://github.com/nolanlawson/promise-worker
|
||||||
@@ -50,7 +50,7 @@ class Database {
|
|||||||
delete this.importProgresses[id]
|
delete this.importProgresses[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
async createDb (name, data, progressCounterId) {
|
async importDb (name, data, progressCounterId) {
|
||||||
const result = await this.pw.postMessage({
|
const result = await this.pw.postMessage({
|
||||||
action: 'import',
|
action: 'import',
|
||||||
columns: data.columns,
|
columns: data.columns,
|
||||||
@@ -66,14 +66,15 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadDb (file) {
|
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 })
|
const res = await this.pw.postMessage({ action: 'open', buffer: fileContent })
|
||||||
|
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
throw new Error(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) {
|
async getSchema (name) {
|
||||||
@@ -85,12 +86,14 @@ class Database {
|
|||||||
const result = await this.execute(getSchemaSql)
|
const result = await this.execute(getSchemaSql)
|
||||||
// Parse DDL statements to get column names and types
|
// Parse DDL statements to get column names and types
|
||||||
const parsedSchema = []
|
const parsedSchema = []
|
||||||
result.values.forEach(item => {
|
if (result && result.values) {
|
||||||
parsedSchema.push({
|
result.values.forEach(item => {
|
||||||
name: item[0],
|
parsedSchema.push({
|
||||||
columns: getColumns(item[1])
|
name: item[0],
|
||||||
|
columns: getColumns(item[1])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
// Return db name and schema
|
// Return db name and schema
|
||||||
return {
|
return {
|
||||||
@@ -124,10 +127,10 @@ function getAst (sql) {
|
|||||||
// It throws an error if tokenizer has an arguments:
|
// It throws an error if tokenizer has an arguments:
|
||||||
// https://github.com/codeschool/sqlite-parser/issues/59
|
// https://github.com/codeschool/sqlite-parser/issues/59
|
||||||
const fixedSql = sql
|
const fixedSql = sql
|
||||||
.replace(/(?<=tokenize=.+)"tokenchars=.+"/, '')
|
.replace(/(tokenize=[^,]+)"tokenchars=.+?"/, '$1')
|
||||||
.replace(/(?<=tokenize=.+)"remove_diacritics=.+"/, '')
|
.replace(/(tokenize=[^,]+)"remove_diacritics=.+?"/, '$1')
|
||||||
.replace(/(?<=tokenize=.+)"separators=.+"/, '')
|
.replace(/(tokenize=[^,]+)"separators=.+?"/, '$1')
|
||||||
.replace(/tokenize=.+(?=(,|\)))/, 'tokenize=unicode61')
|
.replace(/tokenize=.+?(,|\))/, 'tokenize=unicode61$1')
|
||||||
|
|
||||||
return sqliteParser(fixedSql)
|
return sqliteParser(fixedSql)
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
import fu from '@/file.utils'
|
import fu from '@/lib/utils/fileIo'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getStoredQueries () {
|
getStoredQueries () {
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
export default {
|
export default {
|
||||||
|
isDatabase (file) {
|
||||||
|
const dbTypes = ['application/vnd.sqlite3', 'application/x-sqlite3']
|
||||||
|
return file.type
|
||||||
|
? dbTypes.includes(file.type)
|
||||||
|
: /\.(db|sqlite(3)?)+$/.test(file.name)
|
||||||
|
},
|
||||||
|
|
||||||
exportToFile (str, fileName, type = 'octet/stream') {
|
exportToFile (str, fileName, type = 'octet/stream') {
|
||||||
// Create downloader
|
// Create downloader
|
||||||
const downloader = document.createElement('a')
|
const downloader = document.createElement('a')
|
||||||
7
src/lib/utils/time.js
Normal file
7
src/lib/utils/time.js
Normal 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'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import App from './App.vue'
|
import App from '@/App.vue'
|
||||||
import router from './router'
|
import router from '@/router'
|
||||||
import store from './store'
|
import store from '@/store'
|
||||||
import { VuePlugin } from 'vuera'
|
import { VuePlugin } from 'vuera'
|
||||||
import VModal from 'vue-js-modal'
|
import VModal from 'vue-js-modal'
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import '@/assets/styles/tooltips.css'
|
|||||||
import '@/assets/styles/messages.css'
|
import '@/assets/styles/messages.css'
|
||||||
|
|
||||||
if (!['localhost', '127.0.0.1'].includes(location.hostname)) {
|
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)
|
Vue.use(VuePlugin)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import VueRouter from 'vue-router'
|
import VueRouter from 'vue-router'
|
||||||
import Editor from '@/views/Editor'
|
import Editor from '@/views/Main/Editor'
|
||||||
import MyQueries from '@/views/MyQueries'
|
import MyQueries from '@/views/Main/MyQueries'
|
||||||
import Home from '@/views/Home'
|
import Welcome from '@/views/Welcome'
|
||||||
import MainView from '@/views/MainView'
|
import Main from '@/views/Main'
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
@@ -11,12 +11,12 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Welcome',
|
name: 'Welcome',
|
||||||
component: Home
|
component: Welcome
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'MainView',
|
name: 'Main',
|
||||||
component: MainView,
|
component: Main,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/editor',
|
path: '/editor',
|
||||||
30
src/store/actions.js
Normal file
30
src/store/actions.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,112 +1,11 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
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)
|
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({
|
export default new Vuex.Store({
|
||||||
state,
|
state,
|
||||||
mutations,
|
mutations,
|
||||||
|
|||||||
63
src/store/mutations.js
Normal file
63
src/store/mutations.js
Normal 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
11
src/store/state.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export default {
|
||||||
|
schema: null,
|
||||||
|
dbFile: null,
|
||||||
|
dbName: null,
|
||||||
|
tabs: [],
|
||||||
|
currentTab: null,
|
||||||
|
currentTabId: null,
|
||||||
|
untitledLastIndex: 0,
|
||||||
|
predefinedQueries: [],
|
||||||
|
db: null
|
||||||
|
}
|
||||||
36
src/time.js
36
src/time.js
@@ -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$/, '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 can’t 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>
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="db">
|
<div id="db">
|
||||||
<div @click="schemaVisible = !schemaVisible" class="db-name">
|
<div @click="schemaVisible = !schemaVisible" class="db-name">
|
||||||
<tree-chevron :expanded="schemaVisible"/>
|
<tree-chevron v-show="schema.length > 0" :expanded="schemaVisible"/>
|
||||||
{{ dbName }}
|
{{ dbName }}
|
||||||
</div>
|
</div>
|
||||||
<db-uploader id="db-edit" type="small" />
|
<db-uploader id="db-edit" type="small" />
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TableDescription from '@/components/TableDescription'
|
import TableDescription from './TableDescription'
|
||||||
import TextField from '@/components/TextField'
|
import TextField from '@/components/TextField'
|
||||||
import TreeChevron from '@/components/svg/treeChevron'
|
import TreeChevron from '@/components/svg/treeChevron'
|
||||||
import DbUploader from '@/components/DbUploader'
|
import DbUploader from '@/components/DbUploader'
|
||||||
@@ -86,7 +86,7 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-image: linear-gradient(white 73%, transparent);;
|
background-image: linear-gradient(white 73%, rgba(255, 255, 255, 0));
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.schema, .db-name {
|
.schema, .db-name {
|
||||||
@@ -28,7 +28,7 @@ import plotly from 'plotly.js/dist/plotly'
|
|||||||
import 'react-chart-editor/lib/react-chart-editor.min.css'
|
import 'react-chart-editor/lib/react-chart-editor.min.css'
|
||||||
|
|
||||||
import PlotlyEditor from 'react-chart-editor'
|
import PlotlyEditor from 'react-chart-editor'
|
||||||
import chart from '@/chart'
|
import chartHelper from './chartHelper'
|
||||||
import dereference from 'react-chart-editor/lib/lib/dereference'
|
import dereference from 'react-chart-editor/lib/lib/dereference'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -49,10 +49,10 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
dataSources () {
|
dataSources () {
|
||||||
return chart.getDataSourcesFromSqlResult(this.sqlResult)
|
return chartHelper.getDataSourcesFromSqlResult(this.sqlResult)
|
||||||
},
|
},
|
||||||
dataSourceOptions () {
|
dataSourceOptions () {
|
||||||
return chart.getOptionsFromDataSources(this.dataSources)
|
return chartHelper.getOptionsFromDataSources(this.dataSources)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -71,7 +71,7 @@ export default {
|
|||||||
this.$emit('update')
|
this.$emit('update')
|
||||||
},
|
},
|
||||||
getChartStateForSave () {
|
getChartStateForSave () {
|
||||||
return chart.getChartStateForSave(this.state, this.dataSources)
|
return chartHelper.getChartStateForSave(this.state, this.dataSources)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ import CM from 'codemirror'
|
|||||||
import 'codemirror/addon/hint/show-hint.js'
|
import 'codemirror/addon/hint/show-hint.js'
|
||||||
import 'codemirror/addon/hint/sql-hint.js'
|
import 'codemirror/addon/hint/sql-hint.js'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import { debounce } from 'debounce'
|
|
||||||
|
|
||||||
export function getHints (cm, options) {
|
export function getHints (cm, options) {
|
||||||
const token = cm.getTokenAt(cm.getCursor()).string.toUpperCase()
|
const token = cm.getTokenAt(cm.getCursor()).string.toUpperCase()
|
||||||
@@ -27,23 +26,25 @@ const hintOptions = {
|
|||||||
},
|
},
|
||||||
get defaultTable () {
|
get defaultTable () {
|
||||||
const schema = store.state.schema
|
const schema = store.state.schema
|
||||||
return schema.length === 1 ? schema[0].name : null
|
return schema && schema.length === 1 ? schema[0].name : null
|
||||||
},
|
},
|
||||||
completeSingle: false,
|
completeSingle: false,
|
||||||
completeOnSingleClick: true,
|
completeOnSingleClick: true,
|
||||||
alignWithWord: false
|
alignWithWord: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export function showHintOnDemand (editor) {
|
||||||
show: debounce(function (editor) {
|
CM.showHint(editor, getHints, hintOptions)
|
||||||
// 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)
|
export default function showHint (editor) {
|
||||||
const tokenType = token.type
|
// Don't show autocomplete after a space or semicolon or in string literals
|
||||||
if (tokenType === 'string' || !ch || ch === ' ' || ch === ';') {
|
const token = editor.getTokenAt(editor.getCursor())
|
||||||
return
|
const ch = token.string.slice(-1)
|
||||||
}
|
const tokenType = token.type
|
||||||
|
if (tokenType === 'string' || !ch || ch === ' ' || ch === ';') {
|
||||||
CM.showHint(editor, getHints, hintOptions)
|
return
|
||||||
}, 400)
|
}
|
||||||
|
|
||||||
|
CM.showHint(editor, getHints, hintOptions)
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import hint from '@/hint'
|
import showHint, { showHintOnDemand } from './hint'
|
||||||
|
import { debounce } from 'debounce'
|
||||||
import { codemirror } from 'vue-codemirror'
|
import { codemirror } from 'vue-codemirror'
|
||||||
import 'codemirror/lib/codemirror.css'
|
import 'codemirror/lib/codemirror.css'
|
||||||
import 'codemirror/mode/sql/sql.js'
|
import 'codemirror/mode/sql/sql.js'
|
||||||
@@ -28,7 +29,8 @@ export default {
|
|||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
line: true,
|
line: true,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
autoRefresh: true
|
autoRefresh: true,
|
||||||
|
extraKeys: { 'Ctrl-Space': showHintOnDemand }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -38,7 +40,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange: hint.show
|
onChange: debounce(showHint, 400)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -21,7 +21,8 @@
|
|||||||
>
|
>
|
||||||
Run your query and get results here
|
Run your query and get results here
|
||||||
</div>
|
</div>
|
||||||
<div v-show="isGettingResults" class="table-preview result-in-progress">
|
<div v-if="isGettingResults" class="table-preview result-in-progress">
|
||||||
|
<loading-indicator :size="30"/>
|
||||||
Fetching results...
|
Fetching results...
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -30,10 +31,8 @@
|
|||||||
>
|
>
|
||||||
No rows retrieved according to your query
|
No rows retrieved according to your query
|
||||||
</div>
|
</div>
|
||||||
<div v-show="error" class="table-preview error">
|
<logs v-if="error" :messages="[error]"/>
|
||||||
{{ error }}
|
<sql-table v-if="result" :data-set="result" :time="time" :height="tableViewHeight" />
|
||||||
</div>
|
|
||||||
<sql-table v-if="result" :data-set="result" :height="tableViewHeight" />
|
|
||||||
</div>
|
</div>
|
||||||
<chart
|
<chart
|
||||||
:visible="view === 'chart'"
|
:visible="view === 'chart'"
|
||||||
@@ -50,10 +49,13 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SqlTable from '@/components/SqlTable'
|
import SqlTable from '@/components/SqlTable'
|
||||||
import SqlEditor from '@/components/SqlEditor'
|
|
||||||
import Splitpanes from '@/components/Splitpanes'
|
import Splitpanes from '@/components/Splitpanes'
|
||||||
import ViewSwitcher from '@/components/ViewSwitcher'
|
import LoadingIndicator from '@/components/LoadingIndicator'
|
||||||
import Chart from '@/components/Chart'
|
import SqlEditor from './SqlEditor'
|
||||||
|
import ViewSwitcher from './ViewSwitcher'
|
||||||
|
import Chart from './Chart'
|
||||||
|
import Logs from '@/components/Logs'
|
||||||
|
import time from '@/lib/utils/time'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Tab',
|
name: 'Tab',
|
||||||
@@ -63,7 +65,9 @@ export default {
|
|||||||
SqlTable,
|
SqlTable,
|
||||||
Splitpanes,
|
Splitpanes,
|
||||||
ViewSwitcher,
|
ViewSwitcher,
|
||||||
Chart
|
Chart,
|
||||||
|
LoadingIndicator,
|
||||||
|
Logs
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -73,7 +77,8 @@ export default {
|
|||||||
tableViewHeight: 0,
|
tableViewHeight: 0,
|
||||||
isGettingResults: false,
|
isGettingResults: false,
|
||||||
error: null,
|
error: null,
|
||||||
resizeObserver: null
|
resizeObserver: null,
|
||||||
|
time: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -110,11 +115,16 @@ export default {
|
|||||||
this.error = null
|
this.error = null
|
||||||
const state = this.$store.state
|
const state = this.$store.state
|
||||||
try {
|
try {
|
||||||
|
const start = new Date()
|
||||||
this.result = await state.db.execute(this.query + ';')
|
this.result = await state.db.execute(this.query + ';')
|
||||||
|
this.time = time.getPeriod(start, new Date())
|
||||||
const schema = await state.db.getSchema(state.dbName)
|
const schema = await state.db.getSchema(state.dbName)
|
||||||
this.$store.commit('saveSchema', schema)
|
this.$store.commit('saveSchema', schema)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.error = err
|
this.error = {
|
||||||
|
type: 'error',
|
||||||
|
message: err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.isGettingResults = false
|
this.isGettingResults = false
|
||||||
},
|
},
|
||||||
@@ -183,11 +193,32 @@ export default {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-preview.error {
|
.result-in-progress {
|
||||||
color: var(--color-text-error);
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
will-change: opacity;
|
||||||
|
/*
|
||||||
|
We need to show loader in 1 sec after starting query execution. We can't do that with
|
||||||
|
setTimeout because the main thread can be busy by getting a result set from the web worker.
|
||||||
|
But we can use CSS animation for opacity. Opacity triggers changes only in the Composite Layer
|
||||||
|
stage in rendering waterfall. Hence it can be processed only with Compositor Thread while
|
||||||
|
the Main Thread processes a result set.
|
||||||
|
https://www.viget.com/articles/animation-performance-101-browser-under-the-hood/
|
||||||
|
*/
|
||||||
|
animation: show-loader 1s linear 0s 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-preview.error::first-letter {
|
@keyframes show-loader {
|
||||||
text-transform: capitalize;
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
99% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Tab from '@/components/Tab'
|
import Tab from './Tab'
|
||||||
import CloseIcon from '@/components/svg/close'
|
import CloseIcon from '@/components/svg/close'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
68
src/views/Main/Editor/index.vue
Normal file
68
src/views/Main/Editor/index.vue
Normal 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>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<router-link to="/editor">Editor</router-link>
|
<router-link to="/editor">Editor</router-link>
|
||||||
<router-link to="/my-queries">My queries</router-link>
|
<router-link to="/my-queries">My queries</router-link>
|
||||||
|
<a href="https://github.com/lana-k/sqliteviz/wiki" target="_blank">Help</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import TextField from '@/components/TextField'
|
import TextField from '@/components/TextField'
|
||||||
import CloseIcon from '@/components/svg/close'
|
import CloseIcon from '@/components/svg/close'
|
||||||
import storedQueries from '@/storedQueries'
|
import storedQueries from '@/lib/storedQueries'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MainMenu',
|
name: 'MainMenu',
|
||||||
@@ -141,16 +141,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RenameIcon from '@/components/svg/rename'
|
import RenameIcon from './svg/rename'
|
||||||
import CopyIcon from '@/components/svg/copy'
|
import CopyIcon from './svg/copy'
|
||||||
import ExportIcon from '@/components/svg/export'
|
import ExportIcon from '@/components/svg/export'
|
||||||
import DeleteIcon from '@/components/svg/delete'
|
import DeleteIcon from './svg/delete'
|
||||||
import CloseIcon from '@/components/svg/close'
|
import CloseIcon from '@/components/svg/close'
|
||||||
import TextField from '@/components/TextField'
|
import TextField from '@/components/TextField'
|
||||||
import CheckBox from '@/components/CheckBox'
|
import CheckBox from '@/components/CheckBox'
|
||||||
import tooltipMixin from '@/mixins/tooltips'
|
import tooltipMixin from '@/tooltipMixin'
|
||||||
import storedQueries from '@/storedQueries'
|
import storedQueries from '@/lib/storedQueries'
|
||||||
import fu from '@/file.utils'
|
import fu from '@/lib/utils/fileIo'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MyQueries',
|
name: 'MyQueries',
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import tooltipMixin from '@/mixins/tooltips'
|
import tooltipMixin from '@/tooltipMixin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CopyIcon',
|
name: 'CopyIcon',
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import tooltipMixin from '@/mixins/tooltips'
|
import tooltipMixin from '@/tooltipMixin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DeleteIcon',
|
name: 'DeleteIcon',
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import tooltipMixin from '@/mixins/tooltips'
|
import tooltipMixin from '@/tooltipMixin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RenameIcon',
|
name: 'RenameIcon',
|
||||||
@@ -8,11 +8,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MainMenu from '@/components/MainMenu'
|
import MainMenu from './MainMenu'
|
||||||
import '@/assets/styles/scrollbars.css'
|
import '@/assets/styles/scrollbars.css'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MainView',
|
name: 'Main',
|
||||||
components: { MainMenu }
|
components: { MainMenu }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
<div id="note">
|
<div id="note">
|
||||||
Sqliteviz is fully client-side. Your database never leaves your computer.
|
Sqliteviz is fully client-side. Your database never leaves your computer.
|
||||||
</div>
|
</div>
|
||||||
<button id ="skip" class="secondary" @click="$router.push('/editor')">
|
<button id="skip" class="secondary" @click="$router.push('/editor')">
|
||||||
Skip database loading
|
Create empty database
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
import DbUploader from '@/components/DbUploader'
|
import DbUploader from '@/components/DbUploader'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Welcome',
|
||||||
components: { DbUploader }
|
components: { DbUploader }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -2,15 +2,16 @@ import { expect } from 'chai'
|
|||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import { shallowMount, mount } from '@vue/test-utils'
|
import { shallowMount, mount } from '@vue/test-utils'
|
||||||
import DbUploader from '@/components/DbUploader.vue'
|
import DbUploader from '@/components/DbUploader'
|
||||||
import fu from '@/file.utils'
|
import fu from '@/lib/utils/fileIo'
|
||||||
import database from '@/database'
|
import database from '@/lib/database'
|
||||||
import csv from '@/csv'
|
import csv from '@/components/DbUploader/csv'
|
||||||
|
|
||||||
describe('DbUploader.vue', () => {
|
describe('DbUploader.vue', () => {
|
||||||
let state = {}
|
let state = {}
|
||||||
let mutations = {}
|
let mutations = {}
|
||||||
let store = {}
|
let store = {}
|
||||||
|
let place
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// mock store state and mutations
|
// mock store state and mutations
|
||||||
@@ -20,15 +21,19 @@ describe('DbUploader.vue', () => {
|
|||||||
setDb: sinon.stub()
|
setDb: sinon.stub()
|
||||||
}
|
}
|
||||||
store = new Vuex.Store({ state, mutations })
|
store = new Vuex.Store({ state, mutations })
|
||||||
|
|
||||||
|
place = document.createElement('div')
|
||||||
|
document.body.appendChild(place)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sinon.restore()
|
sinon.restore()
|
||||||
|
place.remove()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('loads db on click and redirects to /editor', async () => {
|
it('loads db on click and redirects to /editor', async () => {
|
||||||
// mock getting a file from user
|
// mock getting a file from user
|
||||||
const file = {}
|
const file = { name: 'test.db' }
|
||||||
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
||||||
|
|
||||||
// mock db loading
|
// mock db loading
|
||||||
@@ -44,15 +49,22 @@ describe('DbUploader.vue', () => {
|
|||||||
|
|
||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = shallowMount(DbUploader, {
|
const wrapper = shallowMount(DbUploader, {
|
||||||
|
attachTo: place,
|
||||||
store,
|
store,
|
||||||
mocks: { $router, $route }
|
mocks: { $router, $route },
|
||||||
|
propsData: {
|
||||||
|
type: 'illustrated'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await wrapper.find('.drop-area').trigger('click')
|
await wrapper.find('.drop-area').trigger('click')
|
||||||
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
|
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
|
||||||
await db.loadDb.returnValues[0]
|
await db.loadDb.returnValues[0]
|
||||||
|
await wrapper.vm.animationPromise
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
|
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
|
||||||
expect($router.push.calledOnceWith('/editor')).to.equal(true)
|
expect($router.push.calledOnceWith('/editor')).to.equal(true)
|
||||||
|
wrapper.destroy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('loads db on drop and redirects to /editor', async () => {
|
it('loads db on drop and redirects to /editor', async () => {
|
||||||
@@ -69,12 +81,16 @@ describe('DbUploader.vue', () => {
|
|||||||
|
|
||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = shallowMount(DbUploader, {
|
const wrapper = shallowMount(DbUploader, {
|
||||||
|
attachTo: place,
|
||||||
store,
|
store,
|
||||||
mocks: { $router, $route }
|
mocks: { $router, $route },
|
||||||
|
propsData: {
|
||||||
|
type: 'illustrated'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// mock a file dropped by a user
|
// mock a file dropped by a user
|
||||||
const file = {}
|
const file = { name: '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],
|
||||||
@@ -84,13 +100,16 @@ describe('DbUploader.vue', () => {
|
|||||||
await wrapper.find('.drop-area').trigger('drop', dropData)
|
await wrapper.find('.drop-area').trigger('drop', dropData)
|
||||||
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
|
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
|
||||||
await db.loadDb.returnValues[0]
|
await db.loadDb.returnValues[0]
|
||||||
|
await wrapper.vm.animationPromise
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
|
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
|
||||||
expect($router.push.calledOnceWith('/editor')).to.equal(true)
|
expect($router.push.calledOnceWith('/editor')).to.equal(true)
|
||||||
|
wrapper.destroy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("doesn't redirect if already on /editor", async () => {
|
it("doesn't redirect if already on /editor", async () => {
|
||||||
// mock getting a file from user
|
// mock getting a file from user
|
||||||
const file = {}
|
const file = { name: 'test.db' }
|
||||||
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
||||||
|
|
||||||
// mock db loading
|
// mock db loading
|
||||||
@@ -106,13 +125,20 @@ describe('DbUploader.vue', () => {
|
|||||||
|
|
||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = shallowMount(DbUploader, {
|
const wrapper = shallowMount(DbUploader, {
|
||||||
|
attachTo: place,
|
||||||
store,
|
store,
|
||||||
mocks: { $router, $route }
|
mocks: { $router, $route },
|
||||||
|
propsData: {
|
||||||
|
type: 'illustrated'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await wrapper.find('.drop-area').trigger('click')
|
await wrapper.find('.drop-area').trigger('click')
|
||||||
await db.loadDb.returnValues[0]
|
await db.loadDb.returnValues[0]
|
||||||
|
await wrapper.vm.animationPromise
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
expect($router.push.called).to.equal(false)
|
expect($router.push.called).to.equal(false)
|
||||||
|
wrapper.destroy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -122,6 +148,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
let actions = {}
|
let actions = {}
|
||||||
const newTabId = 1
|
const newTabId = 1
|
||||||
let store = {}
|
let store = {}
|
||||||
|
let place
|
||||||
|
|
||||||
// mock router
|
// mock router
|
||||||
const $router = { }
|
const $router = { }
|
||||||
@@ -150,15 +177,24 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
|
|
||||||
$router.push = sinon.stub()
|
$router.push = sinon.stub()
|
||||||
|
|
||||||
|
place = document.createElement('div')
|
||||||
|
document.body.appendChild(place)
|
||||||
|
|
||||||
// mount the component
|
// mount the component
|
||||||
wrapper = mount(DbUploader, {
|
wrapper = mount(DbUploader, {
|
||||||
|
attachTo: place,
|
||||||
store,
|
store,
|
||||||
mocks: { $router, $route }
|
mocks: { $router, $route },
|
||||||
|
propsData: {
|
||||||
|
type: 'illustrated'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sinon.restore()
|
sinon.restore()
|
||||||
|
wrapper.destroy()
|
||||||
|
place.remove()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows parse dialog if gets csv file', async () => {
|
it('shows parse dialog if gets csv file', async () => {
|
||||||
@@ -184,6 +220,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
expect(wrapper.find('[data-modal="parse"]').exists()).to.equal(true)
|
expect(wrapper.find('[data-modal="parse"]').exists()).to.equal(true)
|
||||||
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('"')
|
||||||
@@ -219,6 +256,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
parse.onCall(1).resolves({
|
parse.onCall(1).resolves({
|
||||||
delimiter: ',',
|
delimiter: ',',
|
||||||
@@ -328,6 +366,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
let resolveParsing
|
let resolveParsing
|
||||||
parse.onCall(1).returns(new Promise(resolve => {
|
parse.onCall(1).returns(new Promise(resolve => {
|
||||||
@@ -395,6 +434,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
await wrapper.find('#csv-import').trigger('click')
|
await wrapper.find('#csv-import').trigger('click')
|
||||||
await csv.parse.returnValues[1]
|
await csv.parse.returnValues[1]
|
||||||
@@ -452,6 +492,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
await wrapper.find('#csv-import').trigger('click')
|
await wrapper.find('#csv-import').trigger('click')
|
||||||
await csv.parse.returnValues[1]
|
await csv.parse.returnValues[1]
|
||||||
@@ -511,6 +552,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
await wrapper.find('#csv-import').trigger('click')
|
await wrapper.find('#csv-import').trigger('click')
|
||||||
await csv.parse.returnValues[1]
|
await csv.parse.returnValues[1]
|
||||||
@@ -563,7 +605,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
|
|
||||||
let resolveImport = sinon.stub()
|
let resolveImport = sinon.stub()
|
||||||
const newDb = {
|
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),
|
createProgressCounter: sinon.stub().returns(1),
|
||||||
deleteProgressCounter: sinon.stub()
|
deleteProgressCounter: sinon.stub()
|
||||||
}
|
}
|
||||||
@@ -573,6 +615,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
await wrapper.find('#csv-import').trigger('click')
|
await wrapper.find('#csv-import').trigger('click')
|
||||||
await csv.parse.returnValues[1]
|
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.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||||
expect(wrapper.find('#csv-finish').isVisible()).to.equal(false)
|
expect(wrapper.find('#csv-finish').isVisible()).to.equal(false)
|
||||||
expect(wrapper.find('#csv-import').isVisible()).to.equal(true)
|
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
|
// After resolving - loading indicator is not shown
|
||||||
await resolveImport()
|
await resolveImport()
|
||||||
await newDb.createDb.returnValues[0]
|
await newDb.importDb.returnValues[0]
|
||||||
expect(
|
expect(
|
||||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||||
).to.equal(false)
|
).to.equal(false)
|
||||||
@@ -621,7 +664,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
hasErrors: false,
|
hasErrors: false,
|
||||||
messages: []
|
messages: []
|
||||||
})
|
})
|
||||||
|
// we need to separate calles because messages will mutate
|
||||||
parse.onCall(1).resolves({
|
parse.onCall(1).resolves({
|
||||||
delimiter: '|',
|
delimiter: '|',
|
||||||
data: {
|
data: {
|
||||||
@@ -637,7 +680,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
|
|
||||||
const schema = {}
|
const schema = {}
|
||||||
const newDb = {
|
const newDb = {
|
||||||
createDb: sinon.stub().resolves(schema),
|
importDb: sinon.stub().resolves(schema),
|
||||||
createProgressCounter: sinon.stub().returns(1),
|
createProgressCounter: sinon.stub().returns(1),
|
||||||
deleteProgressCounter: sinon.stub()
|
deleteProgressCounter: sinon.stub()
|
||||||
}
|
}
|
||||||
@@ -647,6 +690,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
await wrapper.find('#csv-import').trigger('click')
|
await wrapper.find('#csv-import').trigger('click')
|
||||||
await csv.parse.returnValues[1]
|
await csv.parse.returnValues[1]
|
||||||
@@ -681,7 +725,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
hasErrors: false,
|
hasErrors: false,
|
||||||
messages: []
|
messages: []
|
||||||
})
|
})
|
||||||
|
// we need to separate calles because messages will mutate
|
||||||
parse.onCall(1).resolves({
|
parse.onCall(1).resolves({
|
||||||
delimiter: '|',
|
delimiter: '|',
|
||||||
data: {
|
data: {
|
||||||
@@ -696,7 +740,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const newDb = {
|
const newDb = {
|
||||||
createDb: sinon.stub().rejects(new Error('fail')),
|
importDb: sinon.stub().rejects(new Error('fail')),
|
||||||
createProgressCounter: sinon.stub().returns(1),
|
createProgressCounter: sinon.stub().returns(1),
|
||||||
deleteProgressCounter: sinon.stub()
|
deleteProgressCounter: sinon.stub()
|
||||||
}
|
}
|
||||||
@@ -706,6 +750,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
await wrapper.find('#csv-import').trigger('click')
|
await wrapper.find('#csv-import').trigger('click')
|
||||||
await csv.parse.returnValues[1]
|
await csv.parse.returnValues[1]
|
||||||
@@ -729,8 +774,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('import final', async () => {
|
it('import final', async () => {
|
||||||
const parse = sinon.stub(csv, 'parse')
|
sinon.stub(csv, 'parse').resolves({
|
||||||
parse.onCall(0).resolves({
|
|
||||||
delimiter: '|',
|
delimiter: '|',
|
||||||
data: {
|
data: {
|
||||||
columns: ['col1', 'col2'],
|
columns: ['col1', 'col2'],
|
||||||
@@ -742,22 +786,9 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
messages: []
|
messages: []
|
||||||
})
|
})
|
||||||
|
|
||||||
parse.onCall(1).resolves({
|
|
||||||
delimiter: '|',
|
|
||||||
data: {
|
|
||||||
columns: ['col1', 'col2'],
|
|
||||||
values: [
|
|
||||||
[1, 'foo'],
|
|
||||||
[2, 'bar']
|
|
||||||
]
|
|
||||||
},
|
|
||||||
hasErrors: false,
|
|
||||||
messages: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const schema = {}
|
const schema = {}
|
||||||
const newDb = {
|
const newDb = {
|
||||||
createDb: sinon.stub().resolves(schema),
|
importDb: sinon.stub().resolves(schema),
|
||||||
createProgressCounter: sinon.stub().returns(1),
|
createProgressCounter: sinon.stub().returns(1),
|
||||||
deleteProgressCounter: sinon.stub()
|
deleteProgressCounter: sinon.stub()
|
||||||
}
|
}
|
||||||
@@ -767,6 +798,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
await wrapper.find('#csv-import').trigger('click')
|
await wrapper.find('#csv-import').trigger('click')
|
||||||
await csv.parse.returnValues[1]
|
await csv.parse.returnValues[1]
|
||||||
@@ -784,8 +816,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('import cancel', async () => {
|
it('import cancel', async () => {
|
||||||
const parse = sinon.stub(csv, 'parse')
|
sinon.stub(csv, 'parse').resolves({
|
||||||
parse.onCall(0).resolves({
|
|
||||||
delimiter: '|',
|
delimiter: '|',
|
||||||
data: {
|
data: {
|
||||||
columns: ['col1', 'col2'],
|
columns: ['col1', 'col2'],
|
||||||
@@ -797,22 +828,9 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
messages: []
|
messages: []
|
||||||
})
|
})
|
||||||
|
|
||||||
parse.onCall(1).resolves({
|
|
||||||
delimiter: '|',
|
|
||||||
data: {
|
|
||||||
columns: ['col1', 'col2'],
|
|
||||||
values: [
|
|
||||||
[1, 'foo'],
|
|
||||||
[2, 'bar']
|
|
||||||
]
|
|
||||||
},
|
|
||||||
hasErrors: false,
|
|
||||||
messages: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const schema = {}
|
const schema = {}
|
||||||
const newDb = {
|
const newDb = {
|
||||||
createDb: sinon.stub().resolves(schema),
|
importDb: sinon.stub().resolves(schema),
|
||||||
createProgressCounter: sinon.stub().returns(1),
|
createProgressCounter: sinon.stub().returns(1),
|
||||||
deleteProgressCounter: sinon.stub(),
|
deleteProgressCounter: sinon.stub(),
|
||||||
shutDown: sinon.stub()
|
shutDown: sinon.stub()
|
||||||
@@ -823,6 +841,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
await wrapper.find('#csv-import').trigger('click')
|
await wrapper.find('#csv-import').trigger('click')
|
||||||
await csv.parse.returnValues[1]
|
await csv.parse.returnValues[1]
|
||||||
@@ -856,7 +875,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
|
|
||||||
const schema = {}
|
const schema = {}
|
||||||
const newDb = {
|
const newDb = {
|
||||||
createDb: sinon.stub().resolves(schema),
|
importDb: sinon.stub().resolves(schema),
|
||||||
createProgressCounter: sinon.stub().returns(1),
|
createProgressCounter: sinon.stub().returns(1),
|
||||||
deleteProgressCounter: sinon.stub(),
|
deleteProgressCounter: sinon.stub(),
|
||||||
loadDb: sinon.stub().resolves()
|
loadDb: sinon.stub().resolves()
|
||||||
@@ -867,6 +886,7 @@ describe('DbUploader.vue import CSV', () => {
|
|||||||
await csv.parse.returnValues[0]
|
await csv.parse.returnValues[0]
|
||||||
await wrapper.vm.animationPromise
|
await wrapper.vm.animationPromise
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
await wrapper.find('#csv-import').trigger('click')
|
await wrapper.find('#csv-import').trigger('click')
|
||||||
await csv.parse.returnValues[1]
|
await csv.parse.returnValues[1]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { mount, shallowMount } from '@vue/test-utils'
|
import { mount, shallowMount } from '@vue/test-utils'
|
||||||
import DelimiterSelector from '@/components/DelimiterSelector'
|
import DelimiterSelector from '@/components/DbUploader/DelimiterSelector'
|
||||||
|
|
||||||
describe('DelimiterSelector', async () => {
|
describe('DelimiterSelector', async () => {
|
||||||
it('shows the name of value', async () => {
|
it('shows the name of value', async () => {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import csv from '@/csv'
|
import csv from '@/components/DbUploader/csv'
|
||||||
import Papa from 'papaparse'
|
import Papa from 'papaparse'
|
||||||
|
|
||||||
describe('csv.js', () => {
|
describe('csv.js', () => {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { shallowMount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import LoadingIndicator from '@/components/LoadingIndicator.vue'
|
import LoadingIndicator from '@/components/LoadingIndicator'
|
||||||
|
|
||||||
describe('LoadingIndicator.vue', () => {
|
describe('LoadingIndicator.vue', () => {
|
||||||
it('Calculates animation class', async () => {
|
it('Calculates animation class', async () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { shallowMount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import Logs from '@/components/Logs.vue'
|
import Logs from '@/components/Logs'
|
||||||
|
|
||||||
let place
|
let place
|
||||||
describe('Logs.vue', () => {
|
describe('Logs.vue', () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { shallowMount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import Splitpanes from '@/components/Splitpanes.vue'
|
import Splitpanes from '@/components/Splitpanes'
|
||||||
|
|
||||||
describe('Splitpanes.vue', () => {
|
describe('Splitpanes.vue', () => {
|
||||||
it('renders correctly - vertical', () => {
|
it('renders correctly - vertical', () => {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import splitter from '@/splitter'
|
import splitter from '@/components/Splitpanes/splitter'
|
||||||
|
|
||||||
describe('splitter.js', () => {
|
describe('splitter.js', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import Pager from '@/components/Pager.vue'
|
import Pager from '@/components/SqlTable/Pager'
|
||||||
|
|
||||||
describe('Pager.vue', () => {
|
describe('Pager.vue', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -2,14 +2,14 @@ import chai from 'chai'
|
|||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import chaiAsPromised from 'chai-as-promised'
|
import chaiAsPromised from 'chai-as-promised'
|
||||||
import initSqlJs from 'sql.js'
|
import initSqlJs from 'sql.js'
|
||||||
import Sql from '@/sql'
|
import Sql from '@/lib/database/_sql'
|
||||||
chai.use(chaiAsPromised)
|
chai.use(chaiAsPromised)
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
chai.should()
|
chai.should()
|
||||||
|
|
||||||
const getSQL = initSqlJs()
|
const getSQL = initSqlJs()
|
||||||
|
|
||||||
describe('sql.js', () => {
|
describe('_sql.js', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sinon.restore()
|
sinon.restore()
|
||||||
})
|
})
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import dbUtils from '@/db.utils'
|
import dbUtils from '@/lib/database/_statements'
|
||||||
|
|
||||||
describe('db.utils.js', () => {
|
describe('_statements.js', () => {
|
||||||
it('generateChunks', () => {
|
it('generateChunks', () => {
|
||||||
const arr = ['1', '2', '3', '4', '5']
|
const arr = ['1', '2', '3', '4', '5']
|
||||||
const size = 2
|
const size = 2
|
||||||
@@ -2,8 +2,8 @@ import chai from 'chai'
|
|||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import chaiAsPromised from 'chai-as-promised'
|
import chaiAsPromised from 'chai-as-promised'
|
||||||
import initSqlJs from 'sql.js'
|
import initSqlJs from 'sql.js'
|
||||||
import database from '@/database'
|
import database from '@/lib/database'
|
||||||
import fu from '@/file.utils'
|
import fu from '@/lib/utils/fileIo'
|
||||||
|
|
||||||
chai.use(chaiAsPromised)
|
chai.use(chaiAsPromised)
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
@@ -146,7 +146,7 @@ describe('database.js', () => {
|
|||||||
}
|
}
|
||||||
const progressHandler = sinon.spy()
|
const progressHandler = sinon.spy()
|
||||||
const progressCounterId = db.createProgressCounter(progressHandler)
|
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(dbName).to.equal('foo')
|
||||||
expect(schema).to.have.lengthOf(1)
|
expect(schema).to.have.lengthOf(1)
|
||||||
expect(schema[0].name).to.equal('csv_import')
|
expect(schema[0].name).to.equal('csv_import')
|
||||||
@@ -164,7 +164,7 @@ describe('database.js', () => {
|
|||||||
expect(progressHandler.secondCall.calledWith(100)).to.equal(true)
|
expect(progressHandler.secondCall.calledWith(100)).to.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('createDb throws errors', async () => {
|
it('importDb throws errors', async () => {
|
||||||
const data = {
|
const data = {
|
||||||
columns: ['id', 'name'],
|
columns: ['id', 'name'],
|
||||||
values: [
|
values: [
|
||||||
@@ -174,7 +174,7 @@ describe('database.js', () => {
|
|||||||
}
|
}
|
||||||
const progressHandler = sinon.stub()
|
const progressHandler = sinon.stub()
|
||||||
const progressCounterId = db.createProgressCounter(progressHandler)
|
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')
|
.to.be.rejectedWith('column index out of range')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import storedQueries from '@/storedQueries.js'
|
import storedQueries from '@/lib/storedQueries'
|
||||||
import fu from '@/file.utils'
|
import fu from '@/lib/utils/fileIo'
|
||||||
|
|
||||||
describe('storedQueries.js', () => {
|
describe('storedQueries.js', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import fu from '@/file.utils'
|
import fu from '@/lib/utils/fileIo'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
|
|
||||||
describe('file.utils.js', () => {
|
describe('fileIo.js', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sinon.restore()
|
sinon.restore()
|
||||||
})
|
})
|
||||||
@@ -105,4 +105,27 @@ describe('file.utils.js', () => {
|
|||||||
const blob = new Blob(['foo'])
|
const blob = new Blob(['foo'])
|
||||||
await expect(fu.readAsArrayBuffer(blob)).to.be.rejectedWith('Problem parsing input file.')
|
await expect(fu.readAsArrayBuffer(blob)).to.be.rejectedWith('Problem parsing input file.')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('isDatabase', () => {
|
||||||
|
let file = { type: 'application/vnd.sqlite3' }
|
||||||
|
expect(fu.isDatabase(file)).to.equal(true)
|
||||||
|
|
||||||
|
file = { type: 'application/x-sqlite3' }
|
||||||
|
expect(fu.isDatabase(file)).to.equal(true)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.db' }
|
||||||
|
expect(fu.isDatabase(file)).to.equal(true)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.sqlite' }
|
||||||
|
expect(fu.isDatabase(file)).to.equal(true)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.sqlite3' }
|
||||||
|
expect(fu.isDatabase(file)).to.equal(true)
|
||||||
|
|
||||||
|
file = { type: '', name: 'test.csv' }
|
||||||
|
expect(fu.isDatabase(file)).to.equal(false)
|
||||||
|
|
||||||
|
file = { type: 'text', name: 'test.db' }
|
||||||
|
expect(fu.isDatabase(file)).to.equal(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import time from '@/time'
|
import time from '@/lib/utils/time'
|
||||||
|
|
||||||
describe('time.js', () => {
|
describe('time.js', () => {
|
||||||
it('getPeriod', () => {
|
it('getPeriod', () => {
|
||||||
// 1.01.2021 13:00:00 000
|
// 1.01.2021 13:00:00 000
|
||||||
let start = new Date(2021, 0, 1, 13, 0, 0, 0)
|
let start = new Date(2021, 0, 1, 13, 0, 0, 0)
|
||||||
|
|
||||||
// 3.01.2021 22:15:20 500
|
// 1.01.2021 13:01:00 500
|
||||||
let end = new Date(2021, 0, 3, 22, 15, 20, 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
|
// 1.01.2021 13:00:00 000
|
||||||
start = new Date(2021, 0, 1, 13, 0, 0, 0)
|
start = new Date(2021, 0, 1, 13, 0, 0, 0)
|
||||||
|
|
||||||
// 1.01.2021 22:00:20 000
|
// 1.01.2021 13:00:20 500
|
||||||
end = new Date(2021, 0, 1, 22, 0, 20, 0)
|
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
|
// 1.01.2021 13:00:00 000
|
||||||
start = new Date(2021, 0, 1, 13, 0, 0, 0)
|
start = new Date(2021, 0, 1, 13, 0, 0, 0)
|
||||||
@@ -25,6 +25,6 @@ describe('time.js', () => {
|
|||||||
// 1.01.2021 13:00:00 45
|
// 1.01.2021 13:00:00 45
|
||||||
end = new Date(2021, 0, 1, 13, 0, 0, 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')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
67
tests/store/actions.spec.js
Normal file
67
tests/store/actions.spec.js
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { mutations, actions } from '@/store'
|
import mutations from '@/store/mutations'
|
||||||
const {
|
const {
|
||||||
saveSchema,
|
saveSchema,
|
||||||
updateTab,
|
updateTab,
|
||||||
@@ -11,8 +11,6 @@ const {
|
|||||||
setDb
|
setDb
|
||||||
} = mutations
|
} = mutations
|
||||||
|
|
||||||
const { addTab } = actions
|
|
||||||
|
|
||||||
describe('mutations', () => {
|
describe('mutations', () => {
|
||||||
it('setDb', () => {
|
it('setDb', () => {
|
||||||
const state = {
|
const state = {
|
||||||
@@ -376,66 +374,3 @@ describe('mutations', () => {
|
|||||||
expect(state.predefinedQueries).to.eql(queries)
|
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { mount } from '@vue/test-utils'
|
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', () => {
|
it('tooltip is hidden in initial', () => {
|
||||||
const component = {
|
const component = {
|
||||||
template: '<div :style="tooltipStyle"></div>',
|
template: '<div :style="tooltipStyle"></div>',
|
||||||
@@ -2,8 +2,8 @@ import { expect } from 'chai'
|
|||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { mount, createLocalVue } from '@vue/test-utils'
|
import { mount, createLocalVue } from '@vue/test-utils'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import Schema from '@/components/Schema.vue'
|
import Schema from '@/views/Main/Editor/Schema'
|
||||||
import TableDescription from '@/components/TableDescription.vue'
|
import TableDescription from '@/views/Main/Editor/Schema/TableDescription'
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
const localVue = createLocalVue()
|
||||||
localVue.use(Vuex)
|
localVue.use(Vuex)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { shallowMount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import TableDescription from '@/components/TableDescription.vue'
|
import TableDescription from '@/views/Main/Editor/Schema/TableDescription'
|
||||||
|
|
||||||
describe('TableDescription.vue', () => {
|
describe('TableDescription.vue', () => {
|
||||||
it('Initially the columns are hidden and table name is rendered', () => {
|
it('Initially the columns are hidden and table name is rendered', () => {
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { mount, shallowMount } from '@vue/test-utils'
|
import { mount, shallowMount } from '@vue/test-utils'
|
||||||
import Chart from '@/components/Chart.vue'
|
import Chart from '@/views/Main/Editor/Tabs/Tab/Chart'
|
||||||
import chart from '@/chart.js'
|
import chartHelper from '@/views/Main/Editor/Tabs/Tab/Chart/chartHelper'
|
||||||
import * as dereference from 'react-chart-editor/lib/lib/dereference'
|
import * as dereference from 'react-chart-editor/lib/lib/dereference'
|
||||||
|
|
||||||
describe('Chart.vue', () => {
|
describe('Chart.vue', () => {
|
||||||
@@ -14,7 +14,7 @@ describe('Chart.vue', () => {
|
|||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = shallowMount(Chart)
|
const wrapper = shallowMount(Chart)
|
||||||
const vm = wrapper.vm
|
const vm = wrapper.vm
|
||||||
const stub = sinon.stub(chart, 'getChartStateForSave').returns('result')
|
const stub = sinon.stub(chartHelper, 'getChartStateForSave').returns('result')
|
||||||
const chartData = vm.getChartStateForSave()
|
const chartData = vm.getChartStateForSave()
|
||||||
expect(stub.calledOnceWith(vm.state, vm.dataSources)).to.equal(true)
|
expect(stub.calledOnceWith(vm.state, vm.dataSources)).to.equal(true)
|
||||||
expect(chartData).to.equal('result')
|
expect(chartData).to.equal('result')
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
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'
|
import * as dereference from 'react-chart-editor/lib/lib/dereference'
|
||||||
|
|
||||||
describe('chart.js', () => {
|
describe('chartHelper.js', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sinon.restore()
|
sinon.restore()
|
||||||
})
|
})
|
||||||
@@ -17,7 +17,7 @@ describe('chart.js', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const ds = chart.getDataSourcesFromSqlResult(sqlResult)
|
const ds = chartHelper.getDataSourcesFromSqlResult(sqlResult)
|
||||||
expect(ds).to.eql({
|
expect(ds).to.eql({
|
||||||
id: [1, 2],
|
id: [1, 2],
|
||||||
name: ['foo', 'bar']
|
name: ['foo', 'bar']
|
||||||
@@ -30,7 +30,7 @@ describe('chart.js', () => {
|
|||||||
name: ['foo', 'bar']
|
name: ['foo', 'bar']
|
||||||
}
|
}
|
||||||
|
|
||||||
const ds = chart.getOptionsFromDataSources(dataSources)
|
const ds = chartHelper.getOptionsFromDataSources(dataSources)
|
||||||
expect(ds).to.eql([
|
expect(ds).to.eql([
|
||||||
{ value: 'id', label: 'id' },
|
{ value: 'id', label: 'id' },
|
||||||
{ value: 'name', label: 'name' }
|
{ value: 'name', label: 'name' }
|
||||||
@@ -53,7 +53,7 @@ describe('chart.js', () => {
|
|||||||
sinon.stub(dereference, 'default')
|
sinon.stub(dereference, 'default')
|
||||||
sinon.spy(JSON, 'parse')
|
sinon.spy(JSON, 'parse')
|
||||||
|
|
||||||
const ds = chart.getChartStateForSave(state, dataSources)
|
const ds = chartHelper.getChartStateForSave(state, dataSources)
|
||||||
|
|
||||||
expect(dereference.default.calledOnce).to.equal(true)
|
expect(dereference.default.calledOnce).to.equal(true)
|
||||||
|
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import SqlEditor from '@/components/SqlEditor.vue'
|
import SqlEditor from '@/views/Main/Editor/Tabs/Tab/SqlEditor'
|
||||||
|
|
||||||
describe('SqlEditor.vue', () => {
|
describe('SqlEditor.vue', () => {
|
||||||
it('Emits input event when a query is changed', async () => {
|
it('Emits input event when a query is changed', async () => {
|
||||||
const wrapper = mount(SqlEditor)
|
const wrapper = mount(SqlEditor)
|
||||||
await wrapper.findComponent({ name: 'codemirror' }).vm.$emit('input', 'SELECT * FROM foo')
|
await wrapper.findComponent({ name: 'codemirror' }).vm.$emit('input', 'SELECT * FROM foo')
|
||||||
expect(wrapper.emitted('input')[0]).to.eql(['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'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { state } from '@/store'
|
import state from '@/store/state'
|
||||||
import hint, { getHints } from '@/hint'
|
import showHint, { getHints } from '@/views/Main/Editor/Tabs/Tab/SqlEditor/hint'
|
||||||
import CM from 'codemirror'
|
import CM from 'codemirror'
|
||||||
|
|
||||||
describe('hint.js', () => {
|
describe('hint.js', () => {
|
||||||
@@ -40,9 +40,7 @@ describe('hint.js', () => {
|
|||||||
getCursor: sinon.stub()
|
getCursor: sinon.stub()
|
||||||
}
|
}
|
||||||
|
|
||||||
const clock = sinon.useFakeTimers()
|
showHint(editor)
|
||||||
hint.show(editor)
|
|
||||||
clock.tick(500)
|
|
||||||
|
|
||||||
expect(CM.showHint.called).to.equal(true)
|
expect(CM.showHint.called).to.equal(true)
|
||||||
expect(CM.showHint.firstCall.args[2].tables).to.eql({
|
expect(CM.showHint.firstCall.args[2].tables).to.eql({
|
||||||
@@ -77,10 +75,7 @@ describe('hint.js', () => {
|
|||||||
getCursor: sinon.stub()
|
getCursor: sinon.stub()
|
||||||
}
|
}
|
||||||
|
|
||||||
const clock = sinon.useFakeTimers()
|
showHint(editor)
|
||||||
hint.show(editor)
|
|
||||||
clock.tick(500)
|
|
||||||
|
|
||||||
expect(CM.showHint.firstCall.args[2].defaultTable).to.equal('foo')
|
expect(CM.showHint.firstCall.args[2].defaultTable).to.equal('foo')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -97,10 +92,7 @@ describe('hint.js', () => {
|
|||||||
getCursor: sinon.stub()
|
getCursor: sinon.stub()
|
||||||
}
|
}
|
||||||
|
|
||||||
const clock = sinon.useFakeTimers()
|
showHint(editor)
|
||||||
hint.show(editor)
|
|
||||||
clock.tick(500)
|
|
||||||
|
|
||||||
expect(CM.showHint.called).to.equal(false)
|
expect(CM.showHint.called).to.equal(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -117,10 +109,7 @@ describe('hint.js', () => {
|
|||||||
getCursor: sinon.stub()
|
getCursor: sinon.stub()
|
||||||
}
|
}
|
||||||
|
|
||||||
const clock = sinon.useFakeTimers()
|
showHint(editor)
|
||||||
hint.show(editor)
|
|
||||||
clock.tick(500)
|
|
||||||
|
|
||||||
expect(CM.showHint.called).to.equal(false)
|
expect(CM.showHint.called).to.equal(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -137,10 +126,7 @@ describe('hint.js', () => {
|
|||||||
getCursor: sinon.stub()
|
getCursor: sinon.stub()
|
||||||
}
|
}
|
||||||
|
|
||||||
const clock = sinon.useFakeTimers()
|
showHint(editor)
|
||||||
hint.show(editor)
|
|
||||||
clock.tick(500)
|
|
||||||
|
|
||||||
expect(CM.showHint.called).to.equal(false)
|
expect(CM.showHint.called).to.equal(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -218,10 +204,7 @@ describe('hint.js', () => {
|
|||||||
getCursor: sinon.stub()
|
getCursor: sinon.stub()
|
||||||
}
|
}
|
||||||
|
|
||||||
const clock = sinon.useFakeTimers()
|
showHint(editor)
|
||||||
hint.show(editor)
|
|
||||||
clock.tick(500)
|
|
||||||
|
|
||||||
expect(CM.showHint.called).to.equal(true)
|
expect(CM.showHint.called).to.equal(true)
|
||||||
expect(CM.showHint.firstCall.args[2].tables).to.eql({})
|
expect(CM.showHint.firstCall.args[2].tables).to.eql({})
|
||||||
})
|
})
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import { mutations } from '@/store'
|
import mutations from '@/store/mutations'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import Tab from '@/components/Tab.vue'
|
import Tab from '@/views/Main/Editor/Tabs/Tab'
|
||||||
|
|
||||||
describe('Tab.vue', () => {
|
describe('Tab.vue', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -202,9 +202,9 @@ describe('Tab.vue', () => {
|
|||||||
|
|
||||||
await wrapper.vm.execute()
|
await wrapper.vm.execute()
|
||||||
expect(wrapper.find('.table-view .result-before').isVisible()).to.equal(false)
|
expect(wrapper.find('.table-view .result-before').isVisible()).to.equal(false)
|
||||||
expect(wrapper.find('.table-view .result-in-progress').isVisible()).to.equal(false)
|
expect(wrapper.find('.table-view .result-in-progress').exists()).to.equal(false)
|
||||||
expect(wrapper.find('.table-preview.error').isVisible()).to.equal(true)
|
expect(wrapper.findComponent({ name: 'logs' }).isVisible()).to.equal(true)
|
||||||
expect(wrapper.find('.table-preview.error').text()).to.include('There is no table foo')
|
expect(wrapper.findComponent({ name: 'logs' }).text()).to.include('There is no table foo')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Passes result to sql-table component', async () => {
|
it('Passes result to sql-table component', async () => {
|
||||||
@@ -242,8 +242,8 @@ describe('Tab.vue', () => {
|
|||||||
|
|
||||||
await wrapper.vm.execute()
|
await wrapper.vm.execute()
|
||||||
expect(wrapper.find('.table-view .result-before').isVisible()).to.equal(false)
|
expect(wrapper.find('.table-view .result-before').isVisible()).to.equal(false)
|
||||||
expect(wrapper.find('.table-view .result-in-progress').isVisible()).to.equal(false)
|
expect(wrapper.find('.table-view .result-in-progress').exists()).to.equal(false)
|
||||||
expect(wrapper.find('.table-preview.error').isVisible()).to.equal(false)
|
expect(wrapper.findComponent({ name: 'logs' }).exists()).to.equal(false)
|
||||||
expect(wrapper.findComponent({ name: 'SqlTable' }).vm.dataSet).to.eql(result)
|
expect(wrapper.findComponent({ name: 'SqlTable' }).vm.dataSet).to.eql(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { shallowMount, mount, createWrapper } from '@vue/test-utils'
|
import { shallowMount, mount, createWrapper } from '@vue/test-utils'
|
||||||
import { mutations } from '@/store'
|
import mutations from '@/store/mutations'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import Tabs from '@/components/Tabs.vue'
|
import Tabs from '@/views/Main/Editor/Tabs'
|
||||||
|
|
||||||
describe('Tabs.vue', () => {
|
describe('Tabs.vue', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -2,8 +2,8 @@ import { expect } from 'chai'
|
|||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { mount, shallowMount, createWrapper } from '@vue/test-utils'
|
import { mount, shallowMount, createWrapper } from '@vue/test-utils'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import MainMenu from '@/components/MainMenu.vue'
|
import MainMenu from '@/views/Main/MainMenu'
|
||||||
import storedQueries from '@/storedQueries.js'
|
import storedQueries from '@/lib/storedQueries'
|
||||||
|
|
||||||
let wrapper = null
|
let wrapper = null
|
||||||
|
|
||||||
@@ -2,10 +2,10 @@ import { expect } from 'chai'
|
|||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { mount, shallowMount } from '@vue/test-utils'
|
import { mount, shallowMount } from '@vue/test-utils'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import MyQueries from '@/views/MyQueries.vue'
|
import MyQueries from '@/views/Main/MyQueries'
|
||||||
import storedQueries from '@/storedQueries'
|
import storedQueries from '@/lib/storedQueries'
|
||||||
import { mutations } from '@/store'
|
import mutations from '@/store/mutations'
|
||||||
import fu from '@/file.utils'
|
import fu from '@/lib/utils/fileIo'
|
||||||
|
|
||||||
describe('MyQueries.vue', () => {
|
describe('MyQueries.vue', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -31,11 +31,11 @@ module.exports = {
|
|||||||
|
|
||||||
config.module
|
config.module
|
||||||
.rule('worker')
|
.rule('worker')
|
||||||
.test(/\.worker\.js$/)
|
.test(/worker\.js$/)
|
||||||
.use('worker-loader')
|
.use('worker-loader')
|
||||||
.loader('worker-loader')
|
.loader('worker-loader')
|
||||||
.end()
|
.end()
|
||||||
|
|
||||||
config.module.rule('js').exclude.add(/\.worker\.js$/)
|
config.module.rule('js').exclude.add(/worker\.js$/)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user