Commit 463c7e7b authored by schneider210's avatar schneider210
Browse files

implement unit tests with jest. configure it to match our specific needs. atm...

implement unit tests with jest. configure it to match our specific needs. atm no icons can be imported and have therefor been commented out in the component to be tested
parent 7c3ef562
{
"plugins": ["@babel/plugin-syntax-dynamic-import"],
"env": {
"test": {
"plugins": ["dynamic-import-node"],
"presets": [
[
"@babel/preset-env",
{
"modules": "commonjs",
"targets": {
"node": "current"
}
}
]
]
}
}
}
......@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.1] - 2020-09-29
### Added
- Add config for unit tests via jest. testfiles goto *tests/unit/specs/*test.js*
### Changed
- delete tab-folder with components. provided a computed prop to wait for conditional changes
## [1.4.0] - 2020-09-11
### Added
......
const fs = require('fs-extra')
let extend = undefined
/**
* The .babelrc file has been created to assist Jest for transpiling.
* You should keep your application's babel rules in this file.
*/
if (fs.existsSync('./.babelrc')) {
extend = './.babelrc'
}
module.exports = {
presets: [
'@quasar/babel-preset-app'
]
],
extends: extend
}
module.exports = {
globals: {
__DEV__: true
},
setupFilesAfterEnv: [
// '<rootDir>/tests/unit/jest.setup.js'
],
// noStackTrace: true,
// bail: true,
// cache: false,
// verbose: true,
// watch: true,
collectCoverage: false,
coverageDirectory: '<rootDir>/tests/unit/coverage',
collectCoverageFrom: [
'<rootDir>/src/**/*.vue',
'<rootDir>/src/**/*.js'
// '<rootDir>/src/**/*.ts',
// '<rootDir>/src/**/*.jsx'
],
coverageThreshold: {
global: {
// branches: 50,
// functions: 50,
// lines: 50,
// statements: 50
}
},
testMatch: [
'<rootDir>/tests/unit/**/*.spec.js',
'<rootDir>/tests/unit/**/*.test.js'
],
moduleFileExtensions: [
'vue',
'js',
'json'
// 'ts',
// 'tsx'
],
moduleNameMapper: {
'^vue$': '<rootDir>/node_modules/vue/dist/vue.common.js',
'^test-utils$': '<rootDir>/node_modules/@vue/test-utils/dist/vue-test-utils.js',
'^quasar$': '<rootDir>/node_modules/quasar/dist/quasar.common.js',
'^~/(.*)$': '<rootDir>/$1',
'^@/(.*)$': '<rootDir>/src/$1',
'^src/(.*)$': '<rootDir>/src/$1'
// '.*css$': '<rootDir>/test/unit/utils/stub.css'
},
transform: {
'.*\\.vue$': 'vue-jest',
'.*\\.js$': 'babel-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
// use these if NPM is being flaky
// '.*\\.vue$': '<rootDir>/node_modules/@quasar/quasar-app-extension-testing-unit-jest/node_modules/vue-jest',
// '.*\\.js$': '<rootDir>/node_modules/@quasar/quasar-app-extension-testing-unit-jest/node_modules/babel-jest'
},
transformIgnorePatterns: [
'<rootDir>/node_modules/(?!quasar/lang)'
],
snapshotSerializers: [
'<rootDir>/node_modules/jest-serializer-vue'
]
}
This diff is collapsed.
{
"name": "viewer",
"version": "1.3.2",
"name": "@subugoe/qviewer",
"version": "1.4.1",
"description": "Viewer for the modular framework to present digital editions",
"productName": "EMo Viewer",
"keywords": [
......@@ -33,26 +33,24 @@
"url": "https://gitlab.gwdg.de/subugoe/emo/Qviewer.git"
},
"private": false,
"publishConfig": {
"@subugoe:registry": "https://gitlab.gwdg.de/api/v4/projects/10921/packages/npm/"
},
"dependencies": {
"@quasar/extras": "^1.9.5",
"@quasar/extras": "^1.9.7",
"openseadragon": "^2.4.2",
"quasar": "^1.13.2",
"quasar": "^1.14.0",
"vuedraggable": "^2.24.1"
},
"devDependencies": {
"@quasar/app": "^2.0.8",
"@quasar/app": "^2.1.0",
"@quasar/quasar-app-extension-testing-unit-jest": "^2.0.0",
"autoprefixer": "9.8.6",
"babel-eslint": "^10.1.0",
"eslint": "^7.8.1",
"eslint": "^7.10.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-vue": "^6.2.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-vue": "^7.0.0",
"node-sass": "^4.14.1",
"sass": "^1.26.10",
"sass": "^1.26.11",
"sass-loader": "8.0.2",
"stylelint": "^13.7.2",
"stylelint-config-sass-guidelines": "^7.1.0"
......@@ -67,10 +65,24 @@
"resolutions": {
"@babel/parser": "7.7.5"
},
"unit-jest": {
"runnerCommand": "jest"
},
"@quasar/testing-unit-jest": {
"babel": "babelrc",
"options": [
"scripts",
"SFC"
]
},
"scripts": {
"build": "quasar build --modern",
"dev": "quasar dev --modern",
"lint": "eslint --ext .js,.vue src",
"lint:scss": "node_modules/.bin/stylelint 'src/**/*.{scss,vue}'"
"lint:scss": "node_modules/.bin/stylelint 'src/**/*.{scss,vue}'",
"test:unit": "jest --updateSnapshot"
},
"publishConfig": {
"@subugoe:registry": "https://gitlab.gwdg.de/api/v4/projects/10921/packages/npm/"
}
}
......@@ -22,14 +22,12 @@
justify-md-start
justify-xs-center
row-sm"
:config="config"
:itemurls="itemurls"
:labels="config.labels"
:manifests="manifests"
/>
<ToggleIndex v-if="config.headers.toggle"
:config="config"
:panels="panels"
class="
col
col-md-auto
......@@ -38,7 +36,7 @@
content-sm-center
justify-sm-evenly
row-sm"
:imageurl="imageurl"
:panels="panels"
/>
</div>
</q-header>
......
......@@ -6,8 +6,8 @@
title="Change language"
>
<q-icon
size="md"
:name="fasLanguage"
size="md"
/>
<q-menu
......@@ -15,14 +15,13 @@
fit
self="center middle"
>
<!-- FIXME: remove inline style -->
<q-list style="min-width: 100px;">
<q-list>
<q-item clickable v-close-popup>
<q-item-section>EN</q-item-section>
<q-item-section>DE</q-item-section>
</q-item>
<q-item clickable v-close-popup>
<q-item-section>DE</q-item-section>
<q-item-section>{{ lang }}</q-item-section>
</q-item>
</q-list>
</q-menu>
......@@ -35,6 +34,11 @@ import { fasLanguage } from '@quasar/extras/fontawesome-v5';
export default {
name: 'Language',
data() {
return {
lang: 'EN',
};
},
created() {
this.fasLanguage = fasLanguage;
},
......
<template>
<div>
<div class="scroll-panel">
<!-- Collection-->
<q-list v-if="Object.keys(collection).length">
<q-item>
......@@ -53,7 +53,7 @@
<q-item>
<q-item-section>
<q-item-label overline class="text-uppercase">Year:</q-item-label>
<q-item-label overline class="text-uppercase">Year of creation:</q-item-label>
<q-item-label>{{ date }}</q-item-label>
</q-item-section>
</q-item>
......@@ -67,7 +67,7 @@
<q-item>
<q-item-section>
<q-item-label overline class="text-uppercase">Location:</q-item-label>
<q-item-label overline class="text-uppercase">Current location:</q-item-label>
<q-item-label>{{ location }}</q-item-label>
</q-item-section>
</q-item>
......
<template>
<div class="scroll-panel">
<Metadata v-if="manifests.length"
:collection="collection"
:itemlabel="itemlabel"
:labels="labels"
:language="language"
:manifests="manifests"
/>
</div>
</template>
<script>
import Metadata from '@/components/metadata.vue';
export default {
props: {
collection: Object,
itemlabel: String,
labels: Object,
language: String,
manifests: Array,
},
components: {
Metadata,
},
};
</script>
<template>
<Tree v-if="tree.length && manifests.length"
:manifests="manifests"
:tree="tree"
/>
</template>
<script>
import Tree from '@/components/tree.vue';
export default {
props: {
tree: Array,
manifests: Array,
},
components: {
Tree,
},
};
</script>
......@@ -6,6 +6,7 @@
node-key="label"
:expanded.sync="expanded"
:nodes="tree"
:selected-color="$q.dark.isActive ? 'grey' : ''"
:selected.sync="selected"
>
<template v-slot:default-body={node}>
......
import Content from '@/components/content.vue';
import Metadatatab from '@/components/tab-panels/metadatatab.vue';
import Metadata from '@/components/metadata.vue';
import OpenSeadragon from '@/components/openseadragon.vue';
import Treeviewtab from '@/components/tab-panels/treeviewtab.vue';
import Treeview from '@/components/tree.vue';
import { v4 as uuidv4 } from 'uuid';
// -- Panels --
......@@ -38,11 +39,11 @@ export default {
data: () => ({
components: {
1: {
component: Treeviewtab,
component: Treeview,
label: 'Contents',
},
2: {
component: Metadatatab,
component: Metadata,
label: 'Metadata',
},
3: {
......@@ -69,7 +70,7 @@ export default {
},
setupPanels() {
// will change all id => component
// will change all ids => component
this.panels = this.panels.map((item) => {
const newConnectors = item.connector.map(
(c) => (typeof c === 'number' ? this.findComponent(c) : c),
......@@ -115,7 +116,7 @@ export default {
const component = this.findComponent([idC]);
if (from !== to) {
// Add the componet to the new panel
// Add the component to the new panel
this.panels[to].connector.push(component);
......
@mixin makeResponsiveHeight {
$sizesSreen: ('400px': '26vh','450px': '34vh', '500px': '41vh', '550px': '46vh', '600px': '50vh', '630px': '53vh', '660px': '55vh', '700px': '58vh', '750px': '61vh', '800px': '63vh', '850px': '66vh', '900px': '67vh', '980px': '70vh', '1200px': '75vh', '1300px': '77vh', '1800px': '83vh');
$screenSizes: ("400px": "26vh","450px": "34vh", "500px": "41vh", "550px": "46vh", "600px": "50vh", "630px": "53vh", "660px": "55vh", "700px": "58vh", "750px": "61vh", "800px": "63vh", "850px": "66vh", "900px": "67vh", "980px": "70vh", "1200px": "75vh", "1300px": "77vh", "1800px": "83vh");
@each $minheight, $height in $sizesSreen {
@each $minheight, $height in $screenSizes {
@media (min-height: #{$minheight}) {
height: #{$height};
}
......
......@@ -18,9 +18,7 @@
<body>
<noscript>
<strong>We're sorry but TextViewer doesn't work properly without JavaScript enabled.
Please enable it to continue.
</strong>
<strong>We're sorry but TextViewer doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<script id="emo-config" type="application/json">
......
export default {
props: {
config: Object,
itemurls: Array,
labels: Object,
manifests: Array,
},
data() {
......@@ -41,14 +41,14 @@ export default {
return this.sequenceindex < this.sequencecount - 1
&& lastindexes[this.sequenceindex] === this.itemindex
? `Next ${this.config.labels.manifest}`
: `Next ${this.config.labels.item}`;
? `Next ${this.labels.manifest}`
: `Next ${this.labels.item}`;
},
captionprev() {
return this.sequenceindex > 0 && this.firstiteminsequence === this.itemindex
? `Prev ${this.config.labels.manifest}`
: `Prev ${this.config.labels.item}`;
? `Prev ${this.labels.manifest}`
: `Prev ${this.labels.item}`;
},
computedsequenceindex() {
......
<template>
<section>
<div class="row panels-target">
<div v-for="(p, i) in panels" :key="`pc${i}`" v-show="p.show"
<div v-if="ready" class="row panels-target">
<div v-for="(p, i) in panels" :key="`pc${i}`" v-show="p.show && p.connector.length"
class="col-12 col-sm-6 col-md-3"
>
<Toolbar :heading="p.panel_label" />
<q-separator />
<div v-if="p.connector.length != 0">
<!-- shows the nested tab components -->
<div>
<!-- shows the nested tabs -->
<q-card v-if="p.connector.length > 1" flat>
<div class="tabs-container">
<q-tabs v-for="(tab, i) in p.connector" :key="`pt${i}`"
<q-tabs class="content-tabs" v-for="(tab, i) in p.connector" :key="`pt${i}`"
:active-bg-color="$q.dark.isActive ? 'bg-black' : 'bg-grey-4'"
class="content-tabs"
v-model="p.tab_model"
>
<q-tab :name="`tab${i}`" :label="tab.label" />
......@@ -24,20 +23,15 @@
<q-separator />
<q-tab-panels v-model="p.tab_model" animated class="content-panel" keep-alive>
<q-tab-panel v-for="(tab, i) in p.connector"
:key="`co${i}`"
:name="`tab${i}`"
>
<component :is="tab.component" v-bind="componentProps[tab.id]" />
<q-tab-panel v-for="(tab, i) in p.connector" :key="`co${i}`" :name="`tab${i}`">
<component :is="tab.component" :key="keys[tab.id]" v-bind="$props" />
</q-tab-panel>
</q-tab-panels>
</q-card>
<!-- shows the panels -->
<div v-else class="q-pa-md q-gutter-sm overflow-hidden">
<component v-if="p.connector.length === 1" :is="p.connector[0].component"
v-bind="componentProps[p.connector[0].id]"
/>
<div v-else-if="p.connector.length === 1" class="q-pa-md q-gutter-sm overflow-hidden">
<component :is="p.connector[0].component" :key="keys[p.connector[0].id]" v-bind="$props" />
</div>
</div>
</div>
......@@ -50,11 +44,6 @@ import Toolbar from '@/components/toolbar.vue';
export default {
name: 'MainView',
// watch: {
// '$q.dark.isActive'(val) {
// console.log(val ? 'On dark mode' : 'On light mode');
// },
// },
components: {
Toolbar,
},
......@@ -72,32 +61,11 @@ export default {
tree: Array,
},
computed: {
componentProps() {
return {
1: {
manifests: this.manifests,
tree: this.tree,
},
2: {
collection: this.collection,
config: this.config,
itemlabel: this.itemlabel,
labels: this.labels,
language: this.language,
manifests: this.manifests,
},
3: {
imageurl: this.imageurl,
key: this.imageurl,
},
4: {
contenturl: this.contenturl,
fontsize: this.fontsize,
key: this.contenturl,
manifests: this.manifests,
request: this.request,
},
};
ready() {
return Object.keys(this.collection).length && this.manifests.length && this.tree.length;
},
keys() {
return { 3: this.imageurl, 4: this.contenturl };
},
},
};
......
const fs = require('fs')
const loaderUtils = require('loader-utils')
const path = require('path')
module.exports = async function (source) {
const options = loaderUtils.getOptions(this)
const filename = path.parse(this.resourcePath).name
const extension = (options && options.extension) || '_jest.spec.js'
const dir = `${this.context}/unit`
console.log(`Created test file: ${filename}`)
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
const code = source.replace(/\n{2,}/, '')
const dest = `${dir}/${filename}${extension}`
fs.writeFileSync(dest, code)
return `<!-- Created test file: ${filename} -->`
}
import LANGUAGE from '@/components/language.vue'
jest.mock('@quasar/extras/fontawesome-v5', () => '')
describe('Language component', () => {
it('has a data prop "EN"', () => {
const lang = LANGUAGE.data()
expect(lang.lang).toBe('EN')
})
})
// this is mapped in jest.config.js to resolve @vue/test-utils
import { createLocalVue, shallowMount } from 'test-utils'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import Quasar, { Cookies } from 'quasar'
const mockSsrContext = () => {
return {
req: {
headers: {}
},
res: {
setHeader: () => undefined
}
}
}
// https://eddyerburgh.me/mock-vuex-in-vue-unit-tests
export const mountQuasar = (component, options = {}) => {
const localVue = createLocalVue()
const app = {}
localVue.use(Vuex)
localVue.use(VueRouter)
localVue.use(Quasar)
const store = new Vuex.Store({})
const router = new VueRouter()
if (options) {
const ssrContext = options.ssr ? mockSsrContext() : null
if (options.cookies) {
const cookieStorage = ssrContext ? Cookies.parseSSR(ssrContext) : Cookies
const cookies = options.cookies
Object.keys(cookies).forEach(key => {
cookieStorage.set(key, cookies[key])
})
}
if (options.plugins) {
<