1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2025-12-06 18:18:53 +08:00
This commit is contained in:
lana-k
2025-03-20 22:04:15 +01:00
parent 5e2b34a856
commit 0c1b91ab2f
146 changed files with 3317 additions and 2438 deletions

View File

@@ -2,12 +2,9 @@ module.exports = {
root: true, root: true,
env: { env: {
node: true, node: true,
es2022: true, es2022: true
}, },
extends: [ extends: ['eslint:recommended', 'plugin:vue/vue3-recommended', 'prettier'],
'plugin:vue/essential',
'@vue/standard'
],
rules: { rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
@@ -20,10 +17,7 @@ module.exports = {
}, },
overrides: [ overrides: [
{ {
files: [ files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/**/*.spec.{j,t}s?(x)'],
'**/__tests__/*.{j,t}s?(x)',
'**/tests/**/*.spec.{j,t}s?(x)'
],
env: { env: {
mocha: true mocha: true
} }

View File

@@ -1,17 +1,14 @@
module.exports = { module.exports = {
dataSource: 'milestones', dataSource: 'milestones',
ignoreIssuesWith: [ ignoreIssuesWith: ['wontfix', 'duplicate'],
'wontfix',
'duplicate'
],
milestoneMatch: 'v{{tag_name}}', milestoneMatch: 'v{{tag_name}}',
template: { template: {
issue: '- {{name}} [{{text}}]({{url}})', issue: '- {{name}} [{{text}}]({{url}})',
changelogTitle: "", changelogTitle: '',
release: "{{body}}", release: '{{body}}'
}, },
groupBy: { groupBy: {
'Enhancements': ["enhancement", "internal"], Enhancements: ['enhancement', 'internal'],
'Bug fixes': ["bug"] 'Bug fixes': ['bug']
} }
} }

View File

@@ -40,6 +40,6 @@ jobs:
- name: Create release - name: Create release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
artifacts: "dist.zip,dist_map.zip" artifacts: 'dist.zip,dist_map.zip'
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
bodyFile: "CHANGELOG.md" bodyFile: 'CHANGELOG.md'

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"arrowParens": "avoid"
}

View File

@@ -8,6 +8,7 @@ Sqliteviz is a single-page offline-first PWA for fully client-side visualisation
of SQLite databases, CSV, JSON or NDJSON files. of SQLite databases, CSV, JSON or NDJSON files.
With sqliteviz you can: With sqliteviz you can:
- run SQL queries against a SQLite database and create [Plotly][11] charts and pivot tables based on the result sets - run SQL queries against a SQLite database and create [Plotly][11] charts and pivot tables based on the result sets
- import a CSV/JSON/NDJSON file into a SQLite database and visualize imported data - import a CSV/JSON/NDJSON file into a SQLite database and visualize imported data
- export result set to CSV file - export result set to CSV file
@@ -19,15 +20,19 @@ With sqliteviz you can:
https://user-images.githubusercontent.com/24638357/128249848-f8fab0f5-9add-46e0-a9c1-dd5085a8623e.mp4 https://user-images.githubusercontent.com/24638357/128249848-f8fab0f5-9add-46e0-a9c1-dd5085a8623e.mp4
## Quickstart ## Quickstart
The latest release of sqliteviz is deployed on [sqliteviz.com/app][6]. The latest release of sqliteviz is deployed on [sqliteviz.com/app][6].
## Wiki ## Wiki
For user documentation, check out sqliteviz [documentation][7]. For user documentation, check out sqliteviz [documentation][7].
## Motivation ## Motivation
It's a kind of middleground between [Plotly Falcon][1] and [Redash][2]. It's a kind of middleground between [Plotly Falcon][1] and [Redash][2].
## Components ## Components
It is built on top of [react-chart-editor][3], [PivotTable.js][12], [sql.js][4] and [Vue-Codemirror][8] in [Vue.js][5]. CSV parsing is performed with [Papa Parse][9]. It is built on top of [react-chart-editor][3], [PivotTable.js][12], [sql.js][4] and [Vue-Codemirror][8] in [Vue.js][5]. CSV parsing is performed with [Papa Parse][9].
[1]: https://github.com/plotly/falcon [1]: https://github.com/plotly/falcon

View File

@@ -1,5 +1,3 @@
module.exports = { module.exports = {
presets: [ presets: ['@vue/cli-plugin-babel/preset']
'@vue/cli-plugin-babel/preset'
]
} }

View File

@@ -1,11 +1,11 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="favicon.png"> <link rel="icon" href="favicon.png" />
<link rel="manifest" href="manifest.webmanifest"> <link rel="manifest" href="manifest.webmanifest" />
<title>sqliteviz</title> <title>sqliteviz</title>
<style> <style>
#sqliteviz-loading-wrapper { #sqliteviz-loading-wrapper {
@@ -38,15 +38,18 @@
#sqliteviz-loading-wrapper circle { #sqliteviz-loading-wrapper circle {
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: 5px; stroke-width: 5px;
stroke-linecap: round; stroke-linecap: round;
stroke: #119DFF; stroke: #119dff;
} }
#sqliteviz-loading-wrapper circle.bg { #sqliteviz-loading-wrapper circle.bg {
stroke: #C8D4E3; stroke: #c8d4e3;
} }
#sqliteviz-loading-wrapper circle.front { #sqliteviz-loading-wrapper circle.front {
@@ -74,24 +77,17 @@
</head> </head>
<body> <body>
<noscript> <noscript>
<strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> <strong>
We're sorry but this app doesn't work properly without JavaScript
enabled. Please enable it to continue.
</strong>
</noscript> </noscript>
<div id="app"> <div id="app">
<div id="sqliteviz-loading-wrapper"> <div id="sqliteviz-loading-wrapper">
<div id="sqliteviz-loading-text">LOADING</div> <div id="sqliteviz-loading-text">LOADING</div>
<svg height="170" width="170" viewBox="0 0 170 170"> <svg height="170" width="170" viewBox="0 0 170 170">
<circle <circle class="bg" cx="85" cy="85" r="80" />
class="bg" <circle class="front" cx="85" cy="85" r="80" />
cx="85"
cy="85"
r="80"
/>
<circle
class="front"
cx="85"
cy="85"
r="80"
/>
</svg> </svg>
</div> </div>
</div> </div>

10
jsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@*": ["./src/*"]
}
}
}

View File

@@ -4,7 +4,7 @@ module.exports = function (config) {
config: { config: {
resolve: { resolve: {
alias: { alias: {
'vue': 'vue/dist/vue.esm-bundler.js' vue: 'vue/dist/vue.esm-bundler.js'
} }
}, },
server: { server: {
@@ -32,19 +32,19 @@ module.exports = function (config) {
pattern: 'test.setup.js', pattern: 'test.setup.js',
type: 'module', type: 'module',
watched: false, watched: false,
served: false, served: false
}, },
{ {
pattern: 'tests/**/*.spec.js', pattern: 'tests/**/*.spec.js',
type: 'module', type: 'module',
watched: false, watched: false,
served: false, served: false
}, },
{ {
pattern: 'src/assets/styles/*.css', pattern: 'src/assets/styles/*.css',
type: 'css', type: 'css',
watched: false, watched: false,
served: false, served: false
} }
], ],

View File

@@ -1,12 +1,15 @@
module.exports = function (config) { module.exports = function (config) {
const timeout = 15 * 60 * 1000 const timeout = 15 * 60 * 1000
config.set({ config.set({
frameworks: ['mocha'], frameworks: ['mocha'],
files: [ files: [
'suite.js', 'suite.js',
{ pattern: 'node_modules/sql.js/dist/sql-wasm.wasm', served: true, included: false }, {
pattern: 'node_modules/sql.js/dist/sql-wasm.wasm',
served: true,
included: false
},
{ pattern: 'sample.csv', served: true, included: false } { pattern: 'sample.csv', served: true, included: false }
], ],
@@ -15,7 +18,10 @@ module.exports = function (config) {
singleRun: true, singleRun: true,
customLaunchers: { customLaunchers: {
ChromiumHeadlessNoSandbox: { base: 'ChromiumHeadless', flags: ['--no-sandbox'] } ChromiumHeadlessNoSandbox: {
base: 'ChromiumHeadless',
flags: ['--no-sandbox']
}
}, },
browsers: ['ChromiumHeadlessNoSandbox', 'FirefoxHeadless'], browsers: ['ChromiumHeadlessNoSandbox', 'FirefoxHeadless'],
concurrency: 1, concurrency: 1,
@@ -47,6 +53,5 @@ module.exports = function (config) {
}, },
jsonToFileReporter: { outputPath: '.', fileName: 'suite-result.json' } jsonToFileReporter: { outputPath: '.', fileName: 'suite-result.json' }
}) })
} }

View File

@@ -4,7 +4,6 @@ import lodash from 'lodash'
import Papa from 'papaparse' import Papa from 'papaparse'
import useragent from 'ua-parser-js' import useragent from 'ua-parser-js'
describe('SQLite build benchmark', function () { describe('SQLite build benchmark', function () {
let parsedCsv let parsedCsv
let sqlModule let sqlModule
@@ -50,10 +49,8 @@ describe('SQLite build benchmark', function () {
suite.add('select', { initCount: 3, minSamples: 50, fn: benchmarkSelect }) suite.add('select', { initCount: 3, minSamples: 50, fn: benchmarkSelect })
await run(suite) await run(suite)
}) })
}) })
function importToTable(db, parsedCsv, chunkSize = 1024) { function importToTable(db, parsedCsv, chunkSize = 1024) {
const columnListString = parsedCsv.meta.fields.join(', ') const columnListString = parsedCsv.meta.fields.join(', ')
db.exec(`CREATE TABLE csv_import(${columnListString})`) db.exec(`CREATE TABLE csv_import(${columnListString})`)
@@ -67,7 +64,6 @@ function importToTable (db, parsedCsv, chunkSize = 1024) {
}) })
} }
class PromiseWrapper { class PromiseWrapper {
constructor() { constructor() {
this.promise = new Promise((resolve, reject) => { this.promise = new Promise((resolve, reject) => {
@@ -102,7 +98,6 @@ function chunkArray (arr, size) {
}, []) }, [])
} }
function createSuite() { function createSuite() {
// Combined workaround from: // Combined workaround from:
// - https://github.com/bestiejs/benchmark.js/issues/106 // - https://github.com/bestiejs/benchmark.js/issues/106
@@ -124,10 +119,12 @@ function run (suite) {
console.info(String(event.target)) console.info(String(event.target))
}) })
.on('complete', function () { .on('complete', function () {
console.log(JSON.stringify({ console.log(
JSON.stringify({
browser: useragent(navigator.userAgent).browser, browser: useragent(navigator.userAgent).browser,
result: this.filter('successful') result: this.filter('successful')
})) })
)
suiteResult.resolve() suiteResult.resolve()
}) })
.on('error', function (event) { .on('error', function (event) {

58
package-lock.json generated
View File

@@ -42,6 +42,7 @@
"chai": "^4.1.2", "chai": "^4.1.2",
"chai-as-promised": "^8.0.1", "chai-as-promised": "^8.0.1",
"eslint": "^8.57.1", "eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
@@ -56,6 +57,7 @@
"karma-spec-reporter": "^0.0.36", "karma-spec-reporter": "^0.0.36",
"karma-vite": "^1.0.5", "karma-vite": "^1.0.5",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"prettier": "3.5.3",
"process": "^0.11.10", "process": "^0.11.10",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vite": "^5.4.14", "vite": "^5.4.14",
@@ -3421,6 +3423,22 @@
"url": "https://opencollective.com/postcss/" "url": "https://opencollective.com/postcss/"
} }
}, },
"node_modules/@vue/component-compiler-utils/node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"optional": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/@vue/component-compiler-utils/node_modules/yallist": { "node_modules/@vue/component-compiler-utils/node_modules/yallist": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
@@ -7967,6 +7985,18 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint-config-prettier": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz",
"integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-import-resolver-custom-alias": { "node_modules/eslint-import-resolver-custom-alias": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-custom-alias/-/eslint-import-resolver-custom-alias-1.3.2.tgz", "resolved": "https://registry.npmjs.org/eslint-import-resolver-custom-alias/-/eslint-import-resolver-custom-alias-1.3.2.tgz",
@@ -14788,16 +14818,15 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "2.8.8", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true, "dev": true,
"optional": true,
"bin": { "bin": {
"prettier": "bin-prettier.js" "prettier": "bin/prettier.cjs"
}, },
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=14"
}, },
"funding": { "funding": {
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
@@ -20029,6 +20058,23 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/vue-cli-plugin-ui-karma/node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"optional": true,
"peer": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/vue-cli-plugin-ui-karma/node_modules/read-pkg": { "node_modules/vue-cli-plugin-ui-karma/node_modules/read-pkg": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",

View File

@@ -9,7 +9,8 @@
"build": "vite build", "build": "vite build",
"serve": "vite preview", "serve": "vite preview",
"test": "karma start karma.conf.cjs", "test": "karma start karma.conf.cjs",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src" "lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
"format": "prettier . --write"
}, },
"dependencies": { "dependencies": {
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -45,6 +46,7 @@
"chai": "^4.1.2", "chai": "^4.1.2",
"chai-as-promised": "^8.0.1", "chai-as-promised": "^8.0.1",
"eslint": "^8.57.1", "eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
@@ -59,6 +61,7 @@
"karma-spec-reporter": "^0.0.36", "karma-spec-reporter": "^0.0.36",
"karma-vite": "^1.0.5", "karma-vite": "^1.0.5",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"prettier": "3.5.3",
"process": "^0.11.10", "process": "^0.11.10",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vite": "^5.4.14", "vite": "^5.4.14",

View File

@@ -32,43 +32,43 @@ export default {
<style> <style>
@font-face { @font-face {
font-family: "Open Sans"; font-family: 'Open Sans';
src: url("@/assets/fonts/OpenSans-Regular.woff2"); src: url('@/assets/fonts/OpenSans-Regular.woff2');
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: "Open Sans"; font-family: 'Open Sans';
src: url("@/assets/fonts/OpenSans-SemiBold.woff2"); src: url('@/assets/fonts/OpenSans-SemiBold.woff2');
font-weight: 600; font-weight: 600;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: "Open Sans"; font-family: 'Open Sans';
src: url("@/assets/fonts/OpenSans-Bold.woff2"); src: url('@/assets/fonts/OpenSans-Bold.woff2');
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: "Open Sans"; font-family: 'Open Sans';
src: url("@/assets/fonts/OpenSans-Italic.woff2"); src: url('@/assets/fonts/OpenSans-Italic.woff2');
font-weight: 400; font-weight: 400;
font-style: italic; font-style: italic;
} }
@font-face { @font-face {
font-family: "Open Sans"; font-family: 'Open Sans';
src: url("@/assets/fonts/OpenSans-SemiBoldItalic.woff2"); src: url('@/assets/fonts/OpenSans-SemiBoldItalic.woff2');
font-weight: 600; font-weight: 600;
font-style: italic; font-style: italic;
} }
@font-face { @font-face {
font-family: "Open Sans"; font-family: 'Open Sans';
src: url("@/assets/fonts/OpenSans-BoldItalic.woff2"); src: url('@/assets/fonts/OpenSans-BoldItalic.woff2');
font-weight: 700; font-weight: 700;
font-style: italic; font-style: italic;
} }
@@ -80,7 +80,7 @@ label,
button, button,
.plotly_editor *, .plotly_editor *,
.CodeMirror pre.CodeMirror-line { .CodeMirror pre.CodeMirror-line {
font-family: "Open Sans", Helvetica, Arial, sans-serif; font-family: 'Open Sans', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }

View File

@@ -59,5 +59,3 @@ button.secondary:disabled {
text-shadow: none; text-shadow: none;
cursor: default; cursor: default;
} }

View File

@@ -59,7 +59,8 @@ table.sqliteviz-table {
margin-top: -35px; margin-top: -35px;
border-collapse: collapse; border-collapse: collapse;
} }
.sqliteviz-table thead th, .fixed-header { .sqliteviz-table thead th,
.fixed-header {
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
box-sizing: border-box; box-sizing: border-box;
@@ -108,8 +109,8 @@ table.sqliteviz-table {
color: var(--color-text-base); color: var(--color-text-base);
} }
.sqliteviz-table tbody td[data-isNull="true"], .sqliteviz-table tbody td[data-isNull='true'],
.sqliteviz-table tbody td[data-isBlob="true"] { .sqliteviz-table tbody td[data-isBlob='true'] {
color: var(--color-text-light-2); color: var(--color-text-light-2);
font-style: italic; font-style: italic;
} }

View File

@@ -4,7 +4,7 @@
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
padding: 0 6px; padding: 0 6px;
line-height: 19px;; line-height: 19px;
position: fixed; position: fixed;
height: 19px; height: 19px;
border-radius: var(--border-radius-medium); border-radius: var(--border-radius-medium);

View File

@@ -1,21 +1,19 @@
:root { :root {
--color-white: #ffffff; --color-white: #ffffff;
--color-gray-light: #F3F6FA; --color-gray-light: #f3f6fa;
--color-gray-light-2: #DFE8F3; --color-gray-light-2: #dfe8f3;
--color-gray-light-3: #C8D4E3; --color-gray-light-3: #c8d4e3;
--color-gray-light-4:#EBF0F8; --color-gray-light-4: #ebf0f8;
--color-gray-light-5: #f8f8f9; --color-gray-light-5: #f8f8f9;
--color-gray-medium: #A2B1C6; --color-gray-medium: #a2b1c6;
--color-gray-dark: #506784; --color-gray-dark: #506784;
--color-blue-medium: #119DFF; --color-blue-medium: #119dff;
--color-blue-dark: #0D76BF; --color-blue-dark: #0d76bf;
--color-blue-dark-2: #2A3F5F; --color-blue-dark-2: #2a3f5f;
--color-red: #EF553B; --color-red: #ef553b;
--color-red-2: #DE350B; --color-red-2: #de350b;
--color-red-light: #FFBDAD; --color-red-light: #ffbdad;
--color-yellow: #FBEFCB; --color-yellow: #fbefcb;
--color-bg-light: var(--color-gray-light); --color-bg-light: var(--color-gray-light);
--color-bg-light-2: var(--color-gray-light-2); --color-bg-light-2: var(--color-gray-light-2);
@@ -48,6 +46,3 @@
.plotly-editor--theme-provider { .plotly-editor--theme-provider {
--sidebar-width: 112px; --sidebar-width: 112px;
} }

View File

@@ -1,6 +1,10 @@
<template> <template>
<div <div
:class="['checkbox-container', { 'checked': checked }, {'disabled': disabled}]" :class="[
'checkbox-container',
{ checked: checked },
{ disabled: disabled }
]"
@click.stop="onClick" @click.stop="onClick"
> >
<div v-show="!checked" class="unchecked" /> <div v-show="!checked" class="unchecked" />
@@ -31,7 +35,7 @@ export default {
type: String, type: String,
required: false, required: false,
default: 'accent', default: 'accent',
validator: (value) => { validator: value => {
return ['accent', 'light'].includes(value) return ['accent', 'light'].includes(value)
} }
}, },

View File

@@ -1,5 +1,5 @@
<template> <template>
<div :class="{ 'disabled': disabled }"> <div :class="{ disabled: disabled }">
<div class="text-field-label">Delimiter</div> <div class="text-field-label">Delimiter</div>
<div <div
class="delimiter-selector-container" class="delimiter-selector-container"
@@ -8,7 +8,7 @@
> >
<div class="value"> <div class="value">
<input <input
:class="{ 'filled': filled }" :class="{ filled: filled }"
ref="delimiterInput" ref="delimiterInput"
type="text" type="text"
maxlength="1" maxlength="1"
@@ -33,7 +33,8 @@
@click="chooseOption(option)" @click="chooseOption(option)"
class="option" class="option"
> >
<pre>{{option}}</pre><div>{{ getSymbolName(option) }}</div> <pre>{{ option }}</pre>
<div>{{ getSymbolName(option) }}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -66,10 +66,7 @@
class="preview-table" class="preview-table"
/> />
<div v-else class="no-data">No data</div> <div v-else class="no-data">No data</div>
<logs <logs class="import-errors" :messages="importMessages" />
class="import-errors"
:messages="importMessages"
/>
</div> </div>
<div class="dialog-buttons-container"> <div class="dialog-buttons-container">
<button <button
@@ -175,8 +172,7 @@ export default {
if (!this.tableName) { if (!this.tableName) {
return return
} }
this.db.validateTableName(this.tableName) this.db.validateTableName(this.tableName).catch(err => {
.catch(err => {
this.tableNameError = err.message + '. Try another table name.' this.tableNameError = err.message + '. Try another table name.'
}) })
}, 400) }, 400)
@@ -257,10 +253,12 @@ export default {
} }
} catch (err) { } catch (err) {
console.error(err) console.error(err)
this.importMessages = [{ this.importMessages = [
{
message: err, message: err,
type: 'error' type: 'error'
}] }
]
} }
}, },
async getJsonParseResult(file) { async getJsonParseResult(file) {
@@ -273,7 +271,7 @@ export default {
}, },
hasErrors: false, hasErrors: false,
messages: [], messages: [],
rowCount: +(!isEmpty) rowCount: +!isEmpty
} }
}, },
async loadToDb(file) { async loadToDb(file) {
@@ -297,7 +295,9 @@ export default {
}) })
// Get *reactive* link to parsing message for later updates // Get *reactive* link to parsing message for later updates
parsingMsg = this.importMessages[this.importMessages.length - 1] parsingMsg = this.importMessages[this.importMessages.length - 1]
const parsingLoadingIndicator = setTimeout(() => { parsingMsg.type = 'loading' }, 1000) const parsingLoadingIndicator = setTimeout(() => {
parsingMsg.type = 'loading'
}, 1000)
let importMsg = {} let importMsg = {}
let importLoadingIndicator = null let importLoadingIndicator = null
@@ -321,7 +321,9 @@ export default {
parsingMsg.type = 'success' parsingMsg.type = 'success'
if (parseResult.messages.length > 0) { if (parseResult.messages.length > 0) {
this.importMessages = this.importMessages.concat(parseResult.messages) this.importMessages = this.importMessages.concat(
parseResult.messages
)
parsingMsg.message = `${rowCount} rows are parsed in ${period}.` parsingMsg.message = `${rowCount} rows are parsed in ${period}.`
} else { } else {
// Inform about parsing success // Inform about parsing success
@@ -345,13 +347,18 @@ export default {
// Add table // Add table
start = new Date() start = new Date()
await this.db.addTableFromCsv(this.tableName, parseResult.data, progressCounterId) await this.db.addTableFromCsv(
this.tableName,
parseResult.data,
progressCounterId
)
end = new Date() end = new Date()
this.addedTable = this.tableName this.addedTable = this.tableName
// Inform about import success // Inform about import success
period = time.getPeriod(start, end) period = time.getPeriod(start, end)
importMsg.message = `Importing ${this.typeName} ` + importMsg.message =
`Importing ${this.typeName} ` +
`into a SQLite database is completed in ${period}.` `into a SQLite database is completed in ${period}.`
importMsg.type = 'success' importMsg.type = 'success'
@@ -508,5 +515,4 @@ margin-bottom: 24px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
</style> </style>

View File

@@ -10,8 +10,8 @@
@click="browse" @click="browse"
> >
<div class="text"> <div class="text">
Drop the database, CSV, JSON or NDJSON file here Drop the database, CSV, JSON or NDJSON file here or click to choose a
or click to choose a file from your computer. file from your computer.
</div> </div>
</div> </div>
</div> </div>
@@ -19,16 +19,16 @@
<img id="drop-file-top-img" src="~@/assets/images/top.svg" /> <img id="drop-file-top-img" src="~@/assets/images/top.svg" />
<img <img
id="left-arm-img" id="left-arm-img"
:class="{'swing': state === 'dragover'}" :class="{ swing: state === 'dragover' }"
src="~@/assets/images/leftArm.svg" src="~@/assets/images/leftArm.svg"
/> />
<img <img
id="file-img" id="file-img"
ref="fileImg" ref="fileImg"
:class="{ :class="{
'swing': state === 'dragover', swing: state === 'dragover',
'fly': state === 'dropping', fly: state === 'dropping',
'hidden': state === 'dropped' hidden: state === 'dropped'
}" }"
src="~@/assets/images/file.png" src="~@/assets/images/file.png"
/> />
@@ -36,7 +36,7 @@
<img id="body-img" src="~@/assets/images/body.svg" /> <img id="body-img" src="~@/assets/images/body.svg" />
<img <img
id="right-arm-img" id="right-arm-img"
:class="{'swing': state === 'dragover'}" :class="{ swing: state === 'dragover' }"
src="~@/assets/images/rightArm.svg" src="~@/assets/images/rightArm.svg"
/> />
</div> </div>
@@ -68,7 +68,7 @@ export default {
type: String, type: String,
required: false, required: false,
default: 'small', default: 'small',
validator: (value) => { validator: value => {
return ['illustrated', 'small'].includes(value) return ['illustrated', 'small'].includes(value)
} }
}, },
@@ -93,7 +93,7 @@ export default {
}, },
mounted() { mounted() {
if (this.type === 'illustrated') { if (this.type === 'illustrated') {
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' this.state = 'dropped'
@@ -119,8 +119,9 @@ export default {
}, },
loadDb(file) { loadDb(file) {
return Promise.all([this.newDb.loadDb(file), this.animationPromise]) return Promise.all([this.newDb.loadDb(file), this.animationPromise]).then(
.then(this.finish) this.finish
)
}, },
async checkFile(file) { async checkFile(file) {
@@ -140,12 +141,15 @@ export default {
await this.$nextTick() await this.$nextTick()
const csvJsonImportModal = this.$refs.addCsvJson const csvJsonImportModal = this.$refs.addCsvJson
csvJsonImportModal.reset() csvJsonImportModal.reset()
return Promise.all([csvJsonImportModal.preview(), this.animationPromise]) return Promise.all([
.then(csvJsonImportModal.open) csvJsonImportModal.preview(),
this.animationPromise
]).then(csvJsonImportModal.open)
} }
}, },
browse() { browse() {
fIo.getFileFromUser('.db,.sqlite,.sqlite3,.csv,.json,.ndjson') fIo
.getFileFromUser('.db,.sqlite,.sqlite3,.csv,.json,.ndjson')
.then(this.checkFile) .then(this.checkFile)
}, },
@@ -246,8 +250,12 @@ export default {
transform-origin: -74px 139px; transform-origin: -74px 139px;
} }
@keyframes swing { @keyframes swing {
0% { transform: rotate(0deg); } 0% {
100% { transform: rotate(-7deg); } transform: rotate(0deg);
}
100% {
transform: rotate(-7deg);
}
} }
#file-img.fly { #file-img.fly {

View File

@@ -10,7 +10,12 @@
<div v-show="loading" class="icon-in-progress"> <div v-show="loading" class="icon-in-progress">
<loading-indicator /> <loading-indicator />
</div> </div>
<span v-if="tooltip" class="icon-tooltip" :style="tooltipStyle" ref="tooltip"> <span
v-if="tooltip"
class="icon-tooltip"
:style="tooltipStyle"
ref="tooltip"
>
{{ tooltip }} {{ tooltip }}
</span> </span>
</button> </button>

View File

@@ -16,7 +16,10 @@
{{ loadingMsg }} {{ loadingMsg }}
</div> </div>
<div v-else class="loading-dialog-body"> <div v-else class="loading-dialog-body">
<img src="~@/assets/images/success.svg" class="success-icon state-icon" /> <img
src="~@/assets/images/success.svg"
class="success-icon state-icon"
/>
{{ successMsg }} {{ successMsg }}
</div> </div>
</div> </div>

View File

@@ -1,5 +1,10 @@
<template> <template>
<svg :class="animationClass" :height="size" :width="size" :viewBox="`0 0 ${size} ${size}`"> <svg
:class="animationClass"
:height="size"
:width="size"
:viewBox="`0 0 ${size} ${size}`"
>
<circle <circle
class="loader-svg bg" class="loader-svg bg"
:style="{ strokeWidth }" :style="{ strokeWidth }"
@@ -9,7 +14,11 @@
/> />
<circle <circle
class="loader-svg front" class="loader-svg front"
:style="{ strokeDasharray: circleProgress, strokeDashoffset: offset, strokeWidth }" :style="{
strokeDasharray: circleProgress,
strokeDashoffset: offset,
strokeWidth
}"
:cx="size / 2" :cx="size / 2"
:cy="size / 2" :cy="size / 2"
:r="radius" :r="radius"
@@ -35,7 +44,9 @@ export default {
computed: { computed: {
circleProgress() { circleProgress() {
const circle = this.radius * 3.14 * 2 const circle = this.radius * 3.14 * 2
const dash = this.progress ? (circle * this.progress) / 100 : circle * 1 / 3 const dash = this.progress
? (circle * this.progress) / 100
: (circle * 1) / 3
const space = circle - dash const space = circle - dash
return `${dash}px, ${space}px` return `${dash}px, ${space}px`
}, },
@@ -46,7 +57,7 @@ export default {
return this.size / 2 - this.strokeWidth return this.size / 2 - this.strokeWidth
}, },
offset() { offset() {
return this.radius * 3.14 / 2 return (this.radius * 3.14) / 2
}, },
strokeWidth() { strokeWidth() {
return this.size / 10 return this.size / 10
@@ -58,7 +69,10 @@ export default {
<style scoped> <style scoped>
.loader-svg { .loader-svg {
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-linecap: round; stroke-linecap: round;
stroke: var(--color-accent); stroke: var(--color-accent);
@@ -112,5 +126,4 @@ export default {
r: 8; r: 8;
} }
} }
</style> </style>

View File

@@ -1,10 +1,17 @@
<template> <template>
<div class="logs-container" ref="logsContainer"> <div class="logs-container" ref="logsContainer">
<div v-for="(msg, index) in messages" :key="index" class="msg"> <div v-for="(msg, index) in messages" :key="index" class="msg">
<img v-if="msg.type === 'error'" src="~@/assets/images/error.svg"> <img v-if="msg.type === 'error'" src="~@/assets/images/error.svg" />
<img v-if="msg.type === 'info'" src="~@/assets/images/info.svg" width="20px"> <img
<img v-if="msg.type === 'success'" src="~@/assets/images/success.svg"> v-if="msg.type === 'info'"
<loading-indicator v-if="msg.type === 'loading'" :progress="msg.progress" /> src="~@/assets/images/info.svg"
width="20px"
/>
<img v-if="msg.type === 'success'" src="~@/assets/images/success.svg" />
<loading-indicator
v-if="msg.type === 'loading'"
:progress="msg.progress"
/>
<span class="msg-text">{{ serializeMessage(msg) }}</span> <span class="msg-text">{{ serializeMessage(msg) }}</span>
</div> </div>
</div> </div>
@@ -44,7 +51,7 @@ export default {
} }
result += msg.message result += msg.message
if (!(/(\.|!|\?)$/.test(result))) { if (!/(\.|!|\?)$/.test(result)) {
result += '.' result += '.'
} }

View File

@@ -7,7 +7,11 @@
{ 'splitpanes-dragging': dragging } { 'splitpanes-dragging': dragging }
]" ]"
> >
<div class="movable-splitter" ref="movableSplitter" :style="movableSplitterStyle" /> <div
class="movable-splitter"
ref="movableSplitter"
:style="movableSplitterStyle"
/>
<div <div
class="splitpanes-pane" class="splitpanes-pane"
ref="left" ref="left"
@@ -27,8 +31,11 @@
:class="[ :class="[
'toggle-btns', 'toggle-btns',
{ {
'both': after.max === 100 && before.max === 100 && both:
paneAfter.size > 0 && paneBefore.size > 0 after.max === 100 &&
before.max === 100 &&
paneAfter.size > 0 &&
paneBefore.size > 0
} }
]" ]"
> >
@@ -41,7 +48,7 @@
class="direction-icon" class="direction-icon"
src="~@/assets/images/chevron.svg" src="~@/assets/images/chevron.svg"
:style="directionBeforeIconStyle" :style="directionBeforeIconStyle"
> />
</div> </div>
<div <div
v-if="before.max === 100 && paneBefore.size > 0" v-if="before.max === 100 && paneBefore.size > 0"
@@ -52,16 +59,12 @@
class="direction-icon" class="direction-icon"
src="~@/assets/images/chevron.svg" src="~@/assets/images/chevron.svg"
:style="directionAfterIconStyle" :style="directionAfterIconStyle"
> />
</div> </div>
</div> </div>
</div> </div>
<!-- splitter end --> <!-- splitter end -->
<div <div class="splitpanes-pane" ref="right" :style="styles.after">
class="splitpanes-pane"
ref="right"
:style="styles.after"
>
<slot name="right-pane" /> <slot name="right-pane" />
</div> </div>
</div> </div>
@@ -92,7 +95,8 @@ export default {
container: null, container: null,
paneBefore: this.before, paneBefore: this.before,
paneAfter: this.after, paneAfter: this.after,
beforeMinimising: !this.after.size || !this.before.size beforeMinimising:
!this.after.size || !this.before.size
? this.default ? this.default
: { : {
before: this.before.size, before: this.before.size,
@@ -109,8 +113,12 @@ export default {
computed: { computed: {
styles() { styles() {
return { return {
before: { [this.horizontal ? 'height' : 'width']: `${this.paneBefore.size}%` }, before: {
after: { [this.horizontal ? 'height' : 'width']: `${this.paneAfter.size}%` } [this.horizontal ? 'height' : 'width']: `${this.paneBefore.size}%`
},
after: {
[this.horizontal ? 'height' : 'width']: `${this.paneAfter.size}%`
}
} }
}, },
movableSplitterStyle() { movableSplitterStyle() {
@@ -154,21 +162,29 @@ export default {
methods: { methods: {
bindEvents() { bindEvents() {
// Passive: false to prevent scrolling while touch dragging. // Passive: false to prevent scrolling while touch dragging.
document.addEventListener('mousemove', this.onMouseMove, { passive: false }) document.addEventListener('mousemove', this.onMouseMove, {
passive: false
})
document.addEventListener('mouseup', this.onMouseUp) document.addEventListener('mouseup', this.onMouseUp)
if ('ontouchstart' in window) { if ('ontouchstart' in window) {
document.addEventListener('touchmove', this.onMouseMove, { passive: false }) document.addEventListener('touchmove', this.onMouseMove, {
passive: false
})
document.addEventListener('touchend', this.onMouseUp) document.addEventListener('touchend', this.onMouseUp)
} }
}, },
unbindEvents() { unbindEvents() {
document.removeEventListener('mousemove', this.onMouseMove, { passive: false }) document.removeEventListener('mousemove', this.onMouseMove, {
passive: false
})
document.removeEventListener('mouseup', this.onMouseUp) document.removeEventListener('mouseup', this.onMouseUp)
if ('ontouchstart' in window) { if ('ontouchstart' in window) {
document.removeEventListener('touchmove', this.onMouseMove, { passive: false }) document.removeEventListener('touchmove', this.onMouseMove, {
passive: false
})
document.removeEventListener('touchend', this.onMouseUp) document.removeEventListener('touchend', this.onMouseUp)
} }
}, },
@@ -218,7 +234,8 @@ export default {
this.beforeMinimising.before = this.paneBefore.size this.beforeMinimising.before = this.paneBefore.size
this.beforeMinimising.after = this.paneAfter.size this.beforeMinimising.after = this.paneAfter.size
pane.size = 0 pane.size = 0
const otherPane = pane === this.paneBefore ? this.paneAfter : this.paneBefore const otherPane =
pane === this.paneBefore ? this.paneAfter : this.paneBefore
otherPane.size = 100 - pane.size otherPane.size = 100 - pane.size
} else { } else {
this.paneBefore.size = this.beforeMinimising.before this.paneBefore.size = this.beforeMinimising.before
@@ -239,9 +256,15 @@ export default {
position: relative; position: relative;
} }
.splitpanes-vertical {flex-direction: row;} .splitpanes-vertical {
.splitpanes-horizontal {flex-direction: column;} flex-direction: row;
.splitpanes-dragging * {user-select: none;} }
.splitpanes-horizontal {
flex-direction: column;
}
.splitpanes-dragging * {
user-select: none;
}
.splitpanes-pane { .splitpanes-pane {
width: 100%; width: 100%;
@@ -288,7 +311,7 @@ export default {
.splitpanes-vertical > .movable-splitter { .splitpanes-vertical > .movable-splitter {
width: 8px; width: 8px;
z-index: 5; z-index: 5;
height: 100% height: 100%;
} }
.splitpanes-horizontal > .splitpanes-splitter, .splitpanes-horizontal > .splitpanes-splitter,
@@ -339,20 +362,32 @@ export default {
left: 50%; left: 50%;
} }
.splitpanes-horizontal > .splitpanes-splitter .toggle-btns.both .toggle-btn:first-child { .splitpanes-horizontal
> .splitpanes-splitter
.toggle-btns.both
.toggle-btn:first-child {
border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); border-radius: var(--border-radius-small) 0 0 var(--border-radius-small);
} }
.splitpanes-horizontal > .splitpanes-splitter .toggle-btns.both .toggle-btn:last-child { .splitpanes-horizontal
> .splitpanes-splitter
.toggle-btns.both
.toggle-btn:last-child {
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
margin-left: -1px; margin-left: -1px;
} }
.splitpanes-vertical > .splitpanes-splitter .toggle-btns.both .toggle-btn:first-child { .splitpanes-vertical
> .splitpanes-splitter
.toggle-btns.both
.toggle-btn:first-child {
border-radius: var(--border-radius-small) var(--border-radius-small) 0 0; border-radius: var(--border-radius-small) var(--border-radius-small) 0 0;
} }
.splitpanes-vertical > .splitpanes-splitter .toggle-btns.both .toggle-btn:last-child { .splitpanes-vertical
> .splitpanes-splitter
.toggle-btns.both
.toggle-btn:last-child {
border-radius: 0 0 var(--border-radius-small) var(--border-radius-small); border-radius: 0 0 var(--border-radius-small) var(--border-radius-small);
margin-top: -1px; margin-top: -1px;
} }

View File

@@ -2,9 +2,8 @@ export default {
// Get the cursor position relative to the splitpane container. // Get the cursor position relative to the splitpane container.
getCurrentMouseDrag(event, container) { getCurrentMouseDrag(event, container) {
const rect = container.getBoundingClientRect() const rect = container.getBoundingClientRect()
const { clientX, clientY } = ('ontouchstart' in window && event.touches) const { clientX, clientY } =
? event.touches[0] 'ontouchstart' in window && event.touches ? event.touches[0] : event
: event
return { return {
x: clientX - rect.left, x: clientX - rect.left,
y: clientY - rect.top y: clientY - rect.top
@@ -15,20 +14,32 @@ export default {
getCurrentDragPercentage(event, container, isHorisontal) { getCurrentDragPercentage(event, container, isHorisontal) {
let drag = this.getCurrentMouseDrag(event, container) let drag = this.getCurrentMouseDrag(event, container)
drag = drag[isHorisontal ? 'y' : 'x'] drag = drag[isHorisontal ? 'y' : 'x']
const containerSize = container[isHorisontal ? 'clientHeight' : 'clientWidth'] const containerSize =
return drag * 100 / containerSize container[isHorisontal ? 'clientHeight' : 'clientWidth']
return (drag * 100) / containerSize
}, },
// Returns the new position in percents. // Returns the new position in percents.
calculateOffset (event, { container, isHorisontal, paneBeforeMax, paneAfterMax }) { calculateOffset(
const dragPercentage = this.getCurrentDragPercentage(event, container, isHorisontal) event,
{ container, isHorisontal, paneBeforeMax, paneAfterMax }
) {
const dragPercentage = this.getCurrentDragPercentage(
event,
container,
isHorisontal
)
const paneBeforeMaxReached = paneBeforeMax < 100 && (dragPercentage >= paneBeforeMax) const paneBeforeMaxReached =
const paneAfterMaxReached = paneAfterMax < 100 && (dragPercentage <= 100 - paneAfterMax) paneBeforeMax < 100 && dragPercentage >= paneBeforeMax
const paneAfterMaxReached =
paneAfterMax < 100 && dragPercentage <= 100 - paneAfterMax
// Prevent dragging beyond pane max. // Prevent dragging beyond pane max.
if (paneBeforeMaxReached || paneAfterMaxReached) { if (paneBeforeMaxReached || paneAfterMaxReached) {
return paneBeforeMaxReached ? paneBeforeMax : Math.max(100 - paneAfterMax, 0) return paneBeforeMaxReached
? paneBeforeMax
: Math.max(100 - paneAfterMax, 0)
} else { } else {
return Math.min(Math.max(dragPercentage, 0), paneBeforeMax) return Math.min(Math.max(dragPercentage, 0), paneBeforeMax)
} }

View File

@@ -131,8 +131,9 @@ export default {
if (this.selectedCellCoordinates) { if (this.selectedCellCoordinates) {
const { row, col } = this.selectedCellCoordinates const { row, col } = this.selectedCellCoordinates
const cell = this.$refs.table const cell = this.$refs.table.querySelector(
.querySelector(`td[data-col="${col}"][data-row="${row}"]`) `td[data-col="${col}"][data-row="${row}"]`
)
if (cell) { if (cell) {
this.selectCell(cell) this.selectCell(cell)
} }
@@ -167,7 +168,8 @@ export default {
}) })
}, },
onScrollTable() { onScrollTable() {
this.$refs['header-container'].scrollLeft = this.$refs['table-container'].scrollLeft this.$refs['header-container'].scrollLeft =
this.$refs['table-container'].scrollLeft
}, },
onTableKeydown(e) { onTableKeydown(e) {
const keyCodeMap = { const keyCodeMap = {
@@ -242,8 +244,9 @@ export default {
newColIndex = currentColIndex newColIndex = currentColIndex
} }
const newCell = this.$refs.table const newCell = this.$refs.table.querySelector(
.querySelector(`td[data-col="${newColIndex}"][data-row="${newRowIndex}"]`) `td[data-col="${newColIndex}"][data-row="${newRowIndex}"]`
)
if (newCell) { if (newCell) {
this.selectCell(newCell) this.selectCell(newCell)
} }
@@ -271,7 +274,7 @@ table.sqliteviz-table:focus {
.sqliteviz-table tbody td:hover { .sqliteviz-table tbody td:hover {
background-color: var(--color-bg-light-3); background-color: var(--color-bg-light-3);
} }
.sqliteviz-table tbody td[aria-selected="true"] { .sqliteviz-table tbody td[aria-selected='true'] {
box-shadow: inset 0 0 0 1px var(--color-accent); box-shadow: inset 0 0 0 1px var(--color-accent);
} }
</style> </style>

View File

@@ -1,8 +1,16 @@
<template> <template>
<div> <div>
<div v-if="label" :class="['text-field-label', { error: errorMsg }, {'disabled': disabled}]"> <div
v-if="label"
:class="['text-field-label', { error: errorMsg }, { disabled: disabled }]"
>
{{ label }} {{ label }}
<hint-icon class="hint" v-if="hint" :hint="hint" :max-width="maxHintWidth || '149px'"/> <hint-icon
class="hint"
v-if="hint"
:hint="hint"
:max-width="maxHintWidth || '149px'"
/>
</div> </div>
<input <input
type="text" type="text"

View File

@@ -15,6 +15,5 @@
</template> </template>
<script> <script>
export default { export default {}
}
</script> </script>

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
@@ -46,7 +41,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'ChartIcon' name: 'ChartIcon'
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<svg <svg
:class="['clear-icon', {'disabled': disabled}]" :class="['clear-icon', { disabled: disabled }]"
width="20" width="20"
height="20" height="20"
viewBox="0 0 20 20" viewBox="0 0 20 20"
@@ -21,7 +21,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'ClearIcon', name: 'ClearIcon',
props: ['disabled'] props: ['disabled']
@@ -42,6 +41,6 @@ export default {
} }
.disabled.clear-icon:hover path { .disabled.clear-icon:hover path {
fill: #C8D4E3; fill: #c8d4e3;
} }
</style> </style>

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
>
<path <path
d="M14.1917 1.3851H12.4806V0.703125C12.4806 0.314758 12.1658 0 11.7775 0H6.246C5.85764 0 d="M14.1917 1.3851H12.4806V0.703125C12.4806 0.314758 12.1658 0 11.7775 0H6.246C5.85764 0
5.54288 0.314758 5.54288 0.703125V1.3851H3.83203C2.86276 1.3851 2.07422 2.17365 2.07422 5.54288 0.314758 5.54288 0.703125V1.3851H3.83203C2.86276 1.3851 2.07422 2.17365 2.07422
@@ -26,7 +21,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'ClipboardIcon' name: 'ClipboardIcon'
} }

View File

@@ -1,7 +1,7 @@
<template> <template>
<svg <svg
@click.stop="$emit('click')" @click.stop="$emit('click')"
:class="['icon', {'disabled': disabled }]" :class="['icon', { disabled: disabled }]"
:width="size" :width="size"
:height="size" :height="size"
viewBox="0 0 14 14" viewBox="0 0 14 14"

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
@@ -31,7 +26,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'DataViewIcon' name: 'DataViewIcon'
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<svg <svg
:class="['chevron-icon', {'disabled': disabled}]" :class="['chevron-icon', { disabled: disabled }]"
width="20" width="20"
height="20" height="20"
viewBox="0 0 20 20" viewBox="0 0 20 20"
@@ -15,7 +15,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'DropDownChevron', name: 'DropDownChevron',
props: ['disabled'] props: ['disabled']
@@ -36,6 +35,6 @@ export default {
} }
.disabled.chevron-icon:hover path { .disabled.chevron-icon:hover path {
fill: #C8D4E3; fill: #c8d4e3;
} }
</style> </style>

View File

@@ -21,6 +21,5 @@
</template> </template>
<script> <script>
export default { export default {}
}
</script> </script>

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="19" height="18" viewBox="0 0 19 18" fill="none">
width="19"
height="18"
viewBox="0 0 19 18"
fill="none"
>
<path <path
d="M6.07959 13.5756C6.05908 14.0209 5.93896 14.415 5.71924 14.7578C5.49951 15.0976 5.19043 d="M6.07959 13.5756C6.05908 14.0209 5.93896 14.415 5.71924 14.7578C5.49951 15.0976 5.19043
15.3613 4.79199 15.5488C4.39648 15.7363 3.94385 15.83 3.43408 15.83C2.59326 15.83 15.3613 4.79199 15.5488C4.39648 15.7363 3.94385 15.83 3.43408 15.83C2.59326 15.83
@@ -55,7 +50,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'ExportToCsvIcon' name: 'ExportToCsvIcon'
} }

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="19" height="18" viewBox="0 0 19 18" fill="none">
width="19"
height="18"
viewBox="0 0 19 18"
fill="none"
>
<path <path
d="M4.28369 13.9966C4.28369 13.7711 4.20312 13.5953 4.04199 13.4693C3.88379 13.3433 3.604 d="M4.28369 13.9966C4.28369 13.7711 4.20312 13.5953 4.04199 13.4693C3.88379 13.3433 3.604
13.213 3.20264 13.0782C2.80127 12.9434 2.47314 12.813 2.21826 12.6871C1.38916 12.2798 13.213 3.20264 13.0782C2.80127 12.9434 2.47314 12.813 2.21826 12.6871C1.38916 12.2798
@@ -54,7 +49,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'ExportToSvgIcon' name: 'ExportToSvgIcon'
} }

View File

@@ -33,7 +33,11 @@
fill="#A2B1C6" fill="#A2B1C6"
/> />
</svg> </svg>
<span class="icon-tooltip" :style="{...tooltipStyle, maxWidth: maxWidth }" ref="tooltip"> <span
class="icon-tooltip"
:style="{ ...tooltipStyle, maxWidth: maxWidth }"
ref="tooltip"
>
{{ hint }} {{ hint }}
</span> </span>
</div> </div>

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
@@ -21,7 +16,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'PivotIcon' name: 'PivotIcon'
} }

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
>
<path <path
d="M9 5.51953C6.57686 5.51953 4.60547 7.49092 4.60547 9.91406C4.60547 12.3372 6.57686 d="M9 5.51953C6.57686 5.51953 4.60547 7.49092 4.60547 9.91406C4.60547 12.3372 6.57686
14.3086 9 14.3086C11.4231 14.3086 13.3945 12.3372 13.3945 9.91406C13.3945 7.49092 11.4231 14.3086 9 14.3086C11.4231 14.3086 13.3945 12.3372 13.3945 9.91406C13.3945 7.49092 11.4231
@@ -30,7 +25,10 @@
5.5195V15.0117Z" 5.5195V15.0117Z"
fill="#A2B1C6" fill="#A2B1C6"
/> />
<path d="M15.1875 6.22266H13.7812V7.62891H15.1875V6.22266Z" fill="#A2B1C6"/> <path
d="M15.1875 6.22266H13.7812V7.62891H15.1875V6.22266Z"
fill="#A2B1C6"
/>
</svg> </svg>
</template> </template>

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="19" height="19" viewBox="0 0 19 19" fill="none">
width="19"
height="19"
viewBox="0 0 19 19"
fill="none"
>
<g clip-path="url(#clip0_2130_5292)"> <g clip-path="url(#clip0_2130_5292)">
<path <path
fill-rule="evenodd" fill-rule="evenodd"
@@ -46,7 +41,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'RowIcon' name: 'RowIcon'
} }

View File

@@ -1,16 +1,13 @@
<template> <template>
<svg <svg width="12" height="13" viewBox="0 0 12 13" fill="none">
width="12" <path
height="13" d="M11.1624 6.94358L0.770043 12.9436L0.770043 0.943573L11.1624 6.94358Z"
viewBox="0 0 12 13" fill="#A2B1C6"
fill="none" />
>
<path d="M11.1624 6.94358L0.770043 12.9436L0.770043 0.943573L11.1624 6.94358Z" fill="#A2B1C6"/>
</svg> </svg>
</template> </template>
<script> <script>
export default { export default {
name: 'RunIcon' name: 'RunIcon'
} }

View File

@@ -24,7 +24,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'SortIcon', name: 'SortIcon',
props: { props: {

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="18" height="19" viewBox="0 0 18 19" fill="none">
width="18"
height="19"
viewBox="0 0 18 19"
fill="none"
>
<g clip-path="url(#clip0)"> <g clip-path="url(#clip0)">
<path <path
d="M4.5 1.51343H10.5L15 6.01343V8.45284H13.5V6.76343H9.75V3.01343H4.5V8.45284H3V3.01343C3 d="M4.5 1.51343H10.5L15 6.01343V8.45284H13.5V6.76343H9.75V3.01343H4.5V8.45284H3V3.01343C3
@@ -47,14 +42,18 @@
</g> </g>
<defs> <defs>
<clipPath id="clip0"> <clipPath id="clip0">
<rect width="18" height="18" fill="white" transform="translate(0 0.0134277)"/> <rect
width="18"
height="18"
fill="white"
transform="translate(0 0.0134277)"
/>
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
</template> </template>
<script> <script>
export default { export default {
name: 'SqlEditorIcon' name: 'SqlEditorIcon'
} }

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
@@ -41,7 +36,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'TableIcon' name: 'TableIcon'
} }

View File

@@ -17,7 +17,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'treeChevron', name: 'treeChevron',
props: { props: {
@@ -31,7 +30,7 @@ export default {
<style scoped> <style scoped>
.chevron-icon { .chevron-icon {
-webkit-transition: transform .15s ease-in-out; -webkit-transition: transform 0.15s ease-in-out;
transition: transform .15s ease-in-out; transition: transform 0.15s ease-in-out;
} }
</style> </style>

View File

@@ -1,10 +1,5 @@
<template> <template>
<svg <svg width="19" height="19" viewBox="0 0 19 19" fill="none">
width="19"
height="19"
viewBox="0 0 19 19"
fill="none"
>
<g clip-path="url(#clip0_2131_6054)"> <g clip-path="url(#clip0_2131_6054)">
<path <path
d="M3.53784 11.5846L3.53784 3.14734L11.9751 3.14734V7.676C12.4655 7.51991 d="M3.53784 11.5846L3.53784 3.14734L11.9751 3.14734V7.676C12.4655 7.51991
@@ -43,7 +38,6 @@
</template> </template>
<script> <script>
export default { export default {
name: 'ViewCellValueIcon' name: 'ViewCellValueIcon'
} }

View File

@@ -61,7 +61,9 @@ export default {
} }
for (let rowNumber = 0; rowNumber < rowCount; rowNumber++) { for (let rowNumber = 0; rowNumber < rowCount; rowNumber++) {
result.data.push(columns.map(column => resultSet.values[column][rowNumber])) result.data.push(
columns.map(column => resultSet.values[column][rowNumber])
)
} }
return result return result

View File

@@ -5,7 +5,9 @@ import wasmUrl from 'sql.js/dist/sql-wasm.wasm?url'
let SQL = null let SQL = null
const sqlModuleReady = initSqlJs({ const sqlModuleReady = initSqlJs({
locateFile: () => wasmUrl locateFile: () => wasmUrl
}).then(sqlModule => { SQL = sqlModule }) }).then(sqlModule => {
SQL = sqlModule
})
function _getDataSourcesFromSqlResult(sqlResult) { function _getDataSourcesFromSqlResult(sqlResult) {
if (!sqlResult) { if (!sqlResult) {
@@ -24,8 +26,7 @@ export default class Sql {
} }
static build() { static build() {
return sqlModuleReady return sqlModuleReady.then(() => {
.then(() => {
return new Sql() return new Sql()
}) })
} }
@@ -80,7 +81,10 @@ export default class Sql {
} }
this.db.exec('COMMIT') this.db.exec('COMMIT')
count++ count++
progressCallback({ progress: 100 * (count / chunksAmount), id: progressCounterId }) progressCallback({
progress: 100 * (count / chunksAmount),
id: progressCounterId
})
} }
return { return {

View File

@@ -2,7 +2,9 @@ export default {
*generateChunks(data, size) { *generateChunks(data, size) {
const matrix = Object.keys(data).map(col => data[col]) const matrix = Object.keys(data).map(col => data[col])
const [row] = matrix const [row] = matrix
const transposedMatrix = row.map((value, column) => matrix.map(row => row[column])) const transposedMatrix = row.map((value, column) =>
matrix.map(row => row[column])
)
const count = Math.ceil(transposedMatrix.length / size) const count = Math.ceil(transposedMatrix.length / size)
@@ -38,7 +40,8 @@ export default {
type = 'TEXT' type = 'TEXT'
break break
} }
default: type = 'TEXT' default:
type = 'TEXT'
} }
result += `"${col}" ${type}, ` result += `"${col}" ${type}, `
} }

View File

@@ -35,7 +35,5 @@ function onError (error) {
} }
registerPromiseWorker(data => { registerPromiseWorker(data => {
return sqlReady return sqlReady.then(processMsg.bind(data)).catch(onError)
.then(processMsg.bind(data))
.catch(onError)
}) })

View File

@@ -7,10 +7,9 @@ import PromiseWorker from 'promise-worker'
import events from '@/lib/utils/events' import events from '@/lib/utils/events'
function getNewDatabase() { function getNewDatabase() {
const worker = new Worker( const worker = new Worker(new URL('./_worker.js', import.meta.url), {
new URL('./_worker.js', import.meta.url), type: 'module'
{ type: 'module' } })
)
return new Database(worker) return new Database(worker)
} }
@@ -31,9 +30,11 @@ class Database {
const progress = e.data.progress const progress = e.data.progress
if (progress !== undefined) { if (progress !== undefined) {
const id = e.data.id const id = e.data.id
this.importProgresses[id].dispatchEvent(new CustomEvent('progress', { this.importProgresses[id].dispatchEvent(
new CustomEvent('progress', {
detail: progress detail: progress
})) })
)
} }
}) })
} }
@@ -45,7 +46,9 @@ class Database {
createProgressCounter(callback) { createProgressCounter(callback) {
const id = progressCounterIds++ const id = progressCounterIds++
this.importProgresses[id] = new EventTarget() this.importProgresses[id] = new EventTarget()
this.importProgresses[id].addEventListener('progress', e => { callback(e.detail) }) this.importProgresses[id].addEventListener('progress', e => {
callback(e.detail)
})
return id return id
} }
@@ -70,7 +73,10 @@ class Database {
async loadDb(file) { async loadDb(file) {
const fileContent = file ? await fu.readAsArrayBuffer(file) : null 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)
@@ -130,7 +136,9 @@ class Database {
} }
if (/[^\w]/.test(name)) { if (/[^\w]/.test(name)) {
throw new Error('Table name can contain only letters, digits and underscores') throw new Error(
'Table name can contain only letters, digits and underscores'
)
} }
if (/^(\d)/.test(name)) { if (/^(\d)/.test(name)) {

View File

@@ -37,13 +37,20 @@ export default {
}, },
updateStorage(inquiries) { updateStorage(inquiries) {
localStorage.setItem('myInquiries', JSON.stringify({ version: this.version, inquiries })) localStorage.setItem(
'myInquiries',
JSON.stringify({ version: this.version, inquiries })
)
}, },
serialiseInquiries(inquiryList) { serialiseInquiries(inquiryList) {
const preparedData = JSON.parse(JSON.stringify(inquiryList)) const preparedData = JSON.parse(JSON.stringify(inquiryList))
preparedData.forEach(inquiry => delete inquiry.isPredefined) preparedData.forEach(inquiry => delete inquiry.isPredefined)
return JSON.stringify({ version: this.version, inquiries: preparedData }, null, 4) return JSON.stringify(
{ version: this.version, inquiries: preparedData },
null,
4
)
}, },
deserialiseInquiries(str) { deserialiseInquiries(str) {
@@ -59,7 +66,9 @@ export default {
// Generate new ids if they are the same as existing inquiries // Generate new ids if they are the same as existing inquiries
inquiryList.forEach(inquiry => { inquiryList.forEach(inquiry => {
const allInquiriesIds = this.getStoredInquiries().map(inquiry => inquiry.id) const allInquiriesIds = this.getStoredInquiries().map(
inquiry => inquiry.id
)
if (allInquiriesIds.includes(inquiry.id)) { if (allInquiriesIds.includes(inquiry.id)) {
inquiry.id = nanoid() inquiry.id = nanoid()
} }
@@ -69,8 +78,7 @@ export default {
}, },
importInquiries() { importInquiries() {
return fu.importFile() return fu.importFile().then(str => {
.then(str => {
const inquires = this.deserialiseInquiries(str) const inquires = this.deserialiseInquiries(str)
events.send('inquiry.import', inquires.length) events.send('inquiry.import', inquires.length)

View File

@@ -6,7 +6,9 @@ export default class Tab {
constructor(state, inquiry = {}) { constructor(state, inquiry = {}) {
this.id = inquiry.id || nanoid() this.id = inquiry.id || nanoid()
this.name = inquiry.id ? inquiry.name : null this.name = inquiry.id ? inquiry.name : null
this.tempName = inquiry.name || (state.untitledLastIndex this.tempName =
inquiry.name ||
(state.untitledLastIndex
? `Untitled ${state.untitledLastIndex}` ? `Untitled ${state.untitledLastIndex}`
: 'Untitled') : 'Untitled')
this.query = inquiry.query this.query = inquiry.query
@@ -39,7 +41,8 @@ export default class Tab {
this.time = time.getPeriod(start, new Date()) this.time = time.getPeriod(start, new Date())
if (this.result && this.result.values) { if (this.result && this.result.values) {
events.send('resultset.create', events.send(
'resultset.create',
this.result.values[this.result.columns[0]].length this.result.values[this.result.columns[0]].length
) )
} }

View File

@@ -19,7 +19,8 @@ export default {
async _copyBlob(blob) { async _copyBlob(blob) {
await navigator.clipboard.write([ await navigator.clipboard.write([
new ClipboardItem({ // eslint-disable-line no-undef new ClipboardItem({
// eslint-disable-line no-undef
[blob.type]: blob [blob.type]: blob
}) })
]) ])
@@ -32,9 +33,13 @@ export default {
}, },
async _copyCanvas(canvas) { async _copyCanvas(canvas) {
canvas.toBlob(async (blob) => { canvas.toBlob(
async blob => {
await this._copyBlob(blob) await this._copyBlob(blob)
Lib.notifier('Image copied to clipboard successfully', 'long') Lib.notifier('Image copied to clipboard successfully', 'long')
}, 'image/png', 1) },
'image/png',
1
)
} }
} }

View File

@@ -57,8 +57,7 @@ export default {
}, },
importFile() { importFile() {
return this.getFileFromUser('.json') return this.getFileFromUser('.json').then(file => {
.then(file => {
return this.getFileContent(file) return this.getFileContent(file)
}) })
}, },

View File

@@ -15,7 +15,9 @@ export default {
sleep(ms) { sleep(ms) {
return new Promise(resolve => { return new Promise(resolve => {
setTimeout(() => { resolve() }, ms) setTimeout(() => {
resolve()
}, ms)
}) })
} }
} }

View File

@@ -14,7 +14,8 @@ function invokeServiceWorkerUpdateFlow (registration) {
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => { window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register('service-worker.js') const registration =
await navigator.serviceWorker.register('service-worker.js')
// ensure the case when the updatefound event was missed is also handled // ensure the case when the updatefound event was missed is also handled
// by re-invoking the prompt when there's a waiting Service Worker // by re-invoking the prompt when there's a waiting Service Worker
if (registration.waiting) { if (registration.waiting) {

View File

@@ -31,7 +31,9 @@ export default {
if (newName) { if (newName) {
value.createdAt = new Date() value.createdAt = new Date()
} else { } else {
var inquiryIndex = myInquiries.findIndex(oldInquiry => oldInquiry.id === inquiryTab.id) var inquiryIndex = myInquiries.findIndex(
oldInquiry => oldInquiry.id === inquiryTab.id
)
value.createdAt = myInquiries[inquiryIndex].createdAt value.createdAt = myInquiries[inquiryIndex].createdAt
} }
@@ -63,8 +65,9 @@ export default {
} }
}, },
renameInquiry({ state, commit }, { inquiryId, newName }) { renameInquiry({ state, commit }, { inquiryId, newName }) {
const renamingInquiry = state.inquiries const renamingInquiry = state.inquiries.find(
.find(inquiry => inquiry.id === inquiryId) inquiry => inquiry.id === inquiryId
)
renamingInquiry.name = newName renamingInquiry.name = newName

View File

@@ -14,12 +14,24 @@ export default {
state.currentTabId = id state.currentTabId = id
} }
if (id) { tab.id = id } if (id) {
if (name) { tab.name = name } tab.id = id
if (query) { tab.query = query } }
if (viewType) { tab.viewType = viewType } if (name) {
if (viewOptions) { tab.viewOptions = viewOptions } tab.name = name
if (isSaved !== undefined) { tab.isSaved = isSaved } }
if (query) {
tab.query = query
}
if (viewType) {
tab.viewType = viewType
}
if (viewOptions) {
tab.viewOptions = viewOptions
}
if (isSaved !== undefined) {
tab.isSaved = isSaved
}
if (isSaved) { if (isSaved) {
// Saved inquiry is not predefined // Saved inquiry is not predefined
delete tab.isPredefined delete tab.isPredefined
@@ -49,11 +61,13 @@ export default {
state.currentTabId = id state.currentTabId = id
state.currentTab = state.tabs.find(tab => tab.id === id) state.currentTab = state.tabs.find(tab => tab.id === id)
} catch (e) { } catch (e) {
console.error('Can\'t open a tab id:' + id) console.error("Can't open a tab id:" + id)
} }
}, },
updatePredefinedInquiries(state, inquiries) { updatePredefinedInquiries(state, inquiries) {
state.predefinedInquiries = Array.isArray(inquiries) ? inquiries : [inquiries] state.predefinedInquiries = Array.isArray(inquiries)
? inquiries
: [inquiries]
}, },
setLoadingPredefinedInquiries(state, value) { setLoadingPredefinedInquiries(state, value) {
state.loadingPredefinedInquiries = value state.loadingPredefinedInquiries = value

View File

@@ -13,7 +13,9 @@ export default {
}, },
methods: { methods: {
showTooltip(e, tooltipPosition) { showTooltip(e, tooltipPosition) {
const position = tooltipPosition ? tooltipPosition.split('-') : ['top', 'right'] const position = tooltipPosition
? tooltipPosition.split('-')
: ['top', 'right']
const offset = 12 const offset = 12
if (position[0] === 'top') { if (position[0] === 'top') {
@@ -25,7 +27,8 @@ export default {
if (position[1] === 'right') { if (position[1] === 'right') {
this.tooltipStyle.left = e.clientX + offset + 'px' this.tooltipStyle.left = e.clientX + offset + 'px'
} else { } else {
this.tooltipStyle.left = e.clientX - offset - this.tooltipElement.offsetWidth + 'px' this.tooltipStyle.left =
e.clientX - offset - this.tooltipElement.offsetWidth + 'px'
} }
this.tooltipStyle.visibility = 'visible' this.tooltipStyle.visibility = 'visible'

View File

@@ -1,14 +1,12 @@
<template> <template>
<div> <div>
<logs <logs id="logs" :messages="messages" />
id="logs"
:messages="messages"
/>
<button <button
v-if="hasErrors" v-if="hasErrors"
id="open-workspace-btn" id="open-workspace-btn"
class="secondary" class="secondary"
@click="$router.push('/workspace?hide_schema=1')"> @click="$router.push('/workspace?hide_schema=1')"
>
Open workspace Open workspace
</button> </button>
</div> </div>
@@ -190,7 +188,6 @@ export default {
#logs { #logs {
margin: 8px auto; margin: 8px auto;
max-width: 800px; max-width: 800px;
} }
#open-workspace-btn { #open-workspace-btn {

View File

@@ -5,11 +5,7 @@
src="~@/assets/images/info.svg" src="~@/assets/images/info.svg"
@click="$modal.show('app-info')" @click="$modal.show('app-info')"
/> />
<modal <modal modal-id="app-info" class="dialog" content-class="app-info-modal">
modal-id="app-info"
class="dialog"
content-class="app-info-modal"
>
<div class="dialog-header"> <div class="dialog-header">
App info App info
<close-icon @click="$modal.hide('app-info')" /> <close-icon @click="$modal.hide('app-info')" />

View File

@@ -13,10 +13,18 @@
<loading-indicator /> <loading-indicator />
Loading predefined inquiries... Loading predefined inquiries...
</div> </div>
<div id="my-inquiries-content" ref="my-inquiries-content" v-show="allInquiries.length > 0"> <div
id="my-inquiries-content"
ref="my-inquiries-content"
v-show="allInquiries.length > 0"
>
<div id="my-inquiries-toolbar"> <div id="my-inquiries-toolbar">
<div id="toolbar-buttons"> <div id="toolbar-buttons">
<button id="toolbar-btns-import" class="toolbar" @click="importInquiries"> <button
id="toolbar-btns-import"
class="toolbar"
@click="importInquiries"
>
Import Import
</button> </button>
<button <button
@@ -37,7 +45,11 @@
</button> </button>
</div> </div>
<div id="toolbar-search"> <div id="toolbar-search">
<text-field placeholder="Search inquiry by name" width="300px" v-model="filter"/> <text-field
placeholder="Search inquiry by name"
width="300px"
v-model="filter"
/>
</div> </div>
</div> </div>
@@ -49,15 +61,20 @@
<div class="header-container"> <div class="header-container">
<div> <div>
<div class="fixed-header" ref="name-th"> <div class="fixed-header" ref="name-th">
<check-box ref="mainCheckBox" theme="light" @click="toggleSelectAll"/> <check-box
ref="mainCheckBox"
theme="light"
@click="toggleSelectAll"
/>
<div class="name-th">Name</div> <div class="name-th">Name</div>
</div> </div>
<div class="fixed-header"> <div class="fixed-header">Created at</div>
Created at
</div> </div>
</div> </div>
</div> <div
<div class="table-container" :style="{ 'max-height': `${maxTableHeight}px` }"> class="table-container"
:style="{ 'max-height': `${maxTableHeight}px` }"
>
<table ref="table" class="sqliteviz-table"> <table ref="table" class="sqliteviz-table">
<tbody> <tbody>
<tr <tr
@@ -81,9 +98,13 @@
@mouseleave="hideTooltip" @mouseleave="hideTooltip"
> >
Predefined Predefined
<span class="icon-tooltip" :style="tooltipStyle" ref="tooltip"> <span
Predefined inquiries come from the server. class="icon-tooltip"
These inquiries cant be deleted or renamed. :style="tooltipStyle"
ref="tooltip"
>
Predefined inquiries come from the server. These
inquiries cant be deleted or renamed.
</span> </span>
</div> </div>
</div> </div>
@@ -106,7 +127,7 @@
/> />
<delete-icon <delete-icon
v-if="!inquiry.isPredefined" v-if="!inquiry.isPredefined"
@click="showDeleteDialog((new Set()).add(inquiry.id))" @click="showDeleteDialog(new Set().add(inquiry.id))"
/> />
</div> </div>
</div> </div>
@@ -146,8 +167,11 @@
</div> </div>
<div class="dialog-body"> <div class="dialog-body">
{{ deleteDialogMsg }} {{ deleteDialogMsg }}
<div v-show="selectedInquiriesCount > selectedNotPredefinedCount" id="note"> <div
<img src="~@/assets/images/info.svg"> v-show="selectedInquiriesCount > selectedNotPredefinedCount"
id="note"
>
<img src="~@/assets/images/info.svg" />
Note: Predefined inquiries you've selected won't be deleted Note: Predefined inquiries you've selected won't be deleted
</div> </div>
</div> </div>
@@ -217,7 +241,8 @@ export default {
let showedInquiries = this.allInquiries let showedInquiries = this.allInquiries
if (this.filter) { if (this.filter) {
showedInquiries = showedInquiries.filter( showedInquiries = showedInquiries.filter(
inquiry => inquiry.name.toUpperCase().indexOf(this.filter.toUpperCase()) >= 0 inquiry =>
inquiry.name.toUpperCase().indexOf(this.filter.toUpperCase()) >= 0
) )
} }
return showedInquiries return showedInquiries
@@ -227,21 +252,24 @@ export default {
return this.predefinedInquiries.concat(this.inquiries) return this.predefinedInquiries.concat(this.inquiries)
}, },
processedInquiryIndex() { processedInquiryIndex() {
return this.inquiries.findIndex(inquiry => inquiry.id === this.processedInquiryId) return this.inquiries.findIndex(
inquiry => inquiry.id === this.processedInquiryId
)
}, },
deleteDialogMsg() { deleteDialogMsg() {
if (!this.deleteGroup && ( if (
this.processedInquiryIndex === null || !this.deleteGroup &&
(this.processedInquiryIndex === null ||
this.processedInquiryIndex < 0 || this.processedInquiryIndex < 0 ||
this.processedInquiryIndex > this.inquiries.length this.processedInquiryIndex > this.inquiries.length)
)) { ) {
return '' return ''
} }
const deleteItem = this.deleteGroup const deleteItem = this.deleteGroup
? `${this.selectedNotPredefinedCount} ${this.selectedNotPredefinedCount > 1 ? `${this.selectedNotPredefinedCount} ${
? 'inquiries' this.selectedNotPredefinedCount > 1 ? 'inquiries' : 'inquiry'
: 'inquiry'}` }`
: `"${this.inquiries[this.processedInquiryIndex].name}"` : `"${this.inquiries[this.processedInquiryIndex].name}"`
return `Are you sure you want to delete ${deleteItem}?` return `Are you sure you want to delete ${deleteItem}?`
@@ -250,13 +278,15 @@ export default {
watch: { watch: {
showedInquiries: { showedInquiries: {
handler() { handler() {
this.selectedInquiriesIds = new Set(this.showedInquiries this.selectedInquiriesIds = new Set(
this.showedInquiries
.filter(inquiry => this.selectedInquiriesIds.has(inquiry.id)) .filter(inquiry => this.selectedInquiriesIds.has(inquiry.id))
.map(inquiry => inquiry.id) .map(inquiry => inquiry.id)
) )
this.selectedInquiriesCount = this.selectedInquiriesIds.size this.selectedInquiriesCount = this.selectedInquiriesIds.size
this.selectedNotPredefinedCount = ([...this.selectedInquiriesIds] this.selectedNotPredefinedCount = [...this.selectedInquiriesIds].filter(
.filter(id => !this.predefinedInquiriesIds.has(id))).length id => !this.predefinedInquiriesIds.has(id)
).length
if (this.selectedInquiriesIds.size < this.showedInquiries.length) { if (this.selectedInquiriesIds.size < this.showedInquiries.length) {
if (this.$refs.mainCheckBox) { if (this.$refs.mainCheckBox) {
@@ -269,8 +299,10 @@ export default {
} }
}, },
async created() { async created() {
const loadingPredefinedInquiries = this.$store.state.loadingPredefinedInquiries const loadingPredefinedInquiries =
const predefinedInquiriesLoaded = this.$store.state.predefinedInquiriesLoaded this.$store.state.loadingPredefinedInquiries
const predefinedInquiriesLoaded =
this.$store.state.predefinedInquiriesLoaded
if (!predefinedInquiriesLoaded && !loadingPredefinedInquiries) { if (!predefinedInquiriesLoaded && !loadingPredefinedInquiries) {
try { try {
this.$store.commit('setLoadingPredefinedInquiries', true) this.$store.commit('setLoadingPredefinedInquiries', true)
@@ -310,11 +342,15 @@ export default {
hour: '2-digit', hour: '2-digit',
minute: '2-digit' minute: '2-digit'
} }
return new Date(value).toLocaleDateString('en-GB', dateOptions) + ' ' + return (
new Date(value).toLocaleDateString('en-GB', dateOptions) +
' ' +
new Date(value).toLocaleTimeString('en-GB', timeOptions) new Date(value).toLocaleTimeString('en-GB', timeOptions)
)
}, },
calcNameWidth() { calcNameWidth() {
const nameWidth = this.$refs['name-td'] && this.$refs['name-td'][0] const nameWidth =
this.$refs['name-td'] && this.$refs['name-td'][0]
? this.$refs['name-td'][0].getBoundingClientRect().width ? this.$refs['name-td'][0].getBoundingClientRect().width
: 0 : 0
this.$refs['name-th'].style = `width: ${nameWidth}px` this.$refs['name-th'].style = `width: ${nameWidth}px`
@@ -352,7 +388,9 @@ export default {
this.$modal.hide('rename') this.$modal.hide('rename')
}, },
duplicateInquiry(index) { duplicateInquiry(index) {
const newInquiry = storedInquiries.duplicateInquiry(this.showedInquiries[index]) const newInquiry = storedInquiries.duplicateInquiry(
this.showedInquiries[index]
)
this.$store.dispatch('addInquiry', newInquiry) this.$store.dispatch('addInquiry', newInquiry)
}, },
showDeleteDialog(idsSet) { showDeleteDialog(idsSet) {
@@ -365,7 +403,10 @@ export default {
deleteInquiry() { deleteInquiry() {
this.$modal.hide('delete') this.$modal.hide('delete')
if (!this.deleteGroup) { if (!this.deleteGroup) {
this.$store.dispatch('deleteInquiries', new Set().add(this.processedInquiryId)) this.$store.dispatch(
'deleteInquiries',
new Set().add(this.processedInquiryId)
)
// Clear checkbox // Clear checkbox
if (this.selectedInquiriesIds.has(this.processedInquiryId)) { if (this.selectedInquiriesIds.has(this.processedInquiryId)) {
@@ -383,23 +424,27 @@ export default {
storedInquiries.export(inquiryList, fileName) storedInquiries.export(inquiryList, fileName)
}, },
exportSelectedInquiries() { exportSelectedInquiries() {
const inquiryList = this.allInquiries.filter( const inquiryList = this.allInquiries.filter(inquiry =>
inquiry => this.selectedInquiriesIds.has(inquiry.id) this.selectedInquiriesIds.has(inquiry.id)
) )
this.exportToFile(inquiryList, 'My sqliteviz inquiries.json') this.exportToFile(inquiryList, 'My sqliteviz inquiries.json')
}, },
importInquiries() { importInquiries() {
storedInquiries.importInquiries() storedInquiries.importInquiries().then(importedInquiries => {
.then(importedInquiries => { this.$store.commit(
this.$store.commit('setInquiries', this.inquiries.concat(importedInquiries)) 'setInquiries',
this.inquiries.concat(importedInquiries)
)
}) })
}, },
toggleSelectAll(checked) { toggleSelectAll(checked) {
this.selectAll = checked this.selectAll = checked
this.$refs.rowCheckBox.forEach(item => { item.checked = checked }) this.$refs.rowCheckBox.forEach(item => {
item.checked = checked
})
this.selectedInquiriesIds = checked this.selectedInquiriesIds = checked
? new Set(this.showedInquiries.map(inquiry => inquiry.id)) ? new Set(this.showedInquiries.map(inquiry => inquiry.id))
@@ -407,8 +452,9 @@ export default {
this.selectedInquiriesCount = this.selectedInquiriesIds.size this.selectedInquiriesCount = this.selectedInquiriesIds.size
this.selectedNotPredefinedCount = checked this.selectedNotPredefinedCount = checked
? ([...this.selectedInquiriesIds].filter(id => !this.predefinedInquiriesIds.has(id))) ? [...this.selectedInquiriesIds].filter(
.length id => !this.predefinedInquiriesIds.has(id)
).length
: 0 : 0
}, },

View File

@@ -2,7 +2,7 @@
<nav> <nav>
<div id="nav-links"> <div id="nav-links">
<a href="https://sqliteviz.com"> <a href="https://sqliteviz.com">
<img src="~@/assets/images/logo_simple.svg"> <img src="~@/assets/images/logo_simple.svg" />
</a> </a>
<router-link to="/workspace">Workspace</router-link> <router-link to="/workspace">Workspace</router-link>
<router-link to="/inquiries">Inquiries</router-link> <router-link to="/inquiries">Inquiries</router-link>
@@ -18,11 +18,7 @@
> >
Save Save
</button> </button>
<button <button id="create-btn" class="primary" @click="createNewInquiry">
id="create-btn"
class="primary"
@click="createNewInquiry"
>
Create Create
</button> </button>
<app-diagnostic-info /> <app-diagnostic-info />
@@ -36,9 +32,9 @@
</div> </div>
<div class="dialog-body"> <div class="dialog-body">
<div v-show="isPredefined" id="save-note"> <div v-show="isPredefined" id="save-note">
<img src="~@/assets/images/info.svg"> <img src="~@/assets/images/info.svg" />
Note: Predefined inquiries can't be edited. Note: Predefined inquiries can't be edited. That's why your
That's why your modifications will be saved as a new inquiry. Enter the name for it. modifications will be saved as a new inquiry. Enter the name for it.
</div> </div>
<text-field <text-field
label="Inquiry name" label="Inquiry name"
@@ -87,7 +83,10 @@ export default {
return this.currentInquiry && this.currentInquiry.isPredefined return this.currentInquiry && this.currentInquiry.isPredefined
}, },
runDisabled() { runDisabled() {
return this.currentInquiry && (!this.$store.state.db || !this.currentInquiry.query) return (
this.currentInquiry &&
(!this.$store.state.db || !this.currentInquiry.query)
)
} }
}, },
created() { created() {
@@ -126,7 +125,7 @@ export default {
async saveInquiry() { async saveInquiry() {
const isNeedName = storedInquiries.isTabNeedName(this.currentInquiry) const isNeedName = storedInquiries.isTabNeedName(this.currentInquiry)
if (isNeedName && !this.name) { if (isNeedName && !this.name) {
this.errorMsg = 'Inquiry name can\'t be empty' this.errorMsg = "Inquiry name can't be empty"
return return
} }
const dataSet = this.currentInquiry.result const dataSet = this.currentInquiry.result

View File

@@ -29,7 +29,8 @@ export default {
</script> </script>
<style scoped> <style scoped>
.table-name, .column { .table-name,
.column {
margin-top: 11px; margin-top: 11px;
} }

View File

@@ -69,7 +69,8 @@ export default {
return !this.filter return !this.filter
? this.$store.state.db.schema ? this.$store.state.db.schema
: this.$store.state.db.schema.filter( : this.$store.state.db.schema.filter(
table => table.name.toUpperCase().indexOf(this.filter.toUpperCase()) !== -1 table =>
table.name.toUpperCase().indexOf(this.filter.toUpperCase()) !== -1
) )
}, },
dbName() { dbName() {
@@ -119,7 +120,8 @@ export default {
background-image: linear-gradient(white 73%, rgba(255, 255, 255, 0)); background-image: linear-gradient(white 73%, rgba(255, 255, 255, 0));
z-index: 2; z-index: 2;
} }
.schema, .db-name { .schema,
.db-name {
color: var(--color-text-base); color: var(--color-text-base);
font-size: 13px; font-size: 13px;
white-space: nowrap; white-space: nowrap;

View File

@@ -1,15 +1,23 @@
<template> <template>
<div v-show="visible" class="chart-container" ref="chartContainer"> <div v-show="visible" class="chart-container" ref="chartContainer">
<div class="warning chart-warning" v-show="!dataSources && visible"> <div class="warning chart-warning" v-show="!dataSources && visible">
There is no data to build a chart. Run your SQL query and make sure the result is not empty. There is no data to build a chart. Run your SQL query and make sure the
result is not empty.
</div> </div>
<div class="chart" :style="{ height: !dataSources ? 'calc(100% - 40px)' : '100%' }"> <div
class="chart"
:style="{ height: !dataSources ? 'calc(100% - 40px)' : '100%' }"
>
<PlotlyEditor <PlotlyEditor
v-show="visible" v-show="visible"
:data="state.data" :data="state.data"
:layout="state.layout" :layout="state.layout"
:frames="state.frames" :frames="state.frames"
:config="{ editable: true, displaylogo: false, modeBarButtonsToRemove: ['toImage'] }" :config="{
editable: true,
displaylogo: false,
modeBarButtonsToRemove: ['toImage']
}"
:dataSources="dataSources" :dataSources="dataSources"
:dataSourceOptions="dataSourceOptions" :dataSourceOptions="dataSourceOptions"
:plotly="plotly" :plotly="plotly"
@@ -38,8 +46,10 @@ import events from '@/lib/utils/events'
export default { export default {
name: 'Chart', name: 'Chart',
props: [ props: [
'dataSources', 'initOptions', 'dataSources',
'importToPngEnabled', 'importToSvgEnabled', 'initOptions',
'importToPngEnabled',
'importToSvgEnabled',
'forPivot' 'forPivot'
], ],
emits: ['update:importToSvgEnabled', 'update', 'loadingImageCompleted'], emits: ['update:importToSvgEnabled', 'update', 'loadingImageCompleted'],
@@ -70,10 +80,13 @@ export default {
notifyOnLogging: 1 notifyOnLogging: 1
}) })
this.$watch( this.$watch(
() => this.state && this.state.data && this.state.data () =>
this.state &&
this.state.data &&
this.state.data
.map(trace => `${trace.type}${trace.mode ? '-' + trace.mode : ''}`) .map(trace => `${trace.type}${trace.mode ? '-' + trace.mode : ''}`)
.join(','), .join(','),
(value) => { value => {
events.send('viz_plotly.render', null, { events.send('viz_plotly.render', null, {
type: value, type: value,
pivot: !!this.forPivot pivot: !!this.forPivot
@@ -140,7 +153,10 @@ export default {
) )
}, },
async prepareCopy(type = 'png') { async prepareCopy(type = 'png') {
return await chartHelper.getImageDataUrl(this.$refs.plotlyEditor.$el, type) return await chartHelper.getImageDataUrl(
this.$refs.plotlyEditor.$el,
type
)
} }
} }
} }

View File

@@ -139,7 +139,12 @@
import $ from 'jquery' import $ from 'jquery'
import Multiselect from 'vue-multiselect' import Multiselect from 'vue-multiselect'
import PivotSortBtn from './PivotSortBtn' import PivotSortBtn from './PivotSortBtn'
import { renderers, aggregators, zeroValAggregators, twoValAggregators } from '../pivotHelper' import {
renderers,
aggregators,
zeroValAggregators,
twoValAggregators
} from '../pivotHelper'
export default { export default {
name: 'pivotUi', name: 'pivotUi',
@@ -150,16 +155,28 @@ export default {
PivotSortBtn PivotSortBtn
}, },
data() { data() {
const aggregatorName = (this.modelValue && this.modelValue.aggregatorName) || 'Count' const aggregatorName =
const rendererName = (this.modelValue && this.modelValue.rendererName) || 'Table' (this.modelValue && this.modelValue.aggregatorName) || 'Count'
const rendererName =
(this.modelValue && this.modelValue.rendererName) || 'Table'
return { return {
collapsed: false, collapsed: false,
renderer: { name: rendererName, fun: $.pivotUtilities.renderers[rendererName] }, renderer: {
aggregator: { name: aggregatorName, fun: $.pivotUtilities.aggregators[aggregatorName] }, name: rendererName,
fun: $.pivotUtilities.renderers[rendererName]
},
aggregator: {
name: aggregatorName,
fun: $.pivotUtilities.aggregators[aggregatorName]
},
rows: (this.modelValue && this.modelValue.rows) || [], rows: (this.modelValue && this.modelValue.rows) || [],
cols: (this.modelValue && this.modelValue.cols) || [], cols: (this.modelValue && this.modelValue.cols) || [],
val1: (this.modelValue && this.modelValue.vals && this.modelValue.vals[0]) || '', val1:
val2: (this.modelValue && this.modelValue.vals && this.modelValue.vals[1]) || '', (this.modelValue && this.modelValue.vals && this.modelValue.vals[0]) ||
'',
val2:
(this.modelValue && this.modelValue.vals && this.modelValue.vals[1]) ||
'',
colOrder: (this.modelValue && this.modelValue.colOrder) || 'key_a_to_z', colOrder: (this.modelValue && this.modelValue.colOrder) || 'key_a_to_z',
rowOrder: (this.modelValue && this.modelValue.rowOrder) || 'key_a_to_z' rowOrder: (this.modelValue && this.modelValue.rowOrder) || 'key_a_to_z'
} }
@@ -281,7 +298,6 @@ export default {
white-space: nowrap; white-space: nowrap;
margin: auto; margin: auto;
cursor: pointer; cursor: pointer;
} }
.switcher:hover { .switcher:hover {

View File

@@ -1,7 +1,8 @@
<template> <template>
<div class="pivot-container"> <div class="pivot-container">
<div class="warning pivot-warning" v-show="!dataSources"> <div class="warning pivot-warning" v-show="!dataSources">
There is no data to build a pivot. Run your SQL query and make sure the result is not empty. There is no data to build a pivot. Run your SQL query and make sure the
result is not empty.
</div> </div>
<pivot-ui <pivot-ui
:key-names="columns" :key-names="columns"
@@ -37,7 +38,12 @@ import events from '@/lib/utils/events'
export default { export default {
name: 'pivot', name: 'pivot',
props: ['dataSources', 'initOptions', 'importToPngEnabled', 'importToSvgEnabled'], props: [
'dataSources',
'initOptions',
'importToPngEnabled',
'importToSvgEnabled'
],
emits: [ emits: [
'loadingImageCompleted', 'loadingImageCompleted',
'update', 'update',
@@ -163,7 +169,9 @@ export default {
$(this.$refs.pivotOutput).pivot( $(this.$refs.pivotOutput).pivot(
function (callback) { function (callback) {
const rowCount = !this.dataSources ? 0 : this.dataSources[this.columns[0]].length const rowCount = !this.dataSources
? 0
: this.dataSources[this.columns[0]].length
for (let i = 1; i <= rowCount; i++) { for (let i = 1; i <= rowCount; i++) {
const row = {} const row = {}
this.columns.forEach(col => { this.columns.forEach(col => {
@@ -198,7 +206,9 @@ export default {
} else { } else {
const source = this.viewStandartChart const source = this.viewStandartChart
? await chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'png') ? await chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'png')
: (await pivotHelper.getPivotCanvas(this.$refs.pivotOutput)).toDataURL('image/png') : (
await pivotHelper.getPivotCanvas(this.$refs.pivotOutput)
).toDataURL('image/png')
this.$emit('loadingImageCompleted') this.$emit('loadingImageCompleted')
fIo.downloadFromUrl(source, 'pivot') fIo.downloadFromUrl(source, 'pivot')
@@ -219,7 +229,10 @@ export default {
if (this.viewCustomChart) { if (this.viewCustomChart) {
this.$refs.customChart.saveAsSvg() this.$refs.customChart.saveAsSvg()
} else if (this.viewStandartChart) { } else if (this.viewStandartChart) {
const url = await chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'svg') const url = await chartHelper.getImageDataUrl(
this.$refs.pivotOutput,
'svg'
)
fIo.downloadFromUrl(url, 'pivot') fIo.downloadFromUrl(url, 'pivot')
} }
}, },

View File

@@ -69,12 +69,14 @@ export const renderers = Object.keys($.pivotUtilities.renderers).map(key => {
} }
}) })
export const aggregators = Object.keys($.pivotUtilities.aggregators).map(key => { export const aggregators = Object.keys($.pivotUtilities.aggregators).map(
key => {
return { return {
name: key, name: key,
fun: $.pivotUtilities.aggregators[key] fun: $.pivotUtilities.aggregators[key]
} }
}) }
)
export async function getPivotCanvas(pivotOutput) { export async function getPivotCanvas(pivotOutput) {
const tableElement = pivotOutput.querySelector('.pvtTable') const tableElement = pivotOutput.querySelector('.pvtTable')

View File

@@ -172,7 +172,7 @@ export default {
await time.sleep(0) await time.sleep(0)
this.dataToCopy = await this.$refs.viewComponent.prepareCopy() this.dataToCopy = await this.$refs.viewComponent.prepareCopy()
const t1 = performance.now() const t1 = performance.now()
if ((t1 - t0) < 950) { if (t1 - t0 < 950) {
this.$modal.hide('prepareCopy') this.$modal.hide('prepareCopy')
this.copyToClipboard() this.copyToClipboard()
} else { } else {

View File

@@ -11,9 +11,7 @@
<tr> <tr>
<th /> <th />
<th> <th>
<div class="cell-data"> <div class="cell-data">Row #{{ currentRowIndex + 1 }}</div>
Row #{{ currentRowIndex + 1 }}
</div>
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -78,8 +76,9 @@ export default {
mounted() { mounted() {
const col = this.selectedColumnIndex const col = this.selectedColumnIndex
const row = this.currentRowIndex const row = this.currentRowIndex
const cell = this.$refs.table const cell = this.$refs.table.querySelector(
.querySelector(`td[data-col="${col}"][data-row="${row}"]`) `td[data-col="${col}"][data-row="${row}"]`
)
if (cell) { if (cell) {
this.selectCell(cell) this.selectCell(cell)
} }
@@ -152,19 +151,21 @@ export default {
if (this.selectedCellElement && scrollTo) { if (this.selectedCellElement && scrollTo) {
this.selectedCellElement.scrollIntoView() this.selectedCellElement.scrollIntoView()
this.selectedCellElement.closest('.table-container').scrollTo({ left: 0 }) this.selectedCellElement
.closest('.table-container')
.scrollTo({ left: 0 })
} }
this.$emit('updateSelectedCell', this.selectedCellElement) this.$emit('updateSelectedCell', this.selectedCellElement)
}, },
moveFocusInTable(initialCell, direction) { moveFocusInTable(initialCell, direction) {
const currentColIndex = +initialCell.dataset.col const currentColIndex = +initialCell.dataset.col
const newColIndex = direction === 'up' const newColIndex =
? currentColIndex - 1 direction === 'up' ? currentColIndex - 1 : currentColIndex + 1
: currentColIndex + 1
const newCell = this.$refs.table const newCell = this.$refs.table.querySelector(
.querySelector(`td[data-col="${newColIndex}"][data-row="${this.currentRowIndex}"]`) `td[data-col="${newColIndex}"][data-row="${this.currentRowIndex}"]`
)
if (newCell) { if (newCell) {
this.selectCell(newCell) this.selectCell(newCell)
} }
@@ -180,7 +181,7 @@ table.sqliteviz-table:focus {
.sqliteviz-table tbody td:hover { .sqliteviz-table tbody td:hover {
background-color: var(--color-bg-light-3); background-color: var(--color-bg-light-3);
} }
.sqliteviz-table tbody td[aria-selected="true"] { .sqliteviz-table tbody td[aria-selected='true'] {
box-shadow: inset 0 0 0 1px var(--color-accent); box-shadow: inset 0 0 0 1px var(--color-accent);
} }

View File

@@ -12,13 +12,7 @@
{{ format.text }} {{ format.text }}
</button> </button>
<button <button type="button" class="copy" @click="copyToClipboard">Copy</button>
type="button"
class="copy"
@click="copyToClipboard"
>
Copy
</button>
</div> </div>
<div class="value-body"> <div class="value-body">
<codemirror <codemirror
@@ -30,7 +24,8 @@
<pre <pre
v-if="currentFormat === 'text'" v-if="currentFormat === 'text'"
:class="['text-value', { 'meta-value': isNull || isBlob }]" :class="['text-value', { 'meta-value': isNull || isBlob }]"
>{{ cellText }}</pre> >{{ cellText }}</pre
>
<logs <logs
v-if="messages && messages.length > 0" v-if="messages && messages.length > 0"
:messages="messages" :messages="messages"
@@ -117,22 +112,21 @@ export default {
methods: { methods: {
formatJson(jsonStr) { formatJson(jsonStr) {
try { try {
this.formattedJson = JSON.stringify( this.formattedJson = JSON.stringify(JSON.parse(jsonStr), null, 4)
JSON.parse(jsonStr), null, 4
)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
this.formattedJson = '' this.formattedJson = ''
this.messages = [{ this.messages = [
{
type: 'error', type: 'error',
message: 'Can\'t parse JSON.' message: "Can't parse JSON."
}] }
]
} }
}, },
copyToClipboard() { copyToClipboard() {
cIo.copyText(this.currentFormat === 'json' cIo.copyText(
? this.formattedJson this.currentFormat === 'json' ? this.formattedJson : this.cellValue,
: this.cellValue,
'The value is copied to clipboard.' 'The value is copied to clipboard.'
) )
} }
@@ -188,7 +182,7 @@ export default {
background-color: var(--color-bg-light); background-color: var(--color-bg-light);
} }
.value-viewer-toolbar button[aria-selected="true"] { .value-viewer-toolbar button[aria-selected='true'] {
color: var(--color-accent); color: var(--color-accent);
} }

View File

@@ -8,16 +8,26 @@
class="run-result-panel-content" class="run-result-panel-content"
> >
<template #left-pane> <template #left-pane>
<div :id="'run-result-left-pane-'+tab.id" class="result-set-container"/> <div
:id="'run-result-left-pane-' + tab.id"
class="result-set-container"
/>
</template> </template>
<div :id="'run-result-result-set-'+tab.id" class="result-set-container"/> <div
:id="'run-result-result-set-' + tab.id"
class="result-set-container"
/>
<template #right-pane v-if="viewValuePanelVisible"> <template #right-pane v-if="viewValuePanelVisible">
<div class="value-viewer-container"> <div class="value-viewer-container">
<value-viewer <value-viewer
v-show="selectedCell" v-show="selectedCell"
:cellValue="selectedCell :cellValue="
? result.values[result.columns[selectedCell.dataset.col]][selectedCell.dataset.row] selectedCell
: ''" ? result.values[result.columns[selectedCell.dataset.col]][
selectedCell.dataset.row
]
: ''
"
/> />
<div v-show="!selectedCell" class="table-preview"> <div v-show="!selectedCell" class="table-preview">
No cell selected to view No cell selected to view
@@ -80,11 +90,7 @@
@cancel="cancelCopy" @cancel="cancelCopy"
/> />
<teleport <teleport defer :to="resultSetTeleportTarget" :disabled="!enableTeleport">
defer
:to="resultSetTeleportTarget"
:disabled="!enableTeleport"
>
<div> <div>
<div <div
v-show="result === null && !isGettingResults && !error" v-show="result === null && !isGettingResults && !error"
@@ -192,7 +198,8 @@ export default {
if (!this.enableTeleport) { if (!this.enableTeleport) {
return undefined return undefined
} }
const base = `#${this.viewValuePanelVisible const base = `#${
this.viewValuePanelVisible
? 'run-result-left-pane' ? 'run-result-left-pane'
: 'run-result-result-set' : 'run-result-result-set'
}` }`
@@ -236,7 +243,8 @@ export default {
exportToCsv() { exportToCsv() {
if (this.result && this.result.values) { if (this.result && this.result.values) {
events.send('resultset.export', events.send(
'resultset.export',
this.result.values[this.result.columns[0]].length, this.result.values[this.result.columns[0]].length,
{ to: 'csv' } { to: 'csv' }
) )
@@ -247,7 +255,8 @@ export default {
async prepareCopy() { async prepareCopy() {
if (this.result && this.result.values) { if (this.result && this.result.values) {
events.send('resultset.export', events.send(
'resultset.export',
this.result.values[this.result.columns[0]].length, this.result.values[this.result.columns[0]].length,
{ to: 'clipboard' } { to: 'clipboard' }
) )
@@ -261,7 +270,7 @@ export default {
await time.sleep(0) await time.sleep(0)
this.dataToCopy = csv.serialize(this.result) this.dataToCopy = csv.serialize(this.result)
const t1 = performance.now() const t1 = performance.now()
if ((t1 - t0) < 950) { if (t1 - t0 < 950) {
this.$modal.hide('prepareCSVCopy') this.$modal.hide('prepareCSVCopy')
this.copyToClipboard() this.copyToClipboard()
} else { } else {

View File

@@ -12,8 +12,10 @@ export function getHints (cm, options) {
// Don't show the hint if there is only one option // Don't show the hint if there is only one option
// and the replacingText is already equals to this option // and the replacingText is already equals to this option
const replacedText = cm.getRange(result.from, result.to).toUpperCase() const replacedText = cm.getRange(result.from, result.to).toUpperCase()
if (result.list.length === 1 && if (
_getHintText(result.list[0]).toUpperCase() === replacedText) { result.list.length === 1 &&
_getHintText(result.list[0]).toUpperCase() === replacedText
) {
result.list = [] result.list = []
} }

View File

@@ -64,7 +64,7 @@ export default {
}, },
computed: { computed: {
runDisabled() { runDisabled() {
return (!this.$store.state.db || !this.query || this.isGettingResults) return !this.$store.state.db || !this.query || this.isGettingResults
} }
}, },
watch: { watch: {

View File

@@ -87,7 +87,9 @@ export default {
data() { data() {
return { return {
topPaneSize: this.tab.maximize topPaneSize: this.tab.maximize
? this.tab.layout[this.tab.maximize] === 'above' ? 100 : 0 ? this.tab.layout[this.tab.maximize] === 'above'
? 100
: 0
: 50, : 50,
enableTeleport: this.$store.state.isWorkspaceVisible enableTeleport: this.$store.state.isWorkspaceVisible
} }

View File

@@ -5,7 +5,7 @@
v-for="(tab, index) in tabs" v-for="(tab, index) in tabs"
:key="index" :key="index"
@click="selectTab(tab.id)" @click="selectTab(tab.id)"
:class="[{'tab-selected': (tab.id === selectedTabId)}, 'tab']" :class="[{ 'tab-selected': tab.id === selectedTabId }, 'tab']"
> >
<div class="tab-name"> <div class="tab-name">
<span v-show="!tab.isSaved" class="star">*</span> <span v-show="!tab.isSaved" class="star">*</span>
@@ -13,15 +13,15 @@
<span v-else class="tab-untitled">{{ tab.tempName }}</span> <span v-else class="tab-untitled">{{ tab.tempName }}</span>
</div> </div>
<div> <div>
<close-icon class="close-icon" :size="10" @click="beforeCloseTab(tab)"/> <close-icon
</div> class="close-icon"
</div> :size="10"
</div> @click="beforeCloseTab(tab)"
<tab
v-for="tab in tabs"
:key="tab.id"
:tab="tab"
/> />
</div>
</div>
</div>
<tab v-for="tab in tabs" :key="tab.id" :tab="tab" />
<div v-show="tabs.length === 0" id="start-guide"> <div v-show="tabs.length === 0" id="start-guide">
<span class="link" @click="emitCreateTabEvent">Create</span> <span class="link" @click="emitCreateTabEvent">Create</span>
new inquiry from scratch or open one from new inquiry from scratch or open one from
@@ -31,26 +31,33 @@
<!--Close tab warning dialog --> <!--Close tab warning dialog -->
<modal modal-id="close-warn" class="dialog" content-style="width: 560px;"> <modal modal-id="close-warn" class="dialog" content-style="width: 560px;">
<div class="dialog-header"> <div class="dialog-header">
Close tab {{ Close tab
{{
closingTab !== null closingTab !== null
? (closingTab.name || `[${closingTab.tempName}]`) ? closingTab.name || `[${closingTab.tempName}]`
: '' : ''
}} }}
<close-icon @click="$modal.hide('close-warn')" /> <close-icon @click="$modal.hide('close-warn')" />
</div> </div>
<div class="dialog-body"> <div class="dialog-body">
You have unsaved changes. Save changes in {{ You have unsaved changes. Save changes in
{{
closingTab !== null closingTab !== null
? (closingTab.name || `[${closingTab.tempName}]`) ? closingTab.name || `[${closingTab.tempName}]`
: '' : ''
}} before closing? }}
before closing?
</div> </div>
<div class="dialog-buttons-container"> <div class="dialog-buttons-container">
<button class="secondary" @click="closeTab(closingTab)"> <button class="secondary" @click="closeTab(closingTab)">
Close without saving Close without saving
</button> </button>
<button class="secondary" @click="$modal.hide('close-warn')">Don't close</button> <button class="secondary" @click="$modal.hide('close-warn')">
<button class="primary" @click="saveAndClose(closingTab)">Save and close</button> Don't close
</button>
<button class="primary" @click="saveAndClose(closingTab)">
Save and close
</button>
</div> </div>
</modal> </modal>
</div> </div>

View File

@@ -36,7 +36,10 @@ export default {
}, },
async beforeCreate() { async beforeCreate() {
const schema = this.$store.state.db.schema const schema = this.$store.state.db.schema
if ((!schema || schema.length === 0) && this.$store.state.tabs.length === 0) { if (
(!schema || schema.length === 0) &&
this.$store.state.tabs.length === 0
) {
const stmt = [ const stmt = [
'/*', '/*',
' * Your database is empty. In order to start building charts', ' * Your database is empty. In order to start building charts',

View File

@@ -3,7 +3,7 @@ import { createVfm, VueFinalModal, useVfm } from 'vue-final-modal'
config.global.plugins = [createVfm()] config.global.plugins = [createVfm()]
config.global.components = { config.global.components = {
'modal': VueFinalModal modal: VueFinalModal
} }
const vfm = useVfm() const vfm = useVfm()
config.global.mocks = { config.global.mocks = {

View File

@@ -13,9 +13,9 @@ describe('App.vue', () => {
}) })
it('Gets inquiries', () => { it('Gets inquiries', () => {
sinon.stub(storedInquiries, 'getStoredInquiries').returns([ sinon
{ id: 1 }, { id: 2 }, { id: 3 } .stub(storedInquiries, 'getStoredInquiries')
]) .returns([{ id: 1 }, { id: 2 }, { id: 3 }])
const state = { const state = {
predefinedInquiries: [], predefinedInquiries: [],
inquiries: [] inquiries: []
@@ -33,7 +33,9 @@ describe('App.vue', () => {
it('Updates inquiries when they change in store', async () => { it('Updates inquiries when they change in store', async () => {
sinon.stub(storedInquiries, 'getStoredInquiries').returns([ sinon.stub(storedInquiries, 'getStoredInquiries').returns([
{ id: 1, name: 'foo' }, { id: 2, name: 'baz' }, { id: 3, name: 'bar' } { id: 1, name: 'foo' },
{ id: 2, name: 'baz' },
{ id: 3, name: 'bar' }
]) ])
sinon.spy(storedInquiries, 'updateStorage') sinon.spy(storedInquiries, 'updateStorage')
@@ -43,8 +45,7 @@ describe('App.vue', () => {
} }
const store = createStore({ state, mutations }) const store = createStore({ state, mutations })
shallowMount(App, { shallowMount(App, {
global: { stubs: ['router-view'], global: { stubs: ['router-view'], plugins: [store] }
plugins: [store] }
}) })
store.state.inquiries.splice(0, 1, { id: 1, name: 'new foo name' }) store.state.inquiries.splice(0, 1, { id: 1, name: 'new foo name' })

View File

@@ -50,9 +50,13 @@ describe('CheckBox', () => {
props: { disabled: true } props: { disabled: true }
}) })
expect(wrapper.find('.checkbox-container').classes()).to.include('disabled') expect(wrapper.find('.checkbox-container').classes()).to.include('disabled')
expect(wrapper.find('.checkbox-container').classes()).to.not.include('checked') expect(wrapper.find('.checkbox-container').classes()).to.not.include(
'checked'
)
await wrapper.trigger('click') await wrapper.trigger('click')
expect(wrapper.emitted().click).to.equal(undefined) expect(wrapper.emitted().click).to.equal(undefined)
expect(wrapper.find('.checkbox-container').classes()).to.not.include('checked') expect(wrapper.find('.checkbox-container').classes()).to.not.include(
'checked'
)
}) })
}) })

View File

@@ -6,7 +6,6 @@ import CsvJsonImport from '@/components/CsvJsonImport'
import csv from '@/lib/csv' import csv from '@/lib/csv'
import { nextTick } from 'vue' import { nextTick } from 'vue'
describe('CsvJsonImport.vue', () => { describe('CsvJsonImport.vue', () => {
let state = {} let state = {}
let actions = {} let actions = {}
@@ -74,13 +73,15 @@ describe('CsvJsonImport.vue', () => {
} }
}, },
rowCount: 2, rowCount: 2,
messages: [{ messages: [
{
code: 'UndetectableDelimiter', code: 'UndetectableDelimiter',
message: 'Comma was used as a standart delimiter', message: 'Comma was used as a standart delimiter',
row: 0, row: 0,
type: 'info', type: 'info',
hint: undefined hint: undefined
}] }
]
}) })
wrapper.vm.preview() wrapper.vm.preview()
@@ -89,24 +90,34 @@ describe('CsvJsonImport.vue', () => {
await nextTick() await nextTick()
expect(wrapper.find('.dialog.vfm').exists()).to.equal(true) expect(wrapper.find('.dialog.vfm').exists()).to.equal(true)
expect(wrapper.find('.dialog-header').text()).to.equal('CSV import') expect(wrapper.find('.dialog-header').text()).to.equal('CSV import')
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data') expect(wrapper.find('#csv-json-table-name input').element.value).to.equal(
expect(wrapper.findComponent({ name: 'delimiter-selector' }).props('modelValue')).to.equal('|') 'my_data'
)
expect(
wrapper.findComponent({ name: 'delimiter-selector' }).props('modelValue')
).to.equal('|')
expect(wrapper.find('#quote-char input').element.value).to.equal('"') expect(wrapper.find('#quote-char input').element.value).to.equal('"')
expect(wrapper.find('#escape-char input').element.value).to.equal('"') expect(wrapper.find('#escape-char input').element.value).to.equal('"')
expect(wrapper.findComponent({ name: 'check-box' }).vm.checked).to.equal(true) expect(wrapper.findComponent({ name: 'check-box' }).vm.checked).to.equal(
true
)
const rows = wrapper.findAll('tbody tr') const rows = wrapper.findAll('tbody tr')
expect(rows).to.have.lengthOf(2) expect(rows).to.have.lengthOf(2)
expect(rows[0].findAll('td')[0].text()).to.equal('foo') expect(rows[0].findAll('td')[0].text()).to.equal('foo')
expect(rows[0].findAll('td')[1].text()).to.equal('1') expect(rows[0].findAll('td')[1].text()).to.equal('1')
expect(rows[1].findAll('td')[0].text()).to.equal('bar') expect(rows[1].findAll('td')[0].text()).to.equal('bar')
expect(rows[1].findAll('td')[1].text()).to.equal('2') expect(rows[1].findAll('td')[1].text()).to.equal('2')
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
.to.include('Information about row 0. Comma was used as a standart delimiter.') 'Information about row 0. Comma was used as a standart delimiter.'
expect(wrapper.findComponent({ name: 'logs' }).text()) )
.to.include('Preview parsing is completed in') expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
'Preview parsing is completed in'
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
expect(wrapper.find('#import-start').attributes().disabled).to.equal(undefined) expect(wrapper.find('#import-start').attributes().disabled).to.equal(
undefined
)
}) })
it('disables import if no rows found', async () => { it('disables import if no rows found', async () => {
@@ -129,8 +140,9 @@ describe('CsvJsonImport.vue', () => {
await nextTick() await nextTick()
const rows = wrapper.findAll('tbody tr') const rows = wrapper.findAll('tbody tr')
expect(rows).to.have.lengthOf(0) expect(rows).to.have.lengthOf(0)
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
.to.include('No rows to import.') 'No rows to import.'
)
expect(wrapper.find('.no-data').isVisible()).to.equal(true) expect(wrapper.find('.no-data').isVisible()).to.equal(true)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
@@ -177,8 +189,9 @@ describe('CsvJsonImport.vue', () => {
expect(rows).to.have.lengthOf(1) expect(rows).to.have.lengthOf(1)
expect(rows[0].findAll('td')[0].text()).to.equal('bar') expect(rows[0].findAll('td')[0].text()).to.equal('bar')
expect(rows[0].findAll('td')[1].text()).to.equal('2') expect(rows[0].findAll('td')[1].text()).to.equal('2')
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
.to.include('Preview parsing is completed in') 'Preview parsing is completed in'
)
parse.onCall(2).resolves({ parse.onCall(2).resolves({
delimiter: ',', delimiter: ',',
@@ -191,13 +204,15 @@ describe('CsvJsonImport.vue', () => {
}, },
rowCount: 1, rowCount: 1,
hasErrors: true, hasErrors: true,
messages: [{ messages: [
{
code: 'MissingQuotes', code: 'MissingQuotes',
message: 'Quote is missed', message: 'Quote is missed',
row: 0, row: 0,
type: 'error', type: 'error',
hint: 'Edit your CSV so that the field has a closing quote char.' hint: 'Edit your CSV so that the field has a closing quote char.'
}] }
]
}) })
await wrapper.find('#quote-char input').setValue("'") await wrapper.find('#quote-char input').setValue("'")
@@ -207,13 +222,13 @@ describe('CsvJsonImport.vue', () => {
expect(rows).to.have.lengthOf(1) expect(rows).to.have.lengthOf(1)
expect(rows[0].findAll('td')[0].text()).to.equal('baz') expect(rows[0].findAll('td')[0].text()).to.equal('baz')
expect(rows[0].findAll('td')[1].text()).to.equal('3') expect(rows[0].findAll('td')[1].text()).to.equal('3')
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text()).to.contain(
.to.contain(
'Error in row 0. Quote is missed. ' + 'Error in row 0. Quote is missed. ' +
'Edit your CSV so that the field has a closing quote char.' 'Edit your CSV so that the field has a closing quote char.'
) )
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text()).to.not.contain(
.to.not.contain('Preview parsing is completed in') 'Preview parsing is completed in'
)
parse.onCall(3).resolves({ parse.onCall(3).resolves({
delimiter: ',', delimiter: ',',
@@ -234,8 +249,9 @@ describe('CsvJsonImport.vue', () => {
expect(rows).to.have.lengthOf(1) expect(rows).to.have.lengthOf(1)
expect(rows[0].findAll('td')[0].text()).to.equal('qux') expect(rows[0].findAll('td')[0].text()).to.equal('qux')
expect(rows[0].findAll('td')[1].text()).to.equal('4') expect(rows[0].findAll('td')[1].text()).to.equal('4')
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text()).to.contain(
.to.contain('Preview parsing is completed in') 'Preview parsing is completed in'
)
parse.onCall(4).resolves({ parse.onCall(4).resolves({
delimiter: ',', delimiter: ',',
@@ -257,8 +273,9 @@ describe('CsvJsonImport.vue', () => {
expect(rows[0].findAll('td')[0].text()).to.equal('corge') expect(rows[0].findAll('td')[0].text()).to.equal('corge')
expect(rows[0].findAll('td')[1].text()).to.equal('5') expect(rows[0].findAll('td')[1].text()).to.equal('5')
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
.to.include('Preview parsing is completed in') 'Preview parsing is completed in'
)
}) })
it('has proper state before parsing is complete', async () => { it('has proper state before parsing is complete', async () => {
@@ -282,8 +299,10 @@ describe('CsvJsonImport.vue', () => {
await nextTick() await nextTick()
let resolveParsing let resolveParsing
parse.onCall(1).returns(new Promise(resolve => { parse.onCall(1).returns(
resolveParsing = () => resolve({ new Promise(resolve => {
resolveParsing = () =>
resolve({
delimiter: '|', delimiter: '|',
data: { data: {
columns: ['col1', 'col2'], columns: ['col1', 'col2'],
@@ -295,29 +314,40 @@ describe('CsvJsonImport.vue', () => {
rowCount: 1, rowCount: 1,
messages: [] messages: []
}) })
})) })
)
await wrapper.find('#csv-json-table-name input').setValue('foo') await wrapper.find('#csv-json-table-name input').setValue('foo')
await wrapper.find('#import-start').trigger('click') await wrapper.find('#import-start').trigger('click')
// "Parsing CSV..." in the logs // "Parsing CSV..." in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()) expect(
.to.equal('Parsing CSV...') wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()
).to.equal('Parsing CSV...')
// After 1 second - loading indicator is shown // After 1 second - loading indicator is shown
await clock.tick(1000) await clock.tick(1000)
expect( expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists() wrapper
.findComponent({ name: 'logs' })
.findComponent({ name: 'LoadingIndicator' })
.exists()
).to.equal(true) ).to.equal(true)
// All the dialog controls are disabled // All the dialog controls are disabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true) expect(
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
).to.equal(true)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true) expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true) expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true) expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#import-finish').element.disabled).to.equal(true) expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
await resolveParsing() await resolveParsing()
@@ -325,7 +355,10 @@ describe('CsvJsonImport.vue', () => {
// Loading indicator is not shown when parsing is compete // Loading indicator is not shown when parsing is compete
expect( expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists() wrapper
.findComponent({ name: 'logs' })
.findComponent({ name: 'LoadingIndicator' })
.exists()
).to.equal(false) ).to.equal(false)
}) })
@@ -365,26 +398,35 @@ describe('CsvJsonImport.vue', () => {
await nextTick() await nextTick()
let resolveImport let resolveImport
wrapper.vm.db.addTableFromCsv.onCall(0).returns(new Promise(resolve => { wrapper.vm.db.addTableFromCsv.onCall(0).returns(
new Promise(resolve => {
resolveImport = resolve resolveImport = resolve
})) })
)
await wrapper.find('#csv-json-table-name input').setValue('foo') await wrapper.find('#csv-json-table-name input').setValue('foo')
await wrapper.find('#import-start').trigger('click') await wrapper.find('#import-start').trigger('click')
await csv.parse.returnValues[1] await csv.parse.returnValues[1]
// Parsing success in the logs // Parsing success in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()) expect(
.to.include('2 rows are parsed successfully in') wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()
).to.include('2 rows are parsed successfully in')
// All the dialog controls are disabled // All the dialog controls are disabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true) expect(
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
).to.equal(true)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true) expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true) expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true) expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#import-finish').element.disabled).to.equal(true) expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
await resolveImport() await resolveImport()
@@ -417,18 +459,22 @@ describe('CsvJsonImport.vue', () => {
}, },
rowCount: 2, rowCount: 2,
hasErrors: false, hasErrors: false,
messages: [{ messages: [
{
code: 'UndetectableDelimiter', code: 'UndetectableDelimiter',
message: 'Comma was used as a standart delimiter', message: 'Comma was used as a standart delimiter',
type: 'info', type: 'info',
hint: undefined hint: undefined
}] }
]
}) })
let resolveImport let resolveImport
wrapper.vm.db.addTableFromCsv.onCall(0).returns(new Promise(resolve => { wrapper.vm.db.addTableFromCsv.onCall(0).returns(
new Promise(resolve => {
resolveImport = resolve resolveImport = resolve
})) })
)
wrapper.vm.preview() wrapper.vm.preview()
wrapper.vm.open() wrapper.vm.open()
@@ -446,13 +492,19 @@ describe('CsvJsonImport.vue', () => {
expect(logs[2].text()).to.equals('Comma was used as a standart delimiter.') expect(logs[2].text()).to.equals('Comma was used as a standart delimiter.')
// All the dialog controls are disabled // All the dialog controls are disabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true) expect(
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
).to.equal(true)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true) expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true) expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true) expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#import-finish').element.disabled).to.equal(true) expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
await resolveImport() await resolveImport()
@@ -485,12 +537,14 @@ describe('CsvJsonImport.vue', () => {
}, },
rowCount: 2, rowCount: 2,
hasErrors: true, hasErrors: true,
messages: [{ messages: [
{
code: 'Error', code: 'Error',
message: 'Something is wrong', message: 'Something is wrong',
type: 'error', type: 'error',
hint: undefined hint: undefined
}] }
]
}) })
wrapper.vm.preview() wrapper.vm.preview()
@@ -509,13 +563,19 @@ describe('CsvJsonImport.vue', () => {
expect(logs[2].text()).to.equals('Something is wrong.') expect(logs[2].text()).to.equals('Something is wrong.')
// All the dialog controls are enabled // All the dialog controls are enabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(false) expect(
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
).to.equal(false)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(false) expect(wrapper.find('#quote-char input').element.disabled).to.equal(false)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(false) expect(wrapper.find('#escape-char input').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(false) expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
false
)
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false) expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
expect(wrapper.find('#import-finish').element.disabled).to.equal(false) expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
false
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
}) })
@@ -551,8 +611,11 @@ describe('CsvJsonImport.vue', () => {
}) })
let resolveImport = sinon.stub() let resolveImport = sinon.stub()
wrapper.vm.db.addTableFromCsv = sinon.stub() wrapper.vm.db.addTableFromCsv = sinon.stub().resolves(
.resolves(new Promise(resolve => { resolveImport = resolve })) new Promise(resolve => {
resolveImport = resolve
})
)
wrapper.vm.preview() wrapper.vm.preview()
wrapper.vm.open() wrapper.vm.open()
@@ -564,23 +627,33 @@ describe('CsvJsonImport.vue', () => {
await csv.parse.returnValues[1] await csv.parse.returnValues[1]
// Parsing success in the logs // Parsing success in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text()) expect(
.to.equal('Importing CSV into a SQLite database...') wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text()
).to.equal('Importing CSV into a SQLite database...')
// After 1 second - loading indicator is shown // After 1 second - loading indicator is shown
await clock.tick(1000) await clock.tick(1000)
expect( expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists() wrapper
.findComponent({ name: 'logs' })
.findComponent({ name: 'LoadingIndicator' })
.exists()
).to.equal(true) ).to.equal(true)
// All the dialog controls are disabled // All the dialog controls are disabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true) expect(
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
).to.equal(true)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true) expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true) expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true) expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#import-finish').element.disabled).to.equal(true) expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name
@@ -589,7 +662,10 @@ describe('CsvJsonImport.vue', () => {
await resolveImport() await resolveImport()
await wrapper.vm.db.addTableFromCsv.returnValues[0] await wrapper.vm.db.addTableFromCsv.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)
}) })
@@ -635,16 +711,24 @@ describe('CsvJsonImport.vue', () => {
// Import success in the logs // Import success in the logs
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg') const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
expect(logs).to.have.lengthOf(3) expect(logs).to.have.lengthOf(3)
expect(logs[2].text()).to.contain('Importing CSV into a SQLite database is completed in') expect(logs[2].text()).to.contain(
'Importing CSV into a SQLite database is completed in'
)
// All the dialog controls are enabled // All the dialog controls are enabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(false) expect(
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
).to.equal(false)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(false) expect(wrapper.find('#quote-char input').element.disabled).to.equal(false)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(false) expect(wrapper.find('#escape-char input').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(false) expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
false
)
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false) expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
expect(wrapper.find('#import-finish').element.disabled).to.equal(false) expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
false
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(true) expect(wrapper.find('#import-finish').isVisible()).to.equal(true)
}) })
@@ -696,13 +780,19 @@ describe('CsvJsonImport.vue', () => {
expect(logs[3].text()).to.equal('Error: fail.') expect(logs[3].text()).to.equal('Error: fail.')
// All the dialog controls are enabled // All the dialog controls are enabled
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(false) expect(
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
).to.equal(false)
expect(wrapper.find('#quote-char input').element.disabled).to.equal(false) expect(wrapper.find('#quote-char input').element.disabled).to.equal(false)
expect(wrapper.find('#escape-char input').element.disabled).to.equal(false) expect(wrapper.find('#escape-char input').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(false) expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
false
)
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false) expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
expect(wrapper.find('#import-finish').element.disabled).to.equal(false) expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
false
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
}) })
@@ -733,7 +823,9 @@ describe('CsvJsonImport.vue', () => {
await clock.tick(100) await clock.tick(100)
expect(actions.addTab.calledOnce).to.equal(true) expect(actions.addTab.calledOnce).to.equal(true)
await actions.addTab.returnValues[0] await actions.addTab.returnValues[0]
expect(mutations.setCurrentTabId.calledOnceWith(state, newTabId)).to.equal(true) expect(mutations.setCurrentTabId.calledOnceWith(state, newTabId)).to.equal(
true
)
expect(wrapper.find('.dialog.vfm').exists()).to.equal(false) expect(wrapper.find('.dialog.vfm').exists()).to.equal(false)
expect(wrapper.emitted('finish')).to.have.lengthOf(1) expect(wrapper.emitted('finish')).to.have.lengthOf(1)
}) })
@@ -764,7 +856,9 @@ describe('CsvJsonImport.vue', () => {
await wrapper.find('#import-cancel').trigger('click') await wrapper.find('#import-cancel').trigger('click')
await clock.tick(100) await clock.tick(100)
expect(wrapper.find('.dialog.vfm').exists()).to.equal(false) expect(wrapper.find('.dialog.vfm').exists()).to.equal(false)
expect(wrapper.vm.db.execute.calledOnceWith('DROP TABLE "my_data"')).to.equal(true) expect(
wrapper.vm.db.execute.calledOnceWith('DROP TABLE "my_data"')
).to.equal(true)
expect(wrapper.vm.db.refreshSchema.calledOnce).to.equal(true) expect(wrapper.vm.db.refreshSchema.calledOnce).to.equal(true)
expect(wrapper.emitted('cancel')).to.have.lengthOf(1) expect(wrapper.emitted('cancel')).to.have.lengthOf(1)
}) })
@@ -783,23 +877,31 @@ describe('CsvJsonImport.vue', () => {
await wrapper.find('#csv-json-table-name input').setValue('foo') await wrapper.find('#csv-json-table-name input').setValue('foo')
await clock.tick(400) await clock.tick(400)
await nextTick() await nextTick()
expect(wrapper.find('#csv-json-table-name .text-field-error').text()).to.equal('') expect(
wrapper.find('#csv-json-table-name .text-field-error').text()
).to.equal('')
wrapper.vm.db.validateTableName = sinon.stub().rejects(new Error('this is a bad table name')) wrapper.vm.db.validateTableName = sinon
.stub()
.rejects(new Error('this is a bad table name'))
await wrapper.find('#csv-json-table-name input').setValue('bar') await wrapper.find('#csv-json-table-name input').setValue('bar')
await clock.tick(400) await clock.tick(400)
await nextTick() await nextTick()
expect(wrapper.find('#csv-json-table-name .text-field-error').text()) expect(
.to.equal('this is a bad table name. Try another table name.') wrapper.find('#csv-json-table-name .text-field-error').text()
).to.equal('this is a bad table name. Try another table name.')
await wrapper.find('#csv-json-table-name input').setValue('') await wrapper.find('#csv-json-table-name input').setValue('')
await clock.tick(400) await clock.tick(400)
await nextTick() await nextTick()
expect(wrapper.find('#csv-json-table-name .text-field-error').text()).to.equal('') expect(
wrapper.find('#csv-json-table-name .text-field-error').text()
).to.equal('')
await wrapper.find('#import-start').trigger('click') await wrapper.find('#import-start').trigger('click')
expect(wrapper.find('#csv-json-table-name .text-field-error').text()) expect(
.to.equal("Table name can't be empty") wrapper.find('#csv-json-table-name .text-field-error').text()
).to.equal("Table name can't be empty")
expect(wrapper.vm.db.addTableFromCsv.called).to.equal(false) expect(wrapper.vm.db.addTableFromCsv.called).to.equal(false)
}) })
}) })
@@ -812,12 +914,14 @@ describe('CsvJsonImport.vue - json', () => {
let wrapper let wrapper
const newTabId = 1 const newTabId = 1
const file = new File( const file = new File(
[new Blob( [
[JSON.stringify({ foo: [1, 2, 3] }, null, 2)], new Blob([JSON.stringify({ foo: [1, 2, 3] }, null, 2)], {
{ type: 'application/json' } type: 'application/json'
)], })
],
'my data.json', 'my data.json',
{ type: 'application/json' }) { type: 'application/json' }
)
beforeEach(() => { beforeEach(() => {
clock = sinon.useFakeTimers() clock = sinon.useFakeTimers()
@@ -873,25 +977,25 @@ describe('CsvJsonImport.vue - json', () => {
await nextTick() await nextTick()
expect(wrapper.find('.dialog.vfm').exists()).to.equal(true) expect(wrapper.find('.dialog.vfm').exists()).to.equal(true)
expect(wrapper.find('.dialog-header').text()).to.equal('JSON import') expect(wrapper.find('.dialog-header').text()).to.equal('JSON import')
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data') expect(wrapper.find('#csv-json-table-name input').element.value).to.equal(
expect(wrapper.findComponent({ name: 'delimiter-selector' }).exists()).to.equal(false) 'my_data'
)
expect(
wrapper.findComponent({ name: 'delimiter-selector' }).exists()
).to.equal(false)
expect(wrapper.find('#quote-char input').exists()).to.equal(false) expect(wrapper.find('#quote-char input').exists()).to.equal(false)
expect(wrapper.find('#escape-char input').exists()).to.equal(false) expect(wrapper.find('#escape-char input').exists()).to.equal(false)
expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(false) expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(
false
)
const rows = wrapper.findAll('tbody tr') const rows = wrapper.findAll('tbody tr')
expect(rows).to.have.lengthOf(1) expect(rows).to.have.lengthOf(1)
expect(rows[0].findAll('td')[0].text()).to.equal([ expect(rows[0].findAll('td')[0].text()).to.equal(
'{', ['{', ' "foo": [', ' 1,', ' 2,', ' 3', ' ]', '}'].join('\n')
' "foo": [', )
' 1,', expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
' 2,', 'Preview parsing is completed in'
' 3',
' ]',
'}'
].join('\n')
) )
expect(wrapper.findComponent({ name: 'logs' }).text())
.to.include('Preview parsing is completed in')
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
}) })
@@ -912,8 +1016,10 @@ describe('CsvJsonImport.vue - json', () => {
}) })
let resolveParsing let resolveParsing
getJsonParseResult.onCall(1).returns(new Promise(resolve => { getJsonParseResult.onCall(1).returns(
resolveParsing = () => resolve({ new Promise(resolve => {
resolveParsing = () =>
resolve({
delimiter: '|', delimiter: '|',
data: { data: {
columns: ['doc'], columns: ['doc'],
@@ -925,7 +1031,8 @@ describe('CsvJsonImport.vue - json', () => {
hasErrors: false, hasErrors: false,
messages: [] messages: []
}) })
})) })
)
await wrapper.vm.preview() await wrapper.vm.preview()
await wrapper.vm.open() await wrapper.vm.open()
@@ -937,19 +1044,25 @@ describe('CsvJsonImport.vue - json', () => {
await nextTick() await nextTick()
// "Parsing JSON..." in the logs // "Parsing JSON..." in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()) expect(
.to.equal('Parsing JSON...') wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()
).to.equal('Parsing JSON...')
// After 1 second - loading indicator is shown // After 1 second - loading indicator is shown
await clock.tick(1000) await clock.tick(1000)
expect( expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists() wrapper
.findComponent({ name: 'logs' })
.findComponent({ name: 'LoadingIndicator' })
.exists()
).to.equal(true) ).to.equal(true)
// All the dialog controls are disabled // All the dialog controls are disabled
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true) expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#import-finish').element.disabled).to.equal(true) expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
await resolveParsing() await resolveParsing()
@@ -957,7 +1070,10 @@ describe('CsvJsonImport.vue - json', () => {
// Loading indicator is not shown when parsing is compete // Loading indicator is not shown when parsing is compete
expect( expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists() wrapper
.findComponent({ name: 'logs' })
.findComponent({ name: 'LoadingIndicator' })
.exists()
).to.equal(false) ).to.equal(false)
}) })
@@ -965,8 +1081,11 @@ describe('CsvJsonImport.vue - json', () => {
const getJsonParseResult = sinon.spy(wrapper.vm, 'getJsonParseResult') const getJsonParseResult = sinon.spy(wrapper.vm, 'getJsonParseResult')
let resolveImport = sinon.stub() let resolveImport = sinon.stub()
wrapper.vm.db.addTableFromCsv = sinon.stub() wrapper.vm.db.addTableFromCsv = sinon.stub().resolves(
.resolves(new Promise(resolve => { resolveImport = resolve })) new Promise(resolve => {
resolveImport = resolve
})
)
await wrapper.vm.preview() await wrapper.vm.preview()
await wrapper.vm.open() await wrapper.vm.open()
@@ -979,19 +1098,25 @@ describe('CsvJsonImport.vue - json', () => {
await nextTick() await nextTick()
// Parsing success in the logs // Parsing success in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text()) expect(
.to.equal('Importing JSON into a SQLite database...') wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text()
).to.equal('Importing JSON into a SQLite database...')
// After 1 second - loading indicator is shown // After 1 second - loading indicator is shown
await clock.tick(1000) await clock.tick(1000)
expect( expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists() wrapper
.findComponent({ name: 'logs' })
.findComponent({ name: 'LoadingIndicator' })
.exists()
).to.equal(true) ).to.equal(true)
// All the dialog controls are disabled // All the dialog controls are disabled
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true) expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#import-finish').element.disabled).to.equal(true) expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name
@@ -1000,7 +1125,10 @@ describe('CsvJsonImport.vue - json', () => {
await resolveImport() await resolveImport()
await wrapper.vm.db.addTableFromCsv.returnValues[0] await wrapper.vm.db.addTableFromCsv.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)
}) })
@@ -1021,12 +1149,16 @@ describe('CsvJsonImport.vue - json', () => {
// Import success in the logs // Import success in the logs
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg') const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
expect(logs).to.have.lengthOf(3) expect(logs).to.have.lengthOf(3)
expect(logs[2].text()).to.contain('Importing JSON into a SQLite database is completed in') expect(logs[2].text()).to.contain(
'Importing JSON into a SQLite database is completed in'
)
// All the dialog controls are enabled // All the dialog controls are enabled
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false) expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
expect(wrapper.find('#import-finish').element.disabled).to.equal(false) expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
false
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(true) expect(wrapper.find('#import-finish').isVisible()).to.equal(true)
}) })
}) })
@@ -1106,16 +1238,23 @@ describe('CsvJsonImport.vue - ndjson', () => {
await nextTick() await nextTick()
expect(wrapper.find('.dialog.vfm').exists()).to.equal(true) expect(wrapper.find('.dialog.vfm').exists()).to.equal(true)
expect(wrapper.find('.dialog-header').text()).to.equal('JSON import') expect(wrapper.find('.dialog-header').text()).to.equal('JSON import')
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data') expect(wrapper.find('#csv-json-table-name input').element.value).to.equal(
expect(wrapper.findComponent({ name: 'delimiter-selector' }).exists()).to.equal(false) 'my_data'
)
expect(
wrapper.findComponent({ name: 'delimiter-selector' }).exists()
).to.equal(false)
expect(wrapper.find('#quote-char input').exists()).to.equal(false) expect(wrapper.find('#quote-char input').exists()).to.equal(false)
expect(wrapper.find('#escape-char input').exists()).to.equal(false) expect(wrapper.find('#escape-char input').exists()).to.equal(false)
expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(false) expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(
false
)
const rows = wrapper.findAll('tbody tr') const rows = wrapper.findAll('tbody tr')
expect(rows).to.have.lengthOf(1) expect(rows).to.have.lengthOf(1)
expect(rows[0].findAll('td')[0].text()).to.equal('{ "foo": [ 1, 2, 3 ] }') expect(rows[0].findAll('td')[0].text()).to.equal('{ "foo": [ 1, 2, 3 ] }')
expect(wrapper.findComponent({ name: 'logs' }).text()) expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
.to.include('Preview parsing is completed in') 'Preview parsing is completed in'
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
}) })
@@ -1139,8 +1278,10 @@ describe('CsvJsonImport.vue - ndjson', () => {
await nextTick() await nextTick()
let resolveParsing let resolveParsing
parse.onCall(1).returns(new Promise(resolve => { parse.onCall(1).returns(
resolveParsing = () => resolve({ new Promise(resolve => {
resolveParsing = () =>
resolve({
delimiter: '|', delimiter: '|',
data: { data: {
columns: ['doc'], columns: ['doc'],
@@ -1151,26 +1292,33 @@ describe('CsvJsonImport.vue - ndjson', () => {
rowCount: 1, rowCount: 1,
messages: [] messages: []
}) })
})) })
)
await wrapper.find('#csv-json-table-name input').setValue('foo') await wrapper.find('#csv-json-table-name input').setValue('foo')
await wrapper.find('#import-start').trigger('click') await wrapper.find('#import-start').trigger('click')
await nextTick() await nextTick()
// "Parsing JSON..." in the logs // "Parsing JSON..." in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()) expect(
.to.equal('Parsing JSON...') wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()
).to.equal('Parsing JSON...')
// After 1 second - loading indicator is shown // After 1 second - loading indicator is shown
await clock.tick(1000) await clock.tick(1000)
expect( expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists() wrapper
.findComponent({ name: 'logs' })
.findComponent({ name: 'LoadingIndicator' })
.exists()
).to.equal(true) ).to.equal(true)
// All the dialog controls are disabled // All the dialog controls are disabled
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true) expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#import-finish').element.disabled).to.equal(true) expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
await resolveParsing() await resolveParsing()
@@ -1178,7 +1326,10 @@ describe('CsvJsonImport.vue - ndjson', () => {
// Loading indicator is not shown when parsing is compete // Loading indicator is not shown when parsing is compete
expect( expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists() wrapper
.findComponent({ name: 'logs' })
.findComponent({ name: 'LoadingIndicator' })
.exists()
).to.equal(false) ).to.equal(false)
}) })
@@ -1211,8 +1362,11 @@ describe('CsvJsonImport.vue - ndjson', () => {
}) })
let resolveImport = sinon.stub() let resolveImport = sinon.stub()
wrapper.vm.db.addTableFromCsv = sinon.stub() wrapper.vm.db.addTableFromCsv = sinon.stub().resolves(
.resolves(new Promise(resolve => { resolveImport = resolve })) new Promise(resolve => {
resolveImport = resolve
})
)
wrapper.vm.preview() wrapper.vm.preview()
wrapper.vm.open() wrapper.vm.open()
@@ -1225,19 +1379,25 @@ describe('CsvJsonImport.vue - ndjson', () => {
await nextTick() await nextTick()
// Parsing success in the logs // Parsing success in the logs
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text()) expect(
.to.equal('Importing JSON into a SQLite database...') wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text()
).to.equal('Importing JSON into a SQLite database...')
// After 1 second - loading indicator is shown // After 1 second - loading indicator is shown
await clock.tick(1000) await clock.tick(1000)
expect( expect(
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists() wrapper
.findComponent({ name: 'logs' })
.findComponent({ name: 'LoadingIndicator' })
.exists()
).to.equal(true) ).to.equal(true)
// All the dialog controls are disabled // All the dialog controls are disabled
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true) expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
expect(wrapper.find('#import-finish').element.disabled).to.equal(true) expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
true
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(false) expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
expect(wrapper.find('#import-start').isVisible()).to.equal(true) expect(wrapper.find('#import-start').isVisible()).to.equal(true)
expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name
@@ -1246,7 +1406,10 @@ describe('CsvJsonImport.vue - ndjson', () => {
await resolveImport() await resolveImport()
await wrapper.vm.db.addTableFromCsv.returnValues[0] await wrapper.vm.db.addTableFromCsv.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)
}) })
@@ -1291,12 +1454,16 @@ describe('CsvJsonImport.vue - ndjson', () => {
// Import success in the logs // Import success in the logs
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg') const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
expect(logs).to.have.lengthOf(3) expect(logs).to.have.lengthOf(3)
expect(logs[2].text()).to.contain('Importing JSON into a SQLite database is completed in') expect(logs[2].text()).to.contain(
'Importing JSON into a SQLite database is completed in'
)
// All the dialog controls are enabled // All the dialog controls are enabled
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false) expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
expect(wrapper.find('#import-finish').element.disabled).to.equal(false) expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false) expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
false
)
expect(wrapper.find('#import-finish').isVisible()).to.equal(true) expect(wrapper.find('#import-finish').isVisible()).to.equal(true)
}) })
}) })

View File

@@ -39,7 +39,7 @@ describe('DelimiterSelector', async () => {
const wrapper = shallowMount(DelimiterSelector, { const wrapper = shallowMount(DelimiterSelector, {
props: { props: {
modelValue: ',', modelValue: ',',
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }) 'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
} }
}) })
@@ -51,7 +51,7 @@ describe('DelimiterSelector', async () => {
const wrapper = mount(DelimiterSelector, { const wrapper = mount(DelimiterSelector, {
props: { props: {
modelValue: '|', modelValue: '|',
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }) 'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
}, },
attachTo: document.body attachTo: document.body
}) })
@@ -68,7 +68,7 @@ describe('DelimiterSelector', async () => {
const wrapper = mount(DelimiterSelector, { const wrapper = mount(DelimiterSelector, {
props: { props: {
modelValue: '|', modelValue: '|',
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }) 'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
} }
}) })
@@ -96,7 +96,7 @@ describe('DelimiterSelector', async () => {
props: { props: {
modelValue: '|', modelValue: '|',
disabled: true, disabled: true,
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }) 'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
}, },
attachTo: document.body attachTo: document.body
}) })

View File

@@ -7,7 +7,6 @@ import fu from '@/lib/utils/fileIo'
import database from '@/lib/database' import database from '@/lib/database'
import { nextTick } from 'vue' import { nextTick } from 'vue'
describe('DbUploader.vue', () => { describe('DbUploader.vue', () => {
let state = {} let state = {}
let mutations = {} let mutations = {}

View File

@@ -19,7 +19,8 @@ describe('LoadingIndicator.vue', () => {
}) })
// The lendth of circle in the component is 50.24. If progress is 50% then resulting arc // The lendth of circle in the component is 50.24. If progress is 50% then resulting arc
// should be 25.12 // should be 25.12
expect(wrapper.find('.loader-svg.front').element.style.strokeDasharray) expect(
.to.equal('25.12px, 25.12px') wrapper.find('.loader-svg.front').element.style.strokeDasharray
).to.equal('25.12px, 25.12px')
}) })
}) })

View File

@@ -31,8 +31,9 @@ describe('Logs.vue', () => {
}) })
await nextTick() await nextTick()
const height = wrapper.find('.logs-container').element.scrollHeight const height = wrapper.find('.logs-container').element.scrollHeight
expect(wrapper.find('.logs-container').element.scrollTop) expect(wrapper.find('.logs-container').element.scrollTop).to.equal(
.to.equal(height - viewHeight) height - viewHeight
)
wrapper.unmount() wrapper.unmount()
}) })
@@ -58,8 +59,9 @@ describe('Logs.vue', () => {
await nextTick() await nextTick()
await nextTick() await nextTick()
const height = wrapper.find('.logs-container').element.scrollHeight const height = wrapper.find('.logs-container').element.scrollHeight
expect(wrapper.find('.logs-container').element.scrollTop) expect(wrapper.find('.logs-container').element.scrollTop).to.equal(
.to.equal(height - viewHeight) height - viewHeight
)
wrapper.unmount() wrapper.unmount()
}) })

View File

@@ -18,8 +18,12 @@ describe('Splitpanes.vue', () => {
}) })
expect(wrapper.findAll('.splitpanes-pane')).to.have.lengthOf(2) expect(wrapper.findAll('.splitpanes-pane')).to.have.lengthOf(2)
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('60%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('40%') '60%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'40%'
)
}) })
it('renders correctly - horizontal', () => { it('renders correctly - horizontal', () => {
@@ -37,8 +41,12 @@ describe('Splitpanes.vue', () => {
}) })
expect(wrapper.findAll('.splitpanes-pane')).to.have.lengthOf(2) expect(wrapper.findAll('.splitpanes-pane')).to.have.lengthOf(2)
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.height).to.equal('60%') expect(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.height).to.equal('40%') wrapper.findAll('.splitpanes-pane')[0].element.style.height
).to.equal('60%')
expect(
wrapper.findAll('.splitpanes-pane')[1].element.style.height
).to.equal('40%')
}) })
it('toggles correctly - no maximized initially', async () => { it('toggles correctly - no maximized initially', async () => {
@@ -55,20 +63,36 @@ describe('Splitpanes.vue', () => {
}) })
await wrapper.find('.toggle-btn').trigger('click') await wrapper.find('.toggle-btn').trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('0%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('100%') '0%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'100%'
)
await wrapper.find('.toggle-btn').trigger('click') await wrapper.find('.toggle-btn').trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('60%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('40%') '60%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'40%'
)
await wrapper.findAll('.toggle-btn')[1].trigger('click') await wrapper.findAll('.toggle-btn')[1].trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('100%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('0%') '100%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'0%'
)
await wrapper.find('.toggle-btn').trigger('click') await wrapper.find('.toggle-btn').trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('60%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('40%') '60%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'40%'
)
}) })
it('toggles correctly - with maximized initially', async () => { it('toggles correctly - with maximized initially', async () => {
@@ -86,20 +110,36 @@ describe('Splitpanes.vue', () => {
}) })
await wrapper.find('.toggle-btn').trigger('click') await wrapper.find('.toggle-btn').trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('20%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('80%') '20%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'80%'
)
await wrapper.findAll('.toggle-btn')[0].trigger('click') await wrapper.findAll('.toggle-btn')[0].trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('0%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('100%') '0%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'100%'
)
await wrapper.find('.toggle-btn').trigger('click') await wrapper.find('.toggle-btn').trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('20%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('80%') '20%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'80%'
)
await wrapper.findAll('.toggle-btn')[1].trigger('click') await wrapper.findAll('.toggle-btn')[1].trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('100%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('0%') '100%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'0%'
)
wrapper = shallowMount(Splitpanes, { wrapper = shallowMount(Splitpanes, {
slots: { slots: {
@@ -113,20 +153,36 @@ describe('Splitpanes.vue', () => {
}) })
await wrapper.find('.toggle-btn').trigger('click') await wrapper.find('.toggle-btn').trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('50%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('50%') '50%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'50%'
)
await wrapper.findAll('.toggle-btn')[0].trigger('click') await wrapper.findAll('.toggle-btn')[0].trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('0%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('100%') '0%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'100%'
)
await wrapper.find('.toggle-btn').trigger('click') await wrapper.find('.toggle-btn').trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('50%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('50%') '50%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'50%'
)
await wrapper.findAll('.toggle-btn')[1].trigger('click') await wrapper.findAll('.toggle-btn')[1].trigger('click')
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('100%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('0%') '100%'
)
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
'0%'
)
}) })
it('drag - vertical', async () => { it('drag - vertical', async () => {
@@ -151,13 +207,17 @@ describe('Splitpanes.vue', () => {
parent.style.height = '500px' parent.style.height = '500px'
await wrapper.find('.splitpanes-splitter').trigger('mousedown') await wrapper.find('.splitpanes-splitter').trigger('mousedown')
document.dispatchEvent(new MouseEvent('mousemove', { document.dispatchEvent(
new MouseEvent('mousemove', {
clientX: 300, clientX: 300,
clientY: 80 clientY: 80
})) })
)
document.dispatchEvent(new MouseEvent('mouseup')) document.dispatchEvent(new MouseEvent('mouseup'))
await nextTick() await nextTick()
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('50%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
'50%'
)
wrapper.unmount() wrapper.unmount()
root.remove() root.remove()
}) })
@@ -186,14 +246,18 @@ describe('Splitpanes.vue', () => {
parent.style.height = '500px' parent.style.height = '500px'
await wrapper.find('.splitpanes-splitter').trigger('mousedown') await wrapper.find('.splitpanes-splitter').trigger('mousedown')
document.dispatchEvent(new MouseEvent('mousemove', { document.dispatchEvent(
new MouseEvent('mousemove', {
clientX: 10, clientX: 10,
clientY: 250 clientY: 250
})) })
)
document.dispatchEvent(new MouseEvent('mouseup')) document.dispatchEvent(new MouseEvent('mouseup'))
await nextTick() await nextTick()
await nextTick() await nextTick()
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.height).to.equal('50%') expect(
wrapper.findAll('.splitpanes-pane')[0].element.style.height
).to.equal('50%')
wrapper.unmount() wrapper.unmount()
root.remove() root.remove()
}) })
@@ -225,16 +289,20 @@ describe('Splitpanes.vue', () => {
await wrapper.find('.splitpanes-splitter').trigger('touchstart') await wrapper.find('.splitpanes-splitter').trigger('touchstart')
const event = new TouchEvent('touchmove') const event = new TouchEvent('touchmove')
Object.defineProperty(event, 'touches', { Object.defineProperty(event, 'touches', {
value: [{ value: [
{
clientX: 10, clientX: 10,
clientY: 250 clientY: 250
}], }
],
writable: true writable: true
}) })
document.dispatchEvent(event) document.dispatchEvent(event)
document.dispatchEvent(new MouseEvent('touchend')) document.dispatchEvent(new MouseEvent('touchend'))
await nextTick() await nextTick()
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.height).to.equal('50%') expect(
wrapper.findAll('.splitpanes-pane')[0].element.style.height
).to.equal('50%')
wrapper.unmount() wrapper.unmount()
root.remove() root.remove()
delete window.ontouchstart delete window.ontouchstart
@@ -265,16 +333,20 @@ describe('Splitpanes.vue', () => {
await wrapper.find('.splitpanes-splitter').trigger('touchstart') await wrapper.find('.splitpanes-splitter').trigger('touchstart')
const event = new TouchEvent('touchmove') const event = new TouchEvent('touchmove')
Object.defineProperty(event, 'touches', { Object.defineProperty(event, 'touches', {
value: [{ value: [
{
clientX: 300, clientX: 300,
clientY: 80 clientY: 80
}], }
],
writable: true writable: true
}) })
document.dispatchEvent(event) document.dispatchEvent(event)
document.dispatchEvent(new MouseEvent('touchend')) document.dispatchEvent(new MouseEvent('touchend'))
await nextTick() await nextTick()
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('50%') expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
'50%'
)
wrapper.unmount() wrapper.unmount()
root.remove() root.remove()
delete window.ontouchstart delete window.ontouchstart

View File

@@ -38,7 +38,11 @@ describe('splitter.js', () => {
document.body.appendChild(container) document.body.appendChild(container)
const dragPercentage = splitter.getCurrentDragPercentage(event, container, isHorisontal) const dragPercentage = splitter.getCurrentDragPercentage(
event,
container,
isHorisontal
)
expect(dragPercentage).to.equal(50) expect(dragPercentage).to.equal(50)
}) })
@@ -53,7 +57,11 @@ describe('splitter.js', () => {
document.body.appendChild(container) document.body.appendChild(container)
const dragPercentage = splitter.getCurrentDragPercentage(event, container, isHorisontal) const dragPercentage = splitter.getCurrentDragPercentage(
event,
container,
isHorisontal
)
expect(dragPercentage).to.equal(25) expect(dragPercentage).to.equal(25)
}) })

View File

@@ -12,7 +12,7 @@ describe('Pager.vue', () => {
const wrapper = mount(Pager, { const wrapper = mount(Pager, {
props: { props: {
pageCount: 5, pageCount: 5,
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }) 'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
} }
}) })

View File

@@ -97,7 +97,8 @@ describe('chartHelper.js', () => {
expect(doc.children[0].src).to.includes('plotly-latest.js') expect(doc.children[0].src).to.includes('plotly-latest.js')
expect(doc.children[1].id).to.have.lengthOf(21) expect(doc.children[1].id).to.have.lengthOf(21)
expect(doc.children[2].innerHTML).to.includes(doc.children[1].id) expect(doc.children[2].innerHTML).to.includes(doc.children[1].id)
expect(doc.children[2].innerHTML) expect(doc.children[2].innerHTML).to.includes(
.to.includes('Plotly.newPlot(el, "plotly data", "plotly layout"') 'Plotly.newPlot(el, "plotly data", "plotly layout"'
)
}) })
}) })

View File

@@ -62,7 +62,9 @@ describe('_sql.js', () => {
const data = tempDb.export() const data = tempDb.export()
const sql = await Sql.build() const sql = await Sql.build()
sql.open(data) sql.open(data)
expect(() => { sql.exec() }).to.throw('exec: Missing query string') expect(() => {
sql.exec()
}).to.throw('exec: Missing query string')
}) })
it('imports', async () => { it('imports', async () => {

View File

@@ -19,8 +19,9 @@ describe('_statements.js', () => {
it('getInsertStmt', () => { it('getInsertStmt', () => {
const columns = ['id', 'name'] const columns = ['id', 'name']
expect(stmts.getInsertStmt('foo', columns)) expect(stmts.getInsertStmt('foo', columns)).to.equal(
.to.equal('INSERT INTO "foo" ("id", "name") VALUES (?, ?);') 'INSERT INTO "foo" ("id", "name") VALUES (?, ?);'
)
}) })
it('getCreateStatement', () => { it('getCreateStatement', () => {

View File

@@ -71,10 +71,12 @@ describe('database.js', () => {
type: 'INTEGER' type: 'INTEGER'
}) })
expect(schema[1].columns).to.eql([{ expect(schema[1].columns).to.eql([
{
name: 'amount', name: 'amount',
type: 'INTEGER' type: 'INTEGER'
}]) }
])
}) })
it('creates empty db with name database', async () => { it('creates empty db with name database', async () => {
@@ -114,7 +116,9 @@ describe('database.js', () => {
buffer.name = 'foo.sqlite' buffer.name = 'foo.sqlite'
await db.loadDb(buffer) await db.loadDb(buffer)
const result = await db.execute('SELECT * from test limit 1; SELECT * from test;') const result = await db.execute(
'SELECT * from test limit 1; SELECT * from test;'
)
expect(result.values).to.eql({ expect(result.values).to.eql({
id: [1, 2], id: [1, 2],
name: ['Harry Potter', 'Draco Malfoy'], name: ['Harry Potter', 'Draco Malfoy'],
@@ -141,7 +145,9 @@ describe('database.js', () => {
const buffer = new Blob([data]) const buffer = new Blob([data])
buffer.name = 'foo.sqlite' buffer.name = 'foo.sqlite'
await db.loadDb(buffer) await db.loadDb(buffer)
await expect(db.execute('SELECT * from foo')).to.be.rejectedWith(/^no such table: foo$/) await expect(db.execute('SELECT * from foo')).to.be.rejectedWith(
/^no such table: foo$/
)
}) })
it('adds table from csv', async () => { it('adds table from csv', async () => {
@@ -186,37 +192,44 @@ describe('database.js', () => {
} }
const progressHandler = sinon.stub() const progressHandler = sinon.stub()
const progressCounterId = db.createProgressCounter(progressHandler) const progressCounterId = db.createProgressCounter(progressHandler)
await expect(db.addTableFromCsv('foo', data, progressCounterId)).to.be.rejected await expect(db.addTableFromCsv('foo', data, progressCounterId)).to.be
.rejected
}) })
it('progressCounters', () => { it('progressCounters', () => {
const firstHandler = sinon.stub() const firstHandler = sinon.stub()
const firstId = db.createProgressCounter(firstHandler) const firstId = db.createProgressCounter(firstHandler)
db.worker.dispatchEvent(new MessageEvent('message', { db.worker.dispatchEvent(
new MessageEvent('message', {
data: { data: {
progress: 50, progress: 50,
id: firstId id: firstId
} }
})) })
)
expect(firstHandler.calledOnceWith(50)).to.equal(true) expect(firstHandler.calledOnceWith(50)).to.equal(true)
const secondHandler = sinon.stub() const secondHandler = sinon.stub()
const secondId = db.createProgressCounter(secondHandler) const secondId = db.createProgressCounter(secondHandler)
db.worker.dispatchEvent(new MessageEvent('message', { db.worker.dispatchEvent(
new MessageEvent('message', {
data: { data: {
progress: 70, progress: 70,
id: secondId id: secondId
} }
})) })
)
expect(firstId).to.not.equals(secondId) expect(firstId).to.not.equals(secondId)
expect(secondHandler.calledOnceWith(70)).to.equal(true) expect(secondHandler.calledOnceWith(70)).to.equal(true)
db.worker.dispatchEvent(new MessageEvent('message', { db.worker.dispatchEvent(
new MessageEvent('message', {
data: { data: {
progress: 80, progress: 80,
id: firstId id: firstId
} }
})) })
)
expect(firstHandler.calledTwice).to.equal(true) expect(firstHandler.calledTwice).to.equal(true)
expect(firstHandler.secondCall.calledWith(80)).to.equal(true) expect(firstHandler.secondCall.calledWith(80)).to.equal(true)
@@ -268,12 +281,17 @@ describe('database.js', () => {
it('validateTableName', async () => { it('validateTableName', async () => {
await db.execute('CREATE TABLE foo(id)') await db.execute('CREATE TABLE foo(id)')
await expect(db.validateTableName('foo')).to.be.rejectedWith('table "foo" already exists') await expect(db.validateTableName('foo')).to.be.rejectedWith(
await expect(db.validateTableName('1foo')) 'table "foo" already exists'
.to.be.rejectedWith("Table name can't start with a digit") )
await expect(db.validateTableName('foo(05.08.2020)')) await expect(db.validateTableName('1foo')).to.be.rejectedWith(
.to.be.rejectedWith('Table name can contain only letters, digits and underscores') "Table name can't start with a digit"
await expect(db.validateTableName('sqlite_foo')) )
.to.be.rejectedWith("Table name can't start with sqlite_") await expect(db.validateTableName('foo(05.08.2020)')).to.be.rejectedWith(
'Table name can contain only letters, digits and underscores'
)
await expect(db.validateTableName('sqlite_foo')).to.be.rejectedWith(
"Table name can't start with sqlite_"
)
}) })
}) })

View File

@@ -80,7 +80,6 @@ describe('SQLite extensions', function () {
'sqrt(square(16))': [16], 'sqrt(square(16))': [16],
'ceil(-1.95) + ceil(1.95)': [1], 'ceil(-1.95) + ceil(1.95)': [1],
'floor(-1.95) + floor(1.95)': [-1] 'floor(-1.95) + floor(1.95)': [-1]
}) })
}) })
@@ -452,7 +451,15 @@ describe('SQLite extensions', function () {
FROM dataset; FROM dataset;
`) `)
expect(actual.values).to.eql({ expect(actual.values).to.eql({
xx: [1], xy: [0], xz: [1], yx: [0], yy: [1], yz: [1], zx: [1], zy: [1], zz: [1] xx: [1],
xy: [0],
xz: [1],
yx: [0],
yy: [1],
yz: [1],
zx: [1],
zy: [1],
zz: [1]
}) })
}) })
@@ -475,7 +482,10 @@ describe('SQLite extensions', function () {
SELECT lua_inline(1), lua_full(1) - 1 < 0.000001; SELECT lua_inline(1), lua_full(1) - 1 < 0.000001;
`) `)
expect(actual.values).to.eql({ 'lua_inline(1)': [2], 'lua_full(1) - 1 < 0.000001': [1] }) expect(actual.values).to.eql({
'lua_inline(1)': [2],
'lua_full(1) - 1 < 0.000001': [1]
})
}) })
it('supports aggregate Lua functions', async function () { it('supports aggregate Lua functions', async function () {
@@ -534,6 +544,9 @@ describe('SQLite extensions', function () {
SELECT * FROM lua_match('%w+', 'hello world from Lua'); SELECT * FROM lua_match('%w+', 'hello world from Lua');
`) `)
expect(actual.values).to.eql({ idx: [1, 2, 3, 4], elm: ['hello', 'world', 'from', 'Lua'] }) expect(actual.values).to.eql({
idx: [1, 2, 3, 4],
elm: ['hello', 'world', 'from', 'Lua']
})
}) })
}) })

View File

@@ -19,7 +19,9 @@ describe('storedInquiries.js', () => {
}) })
it('getStoredInquiries migrate and returns inquiries of v1', () => { it('getStoredInquiries migrate and returns inquiries of v1', () => {
localStorage.setItem('myQueries', JSON.stringify([ localStorage.setItem(
'myQueries',
JSON.stringify([
{ {
id: '123', id: '123',
name: 'foo', name: 'foo',
@@ -32,7 +34,8 @@ describe('storedInquiries.js', () => {
query: 'SELECT * FROM bar', query: 'SELECT * FROM bar',
chart: { here_are: 'bar chart settings' } chart: { here_are: 'bar chart settings' }
} }
])) ])
)
const inquiries = storedInquiries.getStoredInquiries() const inquiries = storedInquiries.getStoredInquiries()
expect(inquiries).to.eql([ expect(inquiries).to.eql([
{ {
@@ -53,10 +56,7 @@ describe('storedInquiries.js', () => {
}) })
it('updateStorage and getStoredInquiries', () => { it('updateStorage and getStoredInquiries', () => {
const data = [ const data = [{ id: 1 }, { id: 2 }]
{ id: 1 },
{ id: 2 }
]
storedInquiries.updateStorage(data) storedInquiries.updateStorage(data)
const inquiries = storedInquiries.getStoredInquiries() const inquiries = storedInquiries.getStoredInquiries()
expect(inquiries).to.eql(data) expect(inquiries).to.eql(data)
@@ -77,7 +77,9 @@ describe('storedInquiries.js', () => {
const copy = storedInquiries.duplicateInquiry(base) const copy = storedInquiries.duplicateInquiry(base)
expect(copy).to.have.property('id').which.not.equal(base.id) expect(copy).to.have.property('id').which.not.equal(base.id)
expect(copy).to.have.property('name').which.equal(base.name + ' Copy') expect(copy)
.to.have.property('name')
.which.equal(base.name + ' Copy')
expect(copy).to.have.property('query').which.equal(base.query) expect(copy).to.have.property('query').which.equal(base.query)
expect(copy).to.have.property('viewType').which.equal(base.viewType) expect(copy).to.have.property('viewType').which.equal(base.viewType)
expect(copy).to.have.property('viewOptions').which.eql(base.viewOptions) expect(copy).to.have.property('viewOptions').which.eql(base.viewOptions)
@@ -197,14 +199,16 @@ describe('storedInquiries.js', () => {
` `
const inquiry = storedInquiries.deserialiseInquiries(str) const inquiry = storedInquiries.deserialiseInquiries(str)
expect(inquiry).to.eql([{ expect(inquiry).to.eql([
{
id: 1, id: 1,
name: 'foo', name: 'foo',
query: 'select * from foo', query: 'select * from foo',
viewType: 'chart', viewType: 'chart',
viewOptions: [], viewOptions: [],
createdAt: '2020-11-03T14:17:49.524Z' createdAt: '2020-11-03T14:17:49.524Z'
}]) }
])
}) })
it('deserialiseInquiries generates new id to avoid duplication', () => { it('deserialiseInquiries generates new id to avoid duplication', () => {
@@ -256,14 +260,16 @@ describe('storedInquiries.js', () => {
sinon.stub(fu, 'importFile').returns(Promise.resolve(str)) sinon.stub(fu, 'importFile').returns(Promise.resolve(str))
const inquiries = await storedInquiries.importInquiries() const inquiries = await storedInquiries.importInquiries()
expect(inquiries).to.eql([{ expect(inquiries).to.eql([
{
id: 1, id: 1,
name: 'foo', name: 'foo',
query: 'select * from foo', query: 'select * from foo',
viewType: 'chart', viewType: 'chart',
viewOptions: [], viewOptions: [],
createdAt: '2020-11-03T14:17:49.524Z' createdAt: '2020-11-03T14:17:49.524Z'
}]) }
])
}) })
it('importInquiries', async () => { it('importInquiries', async () => {
@@ -281,14 +287,16 @@ describe('storedInquiries.js', () => {
sinon.stub(fu, 'importFile').returns(Promise.resolve(str)) sinon.stub(fu, 'importFile').returns(Promise.resolve(str))
const inquiries = await storedInquiries.importInquiries() const inquiries = await storedInquiries.importInquiries()
expect(inquiries).to.eql([{ expect(inquiries).to.eql([
{
id: 1, id: 1,
name: 'foo', name: 'foo',
query: 'select * from foo', query: 'select * from foo',
viewType: 'chart', viewType: 'chart',
viewOptions: [], viewOptions: [],
createdAt: '2020-11-03T14:17:49.524Z' createdAt: '2020-11-03T14:17:49.524Z'
}]) }
])
}) })
it('readPredefinedInquiries old', async () => { it('readPredefinedInquiries old', async () => {
@@ -312,7 +320,8 @@ describe('storedInquiries.js', () => {
viewType: 'chart', viewType: 'chart',
viewOptions: [], viewOptions: [],
createdAt: '2020-11-03T14:17:49.524Z' createdAt: '2020-11-03T14:17:49.524Z'
}]) }
])
}) })
it('readPredefinedInquiries', async () => { it('readPredefinedInquiries', async () => {
@@ -340,6 +349,7 @@ describe('storedInquiries.js', () => {
viewType: 'chart', viewType: 'chart',
viewOptions: [], viewOptions: [],
createdAt: '2020-11-03T14:17:49.524Z' createdAt: '2020-11-03T14:17:49.524Z'
}]) }
])
}) })
}) })

Some files were not shown because too many files have changed in this diff Show More