/* Cypress test */
/// <reference types="Cypress" />
const apiURL = window .Cypress.config().apiURL;
const resetDbURL = window .Cypress.config().resetDbURL;
const testAdminUser = window .Cypress.config().testAdminUser;
const tempAdminUser = {
login : 'jan.nowak' ,
password : 'ppp'
};
describe('Login as admin, test module USERS and logout.' , () => {
localStorage.clear();
afterEach(function ( ) {
if (this .currentTest.state === 'failed' ) {
Cypress.runner.stop();
}
});
it('resets database' , () => {
cy.visit(resetDbURL, {
timeout : 30000
});
cy.get('.test-data-generator' )
.find('.new-test-user-login' )
.should('not.be.empty' );
});
it('login an admin user' , () => {
cy.visit('/#/login' );
cy.url().should('include' , '/login' );
cy.server();
cy.route('POST' , apiURL + 'login_check' ).as('postLogin' );
cy.route('GET' , apiURL + 'orders*' ).as('getOrdersAfterLogin' );
cy.get('.LoginView form' )
.find('[type="text"]' )
.type(testAdminUser.login);
cy.get('.LoginView form' )
.find('[type="password"]' )
.type(`${testAdminUser.password} {enter}` );
cy.wait('@postLogin' )
.its('status' )
.should('eq' , 200 );
cy.url()
.should('include' , '/orders' )
.should(() => {
expect(localStorage.getItem('token' )).to.exist;
expect(localStorage.getItem('user' )).to.exist;
});
cy.wait('@getOrdersAfterLogin' )
.its('status' )
.should('eq' , 200 );
});
it('select users module' , () => {
cy.get('.navbar' )
.find('.navbar-item--users' )
.click();
cy.get('.RouterView' ).should('have.class' , 'UsersView' );
});
it('add an user' , () => {
cy.server();
cy.route('POST' , apiURL + 'users' ).as('addUser' );
cy.route('GET' , apiURL + 'users*' ).as('getUsersAfterAdd' );
cy.get('.navbar' )
.find('.button--new-user' )
.click();
cy.get('.NewUserModal' )
.find('.field--username input' )
.type(tempAdminUser.login);
cy.get('.NewUserModal' )
.find('.field--plainPassword input' )
.type(tempAdminUser.password);
cy.get('.NewUserModal' )
.find('.field--isAdmin label' )
.click();
cy.get('.NewUserModal' )
.find('.button--save' )
.click();
cy.wait('@addUser' )
.its('status' )
.should('eq' , 201 );
cy.get('.ViewUserModal' ).should('be.visible' );
cy.get('.ViewUserModal' )
.find('.button--cancel' )
.click();
cy.get('.ViewUserModal' ).should('be.not.visible' );
cy.wait('@getUsersAfterAdd' )
.its('status' )
.should('eq' , 200 );
cy.get('.UsersTable tbody tr:first-child' )
.find('[data-label="Użytkownik"] a' )
.should('contain' , tempAdminUser.login);
cy.get('.UsersTable tbody tr:first-child' )
.find('[data-label="Liczba zamówień"]' )
.should('contain' , 0 );
cy.get('.UsersTable tbody tr:first-child' )
.find('[data-label="Konto aktywne"]' )
.should('contain' , 'tak' );
cy.get('.UsersTable tbody tr:first-child' )
.find('[data-label="Admin"]' )
.should('contain' , 'tak' );
});
it('edit an user' , () => {
cy.server();
cy.route('PUT' , apiURL + 'users/*' ).as('editUser' );
cy.route('GET' , apiURL + 'users*' ).as('getUsersAfterEdit' );
cy.get('.UsersTable tbody tr:first-child' )
.find('[data-label="Użytkownik"] a' )
.click();
cy.get('.ViewUserModal' )
.find('.button--edit' )
.click();
cy.get('.ViewUserModal' )
.find('.field--username input' )
.clear()
.type(tempAdminUser.login);
cy.get('.ViewUserModal' )
.find('.button--save' )
.click();
cy.wait('@editUser' )
.its('status' )
.should('eq' , 200 );
cy.wait('@getUsersAfterEdit' )
.its('status' )
.should('eq' , 200 );
cy.get('.ViewUserModal' )
.find('.button--cancel' )
.click();
cy.get('.UsersTable tbody tr:first-child' )
.find('[data-label="Użytkownik"] a' )
.should('contain' , tempAdminUser.login);
});
it('delete an user' , () => {
cy.server();
cy.route('DELETE' , apiURL + 'users/*' ).as('destroyUser' );
cy.route('GET' , apiURL + 'users*' ).as('getUsersAfterDestroy' );
cy.get('.UsersTable tbody tr:first-child' )
.find('[data-label="Użytkownik"] a' )
.click();
cy.get('.ViewUserModal' )
.find('.button.button--delete' )
.click();
cy.get('.dialog.is-active' )
.find('.button.is-danger' )
.click();
cy.wait('@destroyUser' )
.its('status' )
.should('eq' , 204 );
cy.wait('@getUsersAfterDestroy' )
.its('status' )
.should('eq' , 200 );
cy.get('.UsersTable tbody tr:first-child' )
.find('[data-label="Użytkownik"] a' )
.should('not.contain' , tempAdminUser.login);
cy.url().should('include' , '/users' );
});
it('logout' , () => {
cy.get('.navbar' )
.find('.button--logout' )
.click()
.should(() => {
expect(localStorage.getItem('token' )).to.not.exist;
expect(localStorage.getItem('user' )).to.not.exist;
});
cy.url().should('include' , '/login' );
});
});
/cypress/integration/user_actions.spec.js import bus from '@/EventBus'
import { mapState } from 'vuex'
import OrdersTable from '@/components/tables/OrdersTable'
import RoutesTable from '@/components/tables/RoutesTable'
import Map from '@/components/Map'
import Footer from '@/components/Footer'
export default {
name : 'OrdersView' ,
components : {
OrdersTable,
RoutesTable,
Map ,
Footer
},
data () {
return {
totalNewOrders : null ,
searchText : ''
}
},
computed : {
...mapState('orders' , [
'currentTab' ,
'viewMode'
])
},
methods : {
addTestOrder () {
const testOrder = {
clientName : 'Jan Kowalski' ,
postalCode : '12-123' ,
post : 'Gdańsk' ,
phone : '123-561-345' ,
broker : 'pośrednik ABC' ,
isInvoice : false ,
creator : 3
}
this .isLoading = true
this .$store.dispatch('orders/createOrder' , testOrder)
.then(order => {
this .isLoading = false
this .$snackbar.open(`Zamówienie #${order.id} zostało dodane.` )
bus.$emit('order-created' )
})
.catch((error ) => {
console .log('error' , error)
if (error.response.status === 400 ) {
alert(error.response.data.violations[0 ].message + ': ' + error.response.data.violations[0 ].propertyPath)
// console.log('msgs', error.response.data.violations)
}
})
},
selectTab (tabName) {
this .$store.dispatch('orders/selectTab' , tabName)
},
selectViewMode (mode) {
this .$store.dispatch('orders/selectViewMode' , mode)
},
search () {
this .$store.dispatch('orders/search' , this .searchText)
},
checkIsSearchReseted () {
if (this .searchText === '' ) this .$store.dispatch('orders/search' , this .searchText)
},
ordersTableRowSelect (rows) {
bus.$emit('orders-table-row-select' , rows)
},
routesTableRowSelect (rows) {
bus.$emit('routes-table-row-select' , rows)
}
},
mounted () {
this .selectTab('new' )
},
created () {
bus.$on('new-orders-loaded' , totalNewOrders => { this .totalNewOrders = totalNewOrders })
},
destroyed () {
bus.$off('tab-change' )
}
}
src/components/views/OrdersView.vue import moment from 'moment'
import bus from '@/EventBus'
import UserService from '@/services/UserService'
export default {
data () {
return {
users : [], // current loaded
checkedRows: [],
perPage : localStorage.getItem('users-table-per-page' ) || 10 ,
perPageOptions : [ 5 , 10 , 25 , 50 , 100 ],
pageNr : 1 ,
sortParams : {
field : 'id' ,
order : 'desc'
},
total : 0 ,
isLoading : false
}
},
methods : {
changePage (pageNr) {
this .pageNr = pageNr
this .loadUsers()
},
changePerPage (num) {
this .perPage = num
localStorage.setItem('users-table-per-page' , this .perPage)
this .loadUsers()
},
changeSortParams (field, order) {
this .sortParams = { field, order }
this .loadUsers()
},
loadUsers () {
this .isLoading = true
UserService.getAll(this .sortParams, this .perPage, this .pageNr)
.then(res => this .loadUsersSuccess(res.data))
.catch(err => this .loadUsersFailure(err))
},
loadUsersSuccess (data) {
this .users = []
this .total = data['hydra:totalItems' ]
if (this .total / this .perPage > 1000 ) {
this .total = this .perPage * 1000
}
this .isLoading = false
if (this .total === 0 ) return false
data['hydra:member' ].forEach(item => {
this .users.push(item)
})
this .checkedRows = []
bus.$emit('users-loaded' )
},
loadUsersFailure (err) {
this .users = []
this .isLoading = false
throw { error : err }
}
},
filters : {
date (value, format) {
return moment(value).format(format)
},
humanDate (value) {
return moment(value).fromNow()
}
},
created () {
moment.locale('pl' )
this .events = [
'user-created' ,
'user-updated' ,
'user-deleted'
]
bus.$on(this .events, () => {
this .loadUsers()
})
bus.$on(['users-loaded' ], () => {
this .checkedRows = []
})
},
mounted () {
this .loadUsers()
},
destroyed () {
bus.$off(this .events)
}
}
src/components/tables/UsersTable.vue import axios from '@/axios'
import { isBoolean } from 'util'
const find = id => {
const path = `routes?id=${id} `
return axios.get(path)
}
const getAll = ({ field, order }, perPage = 10 , pageNr = 1 , filter, searchText ) => {
let filterSegment = ''
let searchSegment = ''
if (filter) {
if (isBoolean(filter.value)) filterSegment += `&${filter.by} [exists]=${filter.value} `
else if (Array .isArray(filter.value)) filter.value.forEach(value => {
filterSegment += `&${filter.by} []=${value} `
})
else filterSegment += `&${filter.by} =${filter.value} `
}
if (searchText) searchSegment = `&search=${searchText} `
const path = `routes?order[${field} ]=${order} &itemsPerPage=${perPage} &page=${pageNr} ${filterSegment} ${searchSegment} `
return axios.get(path)
}
const update = route => {
const path = `routes/${route.id} `
// if (route.route) route.route = `/api/routes/${route.route.id}`
if (route.orders) delete (route.orders)
return axios.put(path, route)
}
const create = route => {
const path = `routes`
return axios.post(path, route)
}
const destroy = route => {
const path = `routes/${route.id} `
return axios.delete(path)
}
export default {
find,
getAll,
update,
create,
destroy
}
src/services/RouteService.js import bus from '@/EventBus'
import OrderService from '@/services/OrderService'
import RouteService from '@/services/RouteService'
import LocationService from '@/services/LocationService'
// initial state
const state = {
currentOrder : null ,
searchText : '' ,
currentTab : null ,
viewMode : 'orders'
}
// actions
const actions = {
selectTab ({ commit }, tabName) {
commit('SET_CURRENT_TAB' , tabName)
bus.$emit('tab-change' )
},
selectViewMode ({ commit }, viewMode) {
commit('SET_CURRENT_VIEW_MODE' , viewMode)
bus.$emit('change-view-mode' )
},
search ({ commit }, text) {
commit('SET_SEARCH_TEXT' , text)
bus.$emit('search' )
},
setCurrentOrder ({ commit }, order) {
if (order && !order.logs) {
return OrderService.find(order.id).then(res => {
commit('SET_CURRENT_ORDER' , res.data['hydra:member' ][0 ])
})
} else commit('SET_CURRENT_ORDER' , order)
commit('SET_CURRENT_ORDER' , order)
},
updateOrder ({ commit, state }, order) {
commit('SET_CURRENT_ORDER' , order)
return OrderService.update(state.currentOrder)
.then(res => {
commit('SET_CURRENT_ORDER' , res.data)
return state.currentOrder
})
},
archiveOrder ({ commit, state }, order) {
commit('SET_CURRENT_ORDER' , order)
commit('SET_ORDER_STATUS' , 'archived' )
return OrderService.update(state.currentOrder)
.then(res => {
commit('SET_CURRENT_ORDER' , res.data)
return state.currentOrder
})
},
createOrder ({ commit, state }, { order, user }) {
commit('SET_CURRENT_ORDER' , order)
commit('SET_NEW_ORDER_PARAMS' , user)
return OrderService.create(state.currentOrder)
.then(res => {
commit('SET_CURRENT_ORDER' , res.data)
return state.currentOrder
})
},
addToExisitingRoute ({ commit, state }, { order, route }) {
commit('SET_CURRENT_ORDER' , order)
commit('ADD_ROUTE_TO_ORDER' , route)
commit('SET_ORDER_STATUS' , route.status)
return OrderService.update(state.currentOrder)
.then(res => {
commit('SET_CURRENT_ORDER' , res.data)
return state.currentOrder
})
},
removeFromRoute ({ commit, state }, order) {
commit('SET_CURRENT_ORDER' , order)
commit('REMOVE_ROUTE_FROM_ORDER' )
commit('SET_ORDER_STATUS' , 'new' )
return OrderService.update(state.currentOrder)
.then(res => {
commit('SET_CURRENT_ORDER' , res.data)
return state.currentOrder
})
},
addToNewRoute ({ commit, state }, { order, routeName }) {
commit('SET_CURRENT_ORDER' , order)
return RouteService.create({ name : routeName, status : 'planned' })
.then(res => {
commit('ADD_ROUTE_TO_ORDER' , res.data)
commit('SET_ORDER_STATUS' , res.data.status)
return OrderService.update(state.currentOrder)
.then(res => {
commit('SET_CURRENT_ORDER' , res.data)
return state.currentOrder
})
})
},
deleteOrder ({ commit, state }, order) {
commit('SET_CURRENT_ORDER' , order)
return OrderService.destroy(state.currentOrder)
.then(() => {
commit('SET_CURRENT_ORDER' , null )
return order
})
},
findLocation () {
return LocationService.find(state.currentOrder).then(locations => locations.data)
},
updateOrderLocation ({ commit }, location) {
commit('SET_ORDER_LOCATION' , location)
return OrderService.update(state.currentOrder)
.then(res => {
commit('SET_CURRENT_ORDER' , res.data)
return state.currentOrder
})
},
clearLocation ({ commit }) {
commit('CLEAR_ORDER_LOCATION' )
return OrderService.update(state.currentOrder)
.then(res => {
commit('SET_CURRENT_ORDER' , res.data)
return state.currentOrder
})
}
}
// mutations
const mutations = {
SET_CURRENT_TAB (state, tabName) {
state.currentTab = tabName
},
SET_CURRENT_VIEW_MODE (state, mode) {
state.viewMode = mode
},
SET_SEARCH_TEXT (state, text) {
state.searchText = text
},
SET_CURRENT_ORDER (state, order) {
state.currentOrder = Object .assign({}, order) // remove reference
},
SET_NEW_ORDER_PARAMS (state, user) {
state.currentOrder.isInvoice = false
state.currentOrder.status = 'new'
state.currentOrder.creator = user
},
ADD_ROUTE_TO_ORDER (state, route) {
state.currentOrder.route = route
},
REMOVE_ROUTE_FROM_ORDER (state) {
state.currentOrder.route = null
},
SET_ORDER_STATUS (state, status) {
state.currentOrder.status = status
},
SET_ORDER_LOCATION (state, location) {
state.currentOrder.lat = location.lat
state.currentOrder.lon = location.lon
},
CLEAR_ORDER_LOCATION (state) {
state.currentOrder.lat = null
state.currentOrder.lon = null
}
}
export default {
namespaced : true ,
state,
actions,
mutations
}
src/store/modules/orders.js <template>
<form @submit.prevent ="save" >
<b-loading :active="isLoading"></b-loading>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Dodawanie zamówienia</p>
<button type="button" class="delete" aria-label="close" @click="$parent.$emit('close')"></button>
</header>
<section class="modal-card-body">
<div class="group">
<div class="field field--client-name is-horizontal">
<div class="field-label">
Nazwa klienta:
</div>
<editable
:isEditMode="true"
:value="order.clientName"
required
placeholder="Imię Nazwisko / Nazwa firmy"
type="text"
@input="order.clientName = $event"/>
</div>
<div class="field field--client-phone is-horizontal">
<div class="field-label">
Telefon:
</div>
<editable
:isEditMode="true"
:value="order.phone"
required
placeholder="600-123-123"
type="text"
@input="order.phone = $event"/>
</div>
<div class="field field--client-postcode is-horizontal">
<div class="field-label">
Kod pocztowy:
</div>
<editable
:isEditMode="true"
:value="order.postalCode"
required
placeholder="00-000"
type="text"
@input="order.postalCode = $event"/>
</div>
<div class="field field--client-post is-horizontal">
<div class="field-label">
Poczta:
</div>
<editable
:isEditMode="true"
:value="order.post"
required
placeholder="Poczta"
type="text"
@input="order.post = $event"/>
</div>
<hr>
<div class="field field--client-broker is-horizontal">
<div class="field-label">
Pośrednik:
</div>
<editable
:isEditMode="true"
:value="order.broker"
required
placeholder="Nazwa pośrednika"
type="text"
@input="order.broker = $event"/>
</div>
</div>
</section>
<footer class="modal-card-foot">
<button type="submit" class="button is-success button--add"><i class="mdi">save</i> Dodaj</button>
<button class="button is-white button--cancel" type="button" @click="$parent.$emit('close')">Anuluj</button>
</footer>
</div>
</form>
</template>
<script>
import bus from '@/EventBus'
import { mapGetters } from 'vuex'
import EditableField from '@/components/EditableField'
export default {
name: 'NewOrderModal',
components: {
editable: EditableField
},
data () {
return {
order: {},
isLoading: false
}
},
computed: {
...mapGetters('auth', [
'user'
])
},
methods: {
save () {
this.isLoading = true
this.$store.dispatch('orders/createOrder', { order: this.order, user: this.user.id })
.then(order => {
this.isLoading = false
this.$store.dispatch('modals/hideNewOrderModal')
this.$store.dispatch('modals/showViewOrderModal', order)
this.$snackbar.open(`Zamówienie #${order.id} zostało dodane.`)
bus.$emit('order-created')
})
}
}
}
</script>
src/components/modals/NewOrderModal.vue