first commit
This commit is contained in:
6
ui/tests/e2e/.eslintrc.js
Normal file
6
ui/tests/e2e/.eslintrc.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: ['cypress'],
|
||||
env: {
|
||||
'cypress/globals': true,
|
||||
},
|
||||
}
|
||||
55
ui/tests/e2e/plugins/index.js
Normal file
55
ui/tests/e2e/plugins/index.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// https://docs.cypress.io/guides/guides/plugins-guide.html
|
||||
module.exports = (on, config) => {
|
||||
// Dynamic configuration
|
||||
// https://docs.cypress.io/guides/references/configuration.html
|
||||
return Object.assign({}, config, {
|
||||
// ===
|
||||
// General
|
||||
// https://docs.cypress.io/guides/references/configuration.html#Global
|
||||
// ===
|
||||
watchForFileChanges: true,
|
||||
// ===
|
||||
// Environment variables
|
||||
// https://docs.cypress.io/guides/guides/environment-variables.html#Option-1-cypress-json
|
||||
// ===
|
||||
env: {
|
||||
CI: process.env.CI,
|
||||
},
|
||||
// ===
|
||||
// Viewport
|
||||
// https://docs.cypress.io/guides/references/configuration.html#Viewport
|
||||
// ===
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
// ===
|
||||
// Animations
|
||||
// https://docs.cypress.io/guides/references/configuration.html#Animations
|
||||
// ===
|
||||
waitForAnimations: true,
|
||||
animationDistanceThreshold: 4,
|
||||
// ===
|
||||
// Timeouts
|
||||
// https://docs.cypress.io/guides/references/configuration.html#Timeouts
|
||||
// ===
|
||||
defaultCommandTimeout: 4000,
|
||||
execTimeout: 60000,
|
||||
pageLoadTimeout: 60000,
|
||||
requestTimeout: 5000,
|
||||
responseTimeout: 30000,
|
||||
// ===
|
||||
// Main Directories
|
||||
// https://docs.cypress.io/guides/references/configuration.html#Folders-Files
|
||||
// ===
|
||||
supportFile: 'tests/e2e/support/setup.js',
|
||||
integrationFolder: 'tests/e2e/specs',
|
||||
fixturesFolder: 'tests/e2e/fixtures',
|
||||
// ===
|
||||
// Videos & Screenshots
|
||||
// https://docs.cypress.io/guides/core-concepts/screenshots-and-videos.html
|
||||
// ===
|
||||
videoUploadOnPasses: true,
|
||||
videoCompression: 32,
|
||||
videosFolder: 'tests/e2e/videos',
|
||||
screenshotsFolder: 'tests/e2e/screenshots',
|
||||
})
|
||||
}
|
||||
82
ui/tests/e2e/specs/auth.e2e.js
Normal file
82
ui/tests/e2e/specs/auth.e2e.js
Normal file
@@ -0,0 +1,82 @@
|
||||
describe('Authentication', () => {
|
||||
it('login link exists on the home page when logged out', () => {
|
||||
cy.visit('/')
|
||||
cy.contains('a', 'Log in').should('have.attr', 'href', '/login')
|
||||
})
|
||||
|
||||
it('login form shows an error on failure', () => {
|
||||
cy.visit('/login')
|
||||
|
||||
// Enter bad login info
|
||||
cy.get('input[name="username"]').type('badUsername')
|
||||
cy.get('input[name="password"]').type('badPassword')
|
||||
|
||||
// Submit the login form
|
||||
cy.contains('button', 'Log in').click()
|
||||
|
||||
// Ensure that an error displays
|
||||
cy.contains('error logging in')
|
||||
})
|
||||
|
||||
it('successful login works redirects to the home page and logging out works', () => {
|
||||
cy.visit('/login')
|
||||
|
||||
// Enter the user-supplied username and password
|
||||
cy.get('input[name="username"]').type('admin')
|
||||
cy.get('input[name="password"]').type('password')
|
||||
|
||||
// Submit the login form
|
||||
cy.contains('button', 'Log in').click()
|
||||
|
||||
// Confirm redirection to the homepage
|
||||
cy.location('pathname').should('equal', '/')
|
||||
|
||||
// Confirm a logout link exists
|
||||
cy.contains('a', 'Log out')
|
||||
})
|
||||
|
||||
it('login after attempting to visit authenticated route redirects to that route after login', () => {
|
||||
cy.visit('/profile?someQuery')
|
||||
|
||||
// Confirm redirection to the login page
|
||||
cy.location('pathname').should('equal', '/login')
|
||||
|
||||
// Enter the user-supplied username and password
|
||||
cy.get('input[name="username"]').type('admin')
|
||||
cy.get('input[name="password"]').type('password')
|
||||
|
||||
// Submit the login form
|
||||
cy.contains('button', 'Log in').click()
|
||||
|
||||
// Confirm redirection to the homepage
|
||||
cy.location('pathname').should('equal', '/profile')
|
||||
cy.location('search').should('equal', '?someQuery')
|
||||
|
||||
// Confirm a logout link exists
|
||||
cy.contains('a', 'Log out')
|
||||
})
|
||||
|
||||
it('logout link logs the user out when logged in', () => {
|
||||
cy.logIn()
|
||||
|
||||
// Click the logout link
|
||||
cy.contains('a', 'Log out').click()
|
||||
|
||||
// Confirm that the user is logged out
|
||||
cy.contains('a', 'Log in')
|
||||
})
|
||||
|
||||
it('logout from an authenticated route redirects to home', () => {
|
||||
cy.logIn()
|
||||
cy.visit('/profile')
|
||||
|
||||
// Click the logout link
|
||||
cy.contains('a', 'Log out').click()
|
||||
|
||||
// Confirm we're on the correct page
|
||||
cy.location('pathname').should('equal', '/')
|
||||
|
||||
// Confirm that the user is logged out
|
||||
cy.contains('a', 'Log in')
|
||||
})
|
||||
})
|
||||
7
ui/tests/e2e/specs/home.e2e.js
Normal file
7
ui/tests/e2e/specs/home.e2e.js
Normal file
@@ -0,0 +1,7 @@
|
||||
describe('Home Page', () => {
|
||||
it('has the correct title and heading', () => {
|
||||
cy.visit('/')
|
||||
cy.title().should('equal', 'Home | Hammond')
|
||||
cy.contains('h1', 'Home Page')
|
||||
})
|
||||
})
|
||||
33
ui/tests/e2e/specs/profile.e2e.js
Normal file
33
ui/tests/e2e/specs/profile.e2e.js
Normal file
@@ -0,0 +1,33 @@
|
||||
describe('Profile Page', () => {
|
||||
it('redirects to login when logged out', () => {
|
||||
cy.visit('/profile')
|
||||
cy.location('pathname').should('equal', '/login')
|
||||
})
|
||||
|
||||
it('nav link exists when logged in', () => {
|
||||
cy.logIn()
|
||||
cy.contains('a', 'Logged in as Vue Master').should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'/profile'
|
||||
)
|
||||
})
|
||||
|
||||
it('shows the current user profile when logged in', () => {
|
||||
cy.logIn()
|
||||
cy.visit('/profile')
|
||||
cy.contains('h1', 'Vue Master')
|
||||
})
|
||||
|
||||
it('shows non-current users at username routes when logged in', () => {
|
||||
cy.logIn()
|
||||
cy.visit('/profile/user1')
|
||||
cy.contains('h1', 'User One')
|
||||
})
|
||||
|
||||
it('shows a user 404 page when looking for a user that does not exist', () => {
|
||||
cy.logIn()
|
||||
cy.visit('/profile/non-existant-user')
|
||||
cy.contains('h1', /User\s+Not\s+Found/)
|
||||
})
|
||||
})
|
||||
19
ui/tests/e2e/support/commands.js
Normal file
19
ui/tests/e2e/support/commands.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// Create custom Cypress commands and overwrite existing ones.
|
||||
// https://on.cypress.io/custom-commands
|
||||
|
||||
import { getStore } from './utils'
|
||||
|
||||
Cypress.Commands.add(
|
||||
'logIn',
|
||||
({ username = 'admin', password = 'password' } = {}) => {
|
||||
// Manually log the user in
|
||||
cy.location('pathname').then((pathname) => {
|
||||
if (pathname === 'blank') {
|
||||
cy.visit('/')
|
||||
}
|
||||
})
|
||||
getStore().then((store) =>
|
||||
store.dispatch('auth/logIn', { username, password })
|
||||
)
|
||||
}
|
||||
)
|
||||
17
ui/tests/e2e/support/setup.js
Normal file
17
ui/tests/e2e/support/setup.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
2
ui/tests/e2e/support/utils.js
Normal file
2
ui/tests/e2e/support/utils.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// Returns the Vuex store.
|
||||
export const getStore = () => cy.window().its('__app__.$store')
|
||||
13
ui/tests/mock-api/index.js
Normal file
13
ui/tests/mock-api/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const bodyParser = require('body-parser')
|
||||
|
||||
module.exports = (app) => {
|
||||
app.use(bodyParser.json())
|
||||
// Register all routes inside tests/mock-api/routes.
|
||||
fs.readdirSync(path.join(__dirname, 'routes')).forEach((routeFileName) => {
|
||||
if (/\.js$/.test(routeFileName)) {
|
||||
require(`./routes/${routeFileName}`)(app)
|
||||
}
|
||||
})
|
||||
}
|
||||
42
ui/tests/mock-api/resources/users.js
Normal file
42
ui/tests/mock-api/resources/users.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = {
|
||||
all: [
|
||||
{
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
password: 'password',
|
||||
name: 'Vue Master',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'user1',
|
||||
password: 'password',
|
||||
name: 'User One',
|
||||
},
|
||||
].map((user) => {
|
||||
return {
|
||||
...user,
|
||||
token: `valid-token-for-${user.username}`,
|
||||
}
|
||||
}),
|
||||
authenticate({ username, password }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const matchedUser = this.all.find(
|
||||
(user) => user.username === username && user.password === password
|
||||
)
|
||||
if (matchedUser) {
|
||||
resolve(this.json(matchedUser))
|
||||
} else {
|
||||
reject(new Error('Invalid user credentials.'))
|
||||
}
|
||||
})
|
||||
},
|
||||
findBy(propertyName, value) {
|
||||
const matchedUser = this.all.find((user) => user[propertyName] === value)
|
||||
return this.json(matchedUser)
|
||||
},
|
||||
json(user) {
|
||||
return user && _.omit(user, ['password'])
|
||||
},
|
||||
}
|
||||
33
ui/tests/mock-api/routes/auth.js
Normal file
33
ui/tests/mock-api/routes/auth.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const Users = require('../resources/users')
|
||||
|
||||
module.exports = (app) => {
|
||||
// Log in a user with a username and password
|
||||
app.post('/api/session', (request, response) => {
|
||||
Users.authenticate(request.body)
|
||||
.then((user) => {
|
||||
response.json(user)
|
||||
})
|
||||
.catch((error) => {
|
||||
response.status(401).json({ message: error.message })
|
||||
})
|
||||
})
|
||||
|
||||
// Get the user of a provided token, if valid
|
||||
app.get('/api/session', (request, response) => {
|
||||
const currentUser = Users.findBy('token', request.headers.authorization)
|
||||
|
||||
if (!currentUser) {
|
||||
return response.status(401).json({
|
||||
message:
|
||||
'The token is either invalid or has expired. Please log in again.',
|
||||
})
|
||||
}
|
||||
|
||||
response.json(currentUser)
|
||||
})
|
||||
|
||||
// A simple ping for checking online status
|
||||
app.get('/api/ping', (request, response) => {
|
||||
response.send('OK')
|
||||
})
|
||||
}
|
||||
24
ui/tests/mock-api/routes/users.js
Normal file
24
ui/tests/mock-api/routes/users.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const Users = require('../resources/users')
|
||||
|
||||
module.exports = (app) => {
|
||||
app.get('/api/users/:username', (request, response) => {
|
||||
const currentUser = Users.findBy('token', request.headers.authorization)
|
||||
|
||||
if (!currentUser) {
|
||||
return response.status(401).json({
|
||||
message:
|
||||
'The token is either invalid or has expired. Please log in again.',
|
||||
})
|
||||
}
|
||||
|
||||
const matchedUser = Users.findBy('username', request.params.username)
|
||||
|
||||
if (!matchedUser) {
|
||||
return response.status(400).json({
|
||||
message: 'No user with this name was found.',
|
||||
})
|
||||
}
|
||||
|
||||
response.json(matchedUser)
|
||||
})
|
||||
}
|
||||
0
ui/tests/unit/__mocks__/.keep
Normal file
0
ui/tests/unit/__mocks__/.keep
Normal file
14
ui/tests/unit/global-setup.js
Normal file
14
ui/tests/unit/global-setup.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const app = require('express')()
|
||||
|
||||
app.use((request, response, next) => {
|
||||
response.header('Access-Control-Allow-Origin', '*')
|
||||
next()
|
||||
})
|
||||
|
||||
require('../mock-api')(app)
|
||||
|
||||
module.exports = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
global.mockApiServer = app.listen(process.env.MOCK_API_PORT, resolve)
|
||||
})
|
||||
}
|
||||
7
ui/tests/unit/global-teardown.js
Normal file
7
ui/tests/unit/global-teardown.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// Shut down the mock API once all the tests are complete.
|
||||
|
||||
module.exports = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
global.mockApiServer.close(resolve)
|
||||
})
|
||||
}
|
||||
105
ui/tests/unit/matchers.js
Normal file
105
ui/tests/unit/matchers.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// See these docs for details on Jest's matcher utils:
|
||||
// https://facebook.github.io/jest/docs/en/expect.html#thisutils
|
||||
|
||||
const _ = require('lodash')
|
||||
const customMatchers = {}
|
||||
|
||||
customMatchers.toBeAComponent = function(options) {
|
||||
if (isAComponent()) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected ${this.utils.printReceived(
|
||||
options
|
||||
)} not to be a Vue component`,
|
||||
pass: true,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: () =>
|
||||
`expected ${this.utils.printReceived(
|
||||
options
|
||||
)} to be a valid Vue component, exported from a .vue file`,
|
||||
pass: false,
|
||||
}
|
||||
}
|
||||
|
||||
function isAComponent() {
|
||||
return _.isPlainObject(options) && typeof options.render === 'function'
|
||||
}
|
||||
}
|
||||
|
||||
customMatchers.toBeAViewComponent = function(options, mockInstance = {}) {
|
||||
if (usesALayout() && definesAPageTitleAndDescription()) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected ${this.utils.printReceived(
|
||||
options
|
||||
)} not to register a local Layout component nor define a page title and meta description`,
|
||||
pass: true,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: () =>
|
||||
`expected ${this.utils.printReceived(
|
||||
options
|
||||
)} to register a local Layout component and define a page title and meta description`,
|
||||
pass: false,
|
||||
}
|
||||
}
|
||||
|
||||
function usesALayout() {
|
||||
return options.components && options.components.Layout
|
||||
}
|
||||
|
||||
function definesAPageTitleAndDescription() {
|
||||
if (!options.page) return false
|
||||
const pageObject =
|
||||
typeof options.page === 'function'
|
||||
? options.page.apply(mockInstance)
|
||||
: options.page
|
||||
if (!Object.prototype.hasOwnProperty.call(pageObject, 'title')) return false
|
||||
if (!pageObject.meta) return false
|
||||
const hasMetaDescription = pageObject.meta.some(
|
||||
(metaObject) =>
|
||||
metaObject.name === 'description' &&
|
||||
Object.prototype.hasOwnProperty.call(metaObject, 'content')
|
||||
)
|
||||
if (!hasMetaDescription) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
customMatchers.toBeAViewComponentUsing = function(options, mockInstance) {
|
||||
return customMatchers.toBeAViewComponent.apply(this, [options, mockInstance])
|
||||
}
|
||||
|
||||
customMatchers.toBeAVuexModule = function(options) {
|
||||
if (isAVuexModule()) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected ${this.utils.printReceived(options)} not to be a Vuex module`,
|
||||
pass: true,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: () =>
|
||||
`expected ${this.utils.printReceived(
|
||||
options
|
||||
)} to be a valid Vuex module, include state, getters, mutations, and actions`,
|
||||
pass: false,
|
||||
}
|
||||
}
|
||||
|
||||
function isAVuexModule() {
|
||||
return (
|
||||
_.isPlainObject(options) &&
|
||||
_.isPlainObject(options.state) &&
|
||||
_.isPlainObject(options.getters) &&
|
||||
_.isPlainObject(options.mutations) &&
|
||||
_.isPlainObject(options.actions)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// https://facebook.github.io/jest/docs/en/expect.html#expectextendmatchers
|
||||
global.expect.extend(customMatchers)
|
||||
209
ui/tests/unit/setup.js
Normal file
209
ui/tests/unit/setup.js
Normal file
@@ -0,0 +1,209 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import axios from 'axios'
|
||||
|
||||
// ===
|
||||
// Utility functions
|
||||
// ===
|
||||
|
||||
// https://vue-test-utils.vuejs.org/
|
||||
const vueTestUtils = require('@vue/test-utils')
|
||||
// https://lodash.com/
|
||||
const _ = require('lodash')
|
||||
_.mixin({
|
||||
pascalCase: _.flow(_.camelCase, _.upperFirst),
|
||||
})
|
||||
|
||||
// ===
|
||||
// Configure Axios
|
||||
// ===
|
||||
|
||||
// Force Axios to use the XHR adapter so that it behaves
|
||||
// more like it would in a browser environment.
|
||||
axios.defaults.adapter = require('axios/lib/adapters/xhr')
|
||||
|
||||
// ===
|
||||
// Configure Vue
|
||||
// ===
|
||||
|
||||
// Don't warn about not using the production build of Vue, as
|
||||
// we care more about the quality of errors than performance
|
||||
// for tests.
|
||||
Vue.config.productionTip = false
|
||||
|
||||
// ===
|
||||
// Register global components
|
||||
// ===
|
||||
|
||||
const globalComponentFiles = fs
|
||||
.readdirSync(path.join(__dirname, '../../src/components'))
|
||||
.filter((fileName) => /^_base-.+\.vue$/.test(fileName))
|
||||
|
||||
for (const fileName of globalComponentFiles) {
|
||||
const componentName = _.pascalCase(fileName.match(/^_(base-.+)\.vue$/)[1])
|
||||
const componentConfig = require('../../src/components/' + fileName)
|
||||
Vue.component(componentName, componentConfig.default || componentConfig)
|
||||
}
|
||||
|
||||
// ===
|
||||
// Mock window properties not handled by jsdom
|
||||
// ===
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: (function() {
|
||||
let store = {}
|
||||
return {
|
||||
getItem: function(key) {
|
||||
return store[key] || null
|
||||
},
|
||||
setItem: function(key, value) {
|
||||
store[key] = value.toString()
|
||||
},
|
||||
clear: function() {
|
||||
store = {}
|
||||
},
|
||||
}
|
||||
})(),
|
||||
})
|
||||
|
||||
// ===
|
||||
// Console handlers
|
||||
// ===
|
||||
|
||||
// Make console.error throw, so that Jest tests fail
|
||||
const error = console.error
|
||||
console.error = function(message) {
|
||||
error.apply(console, arguments)
|
||||
// NOTE: You can whitelist some `console.error` messages here
|
||||
// by returning if the `message` value is acceptable.
|
||||
throw message instanceof Error ? message : new Error(message)
|
||||
}
|
||||
|
||||
// Make console.warn throw, so that Jest tests fail
|
||||
const warn = console.warn
|
||||
console.warn = function(message) {
|
||||
warn.apply(console, arguments)
|
||||
// NOTE: You can whitelist some `console.warn` messages here
|
||||
// by returning if the `message` value is acceptable.
|
||||
throw message instanceof Error ? message : new Error(message)
|
||||
}
|
||||
|
||||
// ===
|
||||
// Global helpers
|
||||
// ===
|
||||
|
||||
// https://vue-test-utils.vuejs.org/api/#mount
|
||||
global.mount = vueTestUtils.mount
|
||||
|
||||
// https://vue-test-utils.vuejs.org/api/#shallowmount
|
||||
global.shallowMount = vueTestUtils.shallowMount
|
||||
|
||||
// A special version of `shallowMount` for view components
|
||||
global.shallowMountView = (Component, options = {}) => {
|
||||
return global.shallowMount(Component, {
|
||||
...options,
|
||||
stubs: {
|
||||
Layout: {
|
||||
functional: true,
|
||||
render(h, { slots }) {
|
||||
return <div>{slots().default}</div>
|
||||
},
|
||||
},
|
||||
...(options.stubs || {}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// A helper for creating Vue component mocks
|
||||
global.createComponentMocks = ({ store, router, style, mocks, stubs }) => {
|
||||
// Use a local version of Vue, to avoid polluting the global
|
||||
// Vue and thereby affecting other tests.
|
||||
// https://vue-test-utils.vuejs.org/api/#createlocalvue
|
||||
const localVue = vueTestUtils.createLocalVue()
|
||||
const returnOptions = { localVue }
|
||||
|
||||
// https://vue-test-utils.vuejs.org/api/options.html#stubs
|
||||
returnOptions.stubs = stubs || {}
|
||||
// https://vue-test-utils.vuejs.org/api/options.html#mocks
|
||||
returnOptions.mocks = mocks || {}
|
||||
|
||||
// Converts a `store` option shaped like:
|
||||
//
|
||||
// store: {
|
||||
// someModuleName: {
|
||||
// state: { ... },
|
||||
// getters: { ... },
|
||||
// actions: { ... },
|
||||
// },
|
||||
// anotherModuleName: {
|
||||
// getters: { ... },
|
||||
// },
|
||||
// },
|
||||
//
|
||||
// to a store instance, with each module namespaced by
|
||||
// default, just like in our app.
|
||||
if (store) {
|
||||
localVue.use(Vuex)
|
||||
returnOptions.store = new Vuex.Store({
|
||||
modules: Object.keys(store)
|
||||
.map((moduleName) => {
|
||||
const storeModule = store[moduleName]
|
||||
return {
|
||||
[moduleName]: {
|
||||
state: storeModule.state || {},
|
||||
getters: storeModule.getters || {},
|
||||
actions: storeModule.actions || {},
|
||||
namespaced:
|
||||
typeof storeModule.namespaced === 'undefined'
|
||||
? true
|
||||
: storeModule.namespaced,
|
||||
},
|
||||
}
|
||||
})
|
||||
.reduce((moduleA, moduleB) => Object.assign({}, moduleA, moduleB), {}),
|
||||
})
|
||||
}
|
||||
|
||||
// If using `router: true`, we'll automatically stub out
|
||||
// components from Vue Router.
|
||||
if (router) {
|
||||
returnOptions.stubs['router-link'] = true
|
||||
returnOptions.stubs['router-view'] = true
|
||||
}
|
||||
|
||||
// If a `style` object is provided, mock some styles.
|
||||
if (style) {
|
||||
returnOptions.mocks.$style = style
|
||||
}
|
||||
|
||||
return returnOptions
|
||||
}
|
||||
|
||||
global.createModuleStore = (vuexModule, options = {}) => {
|
||||
vueTestUtils.createLocalVue().use(Vuex)
|
||||
const store = new Vuex.Store({
|
||||
..._.cloneDeep(vuexModule),
|
||||
modules: {
|
||||
auth: {
|
||||
namespaced: true,
|
||||
state: {
|
||||
currentUser: options.currentUser,
|
||||
},
|
||||
},
|
||||
},
|
||||
// Enable strict mode when testing Vuex modules so that
|
||||
// mutating state outside of a mutation results in a
|
||||
// failing test.
|
||||
// https://vuex.vuejs.org/guide/strict.html
|
||||
strict: true,
|
||||
})
|
||||
axios.defaults.headers.common.Authorization = options.currentUser
|
||||
? options.currentUser.token
|
||||
: ''
|
||||
if (vuexModule.actions.init) {
|
||||
store.dispatch('init')
|
||||
}
|
||||
return store
|
||||
}
|
||||
Reference in New Issue
Block a user