0
0
Fork 0
This commit is contained in:
Joan Sanchez 2022-10-20 13:48:29 +02:00
commit 592dc7da22
25 changed files with 766 additions and 21480 deletions

14
Jenkinsfile vendored
View File

@ -13,13 +13,13 @@ pipeline {
steps { steps {
script { script {
switch (env.BRANCH_NAME) { switch (env.BRANCH_NAME) {
// case 'master': case 'master':
// env.NODE_ENV = 'production' env.NODE_ENV = 'production'
// env.BACK_REPLICAS = 1 env.FRONT_REPLICAS = 2
// break break
case 'test': case 'test':
env.NODE_ENV = 'test' env.NODE_ENV = 'test'
env.BACK_REPLICAS = 1 env.FRONT_REPLICAS = 1
break break
} }
} }
@ -58,7 +58,7 @@ pipeline {
stage('Build') { stage('Build') {
when { anyOf { when { anyOf {
branch 'test' branch 'test'
// branch 'master' branch 'master'
}} }}
environment { environment {
CREDENTIALS = credentials('docker-registry') CREDENTIALS = credentials('docker-registry')
@ -73,7 +73,7 @@ pipeline {
stage('Deploy') { stage('Deploy') {
when { anyOf { when { anyOf {
branch 'test' branch 'test'
// branch 'master' branch 'master'
}} }}
environment { environment {
DOCKER_HOST = "${env.SWARM_HOST}" DOCKER_HOST = "${env.SWARM_HOST}"

View File

@ -8,7 +8,7 @@ services:
ports: ports:
- 4000 - 4000
deploy: deploy:
replicas: 2 replicas: ${FRONT_REPLICAS:?}
placement: placement:
constraints: constraints:
- node.role == worker - node.role == worker

21627
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
"@quasar/extras": "^1.14.0", "@quasar/extras": "^1.14.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"quasar": "^2.7.1", "quasar": "^2.7.3",
"vue": "^3.0.0", "vue": "^3.0.0",
"vue-i18n": "^9.0.0", "vue-i18n": "^9.0.0",
"vue-router": "^4.0.0" "vue-router": "^4.0.0"

View File

@ -1,6 +1,15 @@
<script setup> <script setup>
import axios from 'axios';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useSession } from 'src/composables/useSession';
const quasar = useQuasar(); const quasar = useQuasar();
const router = useRouter();
const session = useSession();
const { t } = useI18n();
const { isLoggedIn } = session;
quasar.iconMapFn = (iconName) => { quasar.iconMapFn = (iconName) => {
if (iconName.startsWith('vn:')) { if (iconName.startsWith('vn:')) {
@ -11,6 +20,51 @@ quasar.iconMapFn = (iconName) => {
}; };
} }
}; };
function responseError(error) {
let message = error.message;
let logOut = false;
switch (error.response?.status) {
case 401:
message = 'login.loginError';
if (isLoggedIn()) {
message = 'errors.statusUnauthorized';
logOut = true;
}
break;
case 403:
message = 'errors.statusUnauthorized';
break;
case 500:
message = 'errors.statusInternalServerError';
break;
case 502:
message = 'errors.statusBadGateway';
break;
case 504:
message = 'errors.statusGatewayTimeout';
break;
}
let translatedMessage = t(message);
if (!translatedMessage) translatedMessage = message;
quasar.notify({
message: translatedMessage,
type: 'negative',
});
if (logOut) {
session.destroy();
router.push({ path: '/login' });
}
return Promise.resolve(error);
}
axios.interceptors.response.use((response) => response, responseError);
</script> </script>
<template> <template>

87
src/__tests__/App.spec.js Normal file
View File

@ -0,0 +1,87 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper } from 'app/tests/jest/jestHelpers';
import App from '../App.vue';
import { useSession } from 'src/composables/useSession';
const mockPush = jest.fn();
const mockLoggedIn = jest.fn();
const mockDestroy = jest.fn();
const session = useSession();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' }
}),
}));
jest.mock('src/composables/useSession', () => ({
useSession: () => ({
isLoggedIn: mockLoggedIn,
destroy: mockDestroy
}),
}));
jest.mock('vue-i18n', () => ({
createI18n: () => { },
useI18n: () => ({
t: () => { }
}),
}));
describe('App', () => {
let vm;
beforeAll(() => {
const options = {
global: {
stubs: ['router-view']
}
};
vm = createWrapper(App, options).vm;
});
it('should return a login error message', async () => {
jest.spyOn(vm.quasar, 'notify');
session.isLoggedIn.mockReturnValue(false);
const response = {
response: {
status: 401
}
};
await vm.responseError(response);
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{
type: 'negative',
message: 'login.loginError'
}
));
});
it('should return an unauthorized error message', async () => {
jest.spyOn(vm.quasar, 'notify');
session.isLoggedIn.mockReturnValue(true);
const response = {
response: {
status: 401
}
};
await vm.responseError(response);
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{
type: 'negative',
message: 'errors.statusUnauthorized'
}
));
expect(session.destroy).toHaveBeenCalled();
});
});

View File

@ -1,17 +1,10 @@
import { boot } from 'quasar/wrappers';
import axios from 'axios'; import axios from 'axios';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: 'https://api.example.com' });
const { getToken } = useSession(); const { getToken } = useSession();
axios.defaults.baseURL = '/api/';
axios.interceptors.request.use( axios.interceptors.request.use(
function (context) { function (context) {
const token = getToken(); const token = getToken();
@ -26,17 +19,3 @@ axios.interceptors.request.use(
return Promise.reject(error); return Promise.reject(error);
} }
); );
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { api };

View File

@ -60,6 +60,11 @@ async function fetch() {
params: { filter }, params: { filter },
}); });
if (!data) {
isLoading.value = false;
return;
}
hasMoreData.value = data.length === rowsPerPage; hasMoreData.value = data.length === rowsPerPage;
for (const row of data) rows.value.push(row); for (const row of data) rows.value.push(row);

View File

@ -39,7 +39,7 @@ function updatePreferences() {
} }
async function saveDarkMode(value) { async function saveDarkMode(value) {
const query = `/api/UserConfigs/${user.value.id}`; const query = `/UserConfigs/${user.value.id}`;
await axios.patch(query, { await axios.patch(query, {
darkMode: value, darkMode: value,
}); });
@ -47,7 +47,7 @@ async function saveDarkMode(value) {
} }
async function saveLanguage(value) { async function saveLanguage(value) {
const query = `/api/Accounts/${user.value.id}`; const query = `/Accounts/${user.value.id}`;
await axios.patch(query, { await axios.patch(query, {
lang: value, lang: value,
}); });

View File

@ -57,7 +57,7 @@ export function useNavigation() {
}; };
async function fetchFavorites() { async function fetchFavorites() {
const response = await axios.get('api/starredModules/getStarredModules'); const response = await axios.get('StarredModules/getStarredModules');
const filteredModules = modules.value.filter((module) => { const filteredModules = modules.value.filter((module) => {
return response.data.find((element) => element.moduleFk == salixModules[module.name]); return response.data.find((element) => element.moduleFk == salixModules[module.name]);
@ -72,7 +72,7 @@ export function useNavigation() {
event.stopPropagation(); event.stopPropagation();
const params = { moduleName: salixModules[moduleName] }; const params = { moduleName: salixModules[moduleName] };
const query = 'api/starredModules/toggleStarredModule'; const query = 'StarredModules/toggleStarredModule';
await axios.post(query, params); await axios.post(query, params);
updateFavorites(moduleName); updateFavorites(moduleName);

View File

@ -5,7 +5,7 @@ export function useRole() {
const state = useState(); const state = useState();
async function fetch() { async function fetch() {
const { data } = await axios.get('/api/accounts/acl'); const { data } = await axios.get('Accounts/acl');
const roles = data.roles.map(userRoles => userRoles.role.name); const roles = data.roles.map(userRoles => userRoles.role.name);
const userData = { const userData = {

View File

@ -19,6 +19,8 @@ export default {
errors: { errors: {
statusUnauthorized: 'Access denied', statusUnauthorized: 'Access denied',
statusInternalServerError: 'An internal server error has ocurred', statusInternalServerError: 'An internal server error has ocurred',
statusBadGateway: 'It seems that the server has fall down',
statusGatewayTimeout: 'Could not contact the server',
}, },
login: { login: {
title: 'Login', title: 'Login',
@ -54,6 +56,16 @@ export default {
list: 'List', list: 'List',
createTicket: 'Create ticket', createTicket: 'Create ticket',
basicData: 'Basic Data' basicData: 'Basic Data'
},
boxing: {
expedition: 'Expedition',
item: 'Item',
created: 'Created',
worker: 'Worker',
selectTime: 'Select time:',
selectVideo: 'Select video:',
notFound: 'No videos available'
} }
}, },
components: { components: {

View File

@ -19,6 +19,8 @@ export default {
errors: { errors: {
statusUnauthorized: 'Acceso denegado', statusUnauthorized: 'Acceso denegado',
statusInternalServerError: 'Ha ocurrido un error interno del servidor', statusInternalServerError: 'Ha ocurrido un error interno del servidor',
statusBadGateway: 'Parece ser que el servidor ha caído',
statusGatewayTimeout: 'No se ha podido contactar con el servidor',
}, },
login: { login: {
title: 'Inicio de sesión', title: 'Inicio de sesión',
@ -54,6 +56,15 @@ export default {
list: 'Listado', list: 'Listado',
createTicket: 'Crear ticket', createTicket: 'Crear ticket',
basicData: 'Datos básicos' basicData: 'Datos básicos'
},
boxing: {
expedition: 'Expedición',
item: 'Artículo',
created: 'Creado',
worker: 'Trabajador',
selectTime: 'Seleccionar hora:',
selectVideo: 'Seleccionar vídeo:',
notFound: 'No hay vídeos disponibles'
} }
}, },
components: { components: {

View File

@ -17,7 +17,7 @@ const entityId = computed(function () {
const customer = ref({}); const customer = ref({});
async function fetch() { async function fetch() {
const { data } = await axios.get(`/api/Clients/${entityId.value}`); const { data } = await axios.get(`Clients/${entityId.value}`);
if (data) customer.value = data; if (data) customer.value = data;
} }

View File

@ -13,7 +13,7 @@ function navigate(id) {
<template> <template>
<q-page class="q-pa-md"> <q-page class="q-pa-md">
<smart-card url="/api/Clients" sort-by="id DESC" @on-navigate="navigate" auto-load> <smart-card url="/Clients" sort-by="id DESC" @on-navigate="navigate" auto-load>
<template #labels="{ row }"> <template #labels="{ row }">
<q-list> <q-list>
<q-item class="q-pa-none"> <q-item class="q-pa-none">

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import LeftMenu from 'src/components/LeftMenu.vue'; //import LeftMenu from 'src/components/LeftMenu.vue';
const state = useState(); const state = useState();
const miniState = ref(true); const miniState = ref(true);

View File

@ -17,40 +17,25 @@ const password = ref('');
const keepLogin = ref(true); const keepLogin = ref(true);
async function onSubmit() { async function onSubmit() {
try { const { data } = await axios.post('Accounts/login', {
const { data } = await axios.post('/api/accounts/login', { user: username.value,
user: username.value, password: password.value,
password: password.value, });
});
await session.login(data.token, keepLogin.value); if (!data) return;
quasar.notify({ await session.login(data.token, keepLogin.value);
message: t('login.loginSuccess'),
type: 'positive',
});
const currentRoute = router.currentRoute.value; quasar.notify({
if (currentRoute.query && currentRoute.query.redirect) { message: t('login.loginSuccess'),
router.push(currentRoute.query.redirect); type: 'positive',
} else { });
router.push({ name: 'Dashboard' });
} const currentRoute = router.currentRoute.value;
} catch (error) { if (currentRoute.query && currentRoute.query.redirect) {
if (axios.isAxiosError(error)) { router.push(currentRoute.query.redirect);
const errorCode = error.response && error.response.status; } else {
if (errorCode === 401) { router.push({ name: 'Dashboard' });
quasar.notify({
message: t('login.loginError'),
type: 'negative',
});
}
} else {
quasar.notify({
message: t('errors.statusInternalServerError'),
type: 'negative',
});
}
} }
} }
</script> </script>

View File

@ -17,6 +17,10 @@ describe('Login', () => {
vm = createWrapper(Login).vm; vm = createWrapper(Login).vm;
}); });
afterEach(() => {
jest.clearAllMocks();
});
it('should successfully set the token into session', async () => { it('should successfully set the token into session', async () => {
const expectedUser = { const expectedUser = {
id: 999, id: 999,
@ -29,7 +33,7 @@ describe('Login', () => {
} }
jest.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } }); jest.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } });
jest.spyOn(axios, 'get').mockResolvedValue({ data: { roles: [], user: expectedUser } }); jest.spyOn(axios, 'get').mockResolvedValue({ data: { roles: [], user: expectedUser } });
jest.spyOn(vm.quasar, 'notify') jest.spyOn(vm.quasar, 'notify');
expect(vm.session.getToken()).toEqual(''); expect(vm.session.getToken()).toEqual('');
@ -43,15 +47,11 @@ describe('Login', () => {
}); });
it('should not set the token into session if any error occurred', async () => { it('should not set the token into session if any error occurred', async () => {
jest.spyOn(axios, 'post').mockRejectedValue(new Error('error')); jest.spyOn(axios, 'post').mockReturnValue({ data: null });
jest.spyOn(vm.quasar, 'notify') jest.spyOn(vm.quasar, 'notify');
expect(vm.session.getToken()).toEqual('');
await vm.onSubmit(); await vm.onSubmit();
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining( expect(vm.quasar.notify).not.toHaveBeenCalled();
{ 'type': 'negative' }
));
}); });
}); });

View File

@ -0,0 +1,159 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { computed, ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import axios from 'axios';
import { date, useQuasar } from 'quasar';
const router = useRouter();
const { t } = useI18n();
const quasar = useQuasar();
onMounted(async () => {
await fetch();
});
const entityId = computed(function () {
return router.currentRoute.value.params.id;
});
const expeditions = ref({});
const lastExpedition = ref();
const slide = ref(null);
const videoList = ref([]);
const time = ref({
min: 0,
max: 24,
});
async function fetch() {
const filter = {
where: {
ticketFk: entityId.value,
},
};
const { data } = await axios.get(`/Expeditions/filter`, {
params: { filter },
});
if (data) expeditions.value = data;
}
async function getVideoList(expeditionId, timed) {
lastExpedition.value = expeditionId;
const params = {
id: expeditionId,
};
if (timed) {
Object.assign(params, { from: timed.min, to: timed.max });
}
const { data } = await axios.get(`/Boxings/getVideoList`, { params: params });
const list = [];
for (const video of data) {
const videName = video.split('.')[0].split('T')[1].replaceAll('-', ':');
list.push({
label: videName,
value: video,
url: `api/Boxings/getVideo?id=${expeditionId}&filename=${video}`,
});
}
videoList.value = list.reverse();
if (list[0]) {
slide.value = list[0].value;
time.value = {
min: parseInt(list[0].label.split(':')[0]),
max: parseInt(list[list.length - 1].label.split(':')[0]),
};
}
if (!data.length) {
return quasar.notify({
message: t('ticket.boxing.notFound'),
type: 'negative',
});
}
}
</script>
<template>
<q-layout view="hhh lpr ffr" class="fit">
<q-drawer show-if-above side="right" bordered>
<q-scroll-area class="fit">
<q-list bordered separator style="max-width: 318px">
<q-item v-if="lastExpedition && videoList.length">
<q-item-section>
<q-item-label class="text-h6">
{{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ time.max }})
</q-item-label>
<q-range
v-model="time"
@change="getVideoList(lastExpedition, time)"
:min="0"
:max="24"
:step="1"
:left-label-value="time.min + ':00'"
:right-label-value="time.max + ':00'"
label
markers
snap
color="orange"
/>
</q-item-section>
</q-item>
<q-item v-if="lastExpedition && videoList.length">
<q-item-section>
<q-select
color="orange"
v-model="slide"
:options="videoList"
:label="t('ticket.boxing.selectVideo')"
emit-value
map-options
>
<template #prepend>
<q-icon name="schedule" />
</template>
</q-select>
</q-item-section>
</q-item>
<q-item
v-for="expedition in expeditions"
:key="expedition.id"
@click="getVideoList(expedition.id)"
clickable
v-ripple
>
<q-item-section>
<q-item-label class="text-h6">#{{ expedition.id }}</q-item-label>
</q-item-section>
<q-item-section>
<q-item-label caption>{{ t('ticket.boxing.created') }}</q-item-label>
<q-item-label>
{{ date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') }}
</q-item-label>
<q-item-label caption>{{ t('ticket.boxing.item') }}</q-item-label>
<q-item-label>{{ expedition.packagingItemFk }}</q-item-label>
<q-item-label caption>{{ t('ticket.boxing.worker') }}</q-item-label>
<q-item-label>{{ expedition.userName }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-scroll-area>
</q-drawer>
<q-page-container>
<q-page>
<q-card>
<q-carousel animated v-model="slide" height="max-content">
<q-carousel-slide v-for="video in videoList" :key="video.value" :name="video.value">
<q-video :src="video.url" :ratio="16 / 9" />
</q-carousel-slide>
</q-carousel>
</q-card>
</q-page>
</q-page-container>
</q-layout>
</template>

View File

@ -1,23 +1,23 @@
<script setup> <script setup>
import { computed } from 'vue'; //import { computed } from 'vue';
import { useState } from 'src/composables/useState'; //import { useState } from 'src/composables/useState';
import { useRouter } from 'vue-router'; //import { useRouter } from 'vue-router';
const state = useState(); //const state = useState();
const router = useRouter(); //const router = useRouter();
const entityId = computed(function () { /*const entityId = computed(function () {
return router.currentRoute.value.params.id; return router.currentRoute.value.params.id;
}); });*/
</script> </script>
<template> <template>
<q-drawer v-model="state.drawer.value" show-if-above :width="200" :breakpoint="500"> <!--<q-drawer v-model="state.drawer.value" show-if-above :width="200" :breakpoint="500">
<q-scroll-area class="fit text-grey-8"> <q-scroll-area class="fit text-grey-8">
<router-link :to="{ path: '/customer/list' }"> <router-link :to="{ path: '/customer/list' }">
<q-icon name="arrow_back" size="md" color="primary" /> <q-icon name="arrow_back" size="md" color="primary" />
</router-link> </router-link>
<div>Customer ID: {{ entityId }}</div> <div>Customer ID: {{ entityId }}</div>
</q-scroll-area> </q-scroll-area>
</q-drawer> </q-drawer>-->
<q-page-container> <q-page-container>
<router-view></router-view> <router-view></router-view>
</q-page-container> </q-page-container>

View File

@ -0,0 +1,69 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import TicketBoxing from '../TicketBoxing.vue';
const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: {
value: {
params: {
id: 1
}
}
}
}),
}));
describe('TicketBoxing', () => {
let vm;
beforeAll(() => {
vm = createWrapper(TicketBoxing).vm;
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getVideoList()', () => {
it('should when response videoList use to list', async () => {
const expeditionId = 1;
const timed = {
min: 1,
max: 2
}
const videoList = [
"2022-01-01T01-01-00.mp4",
"2022-02-02T02-02-00.mp4",
"2022-03-03T03-03-00.mp4",
]
jest.spyOn(axios, 'get').mockResolvedValue({ data: videoList });
jest.spyOn(vm.quasar, 'notify');
await vm.getVideoList(expeditionId, timed);
expect(vm.videoList.length).toEqual(videoList.length);
expect(vm.slide).toEqual(videoList.reverse()[0]);
});
it('should if not have video show notify', async () => {
const expeditionId = 1;
const timed = {
min: 1,
max: 2
}
jest.spyOn(axios, 'get').mockResolvedValue({ data: [] });
jest.spyOn(vm.quasar, 'notify')
await vm.getVideoList(expeditionId, timed);
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{ 'type': 'negative' }
));
});
});
});

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import LeftMenu from 'src/components/LeftMenu.vue'; //import LeftMenu from 'src/components/LeftMenu.vue';
const state = useState(); const state = useState();
const miniState = ref(true); const miniState = ref(true);
@ -18,9 +18,9 @@ const miniState = ref(true);
:width="256" :width="256"
:breakpoint="500" :breakpoint="500"
> >
<q-scroll-area class="fit text-grey-8"> <!--<q-scroll-area class="fit text-grey-8">
<LeftMenu /> <LeftMenu />
</q-scroll-area> </q-scroll-area>-->
</q-drawer> </q-drawer>
<q-page-container> <q-page-container>
<router-view></router-view> <router-view></router-view>

View File

@ -1,6 +1,6 @@
import { route } from 'quasar/wrappers'; import { route } from 'quasar/wrappers';
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router'; import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router';
import { Notify } from 'quasar'; // import { Notify } from 'quasar';
import routes from './routes'; import routes from './routes';
import { i18n } from 'src/boot/i18n'; import { i18n } from 'src/boot/i18n';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
@ -46,20 +46,20 @@ export default route(function (/* { store, ssrContext } */) {
} }
if (isLoggedIn()) { if (isLoggedIn()) {
try { // try {
const stateRoles = state.getRoles().value; const stateRoles = state.getRoles().value;
if (stateRoles.length === 0) { if (stateRoles.length === 0) {
await role.fetch(); await role.fetch();
}
} catch (error) {
Notify.create({
message: t('errors.statusUnauthorized'),
type: 'negative',
});
session.destroy();
return next({ path: '/login' });
} }
// } catch (error) {
// Notify.create({
// message: t('errors.statusUnauthorized'),
// type: 'negative',
// });
// session.destroy();
// return next({ path: '/login' });
// }
const matches = to.matched; const matches = to.matched;
const hasRequiredRoles = matches.every(route => { const hasRequiredRoles = matches.every(route => {

View File

@ -51,6 +51,14 @@ export default {
title: 'basicData' title: 'basicData'
}, },
component: () => import('src/pages/Ticket/Card/TicketBasicData.vue'), component: () => import('src/pages/Ticket/Card/TicketBasicData.vue'),
},
{
path: 'boxing',
name: 'TicketBoxing',
meta: {
title: 'boxing'
},
component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'),
} }
] ]
}, },

View File

@ -0,0 +1,38 @@
describe('TicketBoxing', () => {
beforeEach(() => {
const ticketId = 1;
cy.viewport(1280, 720)
cy.login('developer')
cy.visit(`/#/ticket/${ticketId}/boxing`);
});
it('should load expeditions of ticket', () => {
cy.get('div[class="q-item__label text-h6"]').eq(0).should('have.text', '#1');
cy.get('div[class="q-item__label text-h6"]').eq(1).should('have.text', '#2');
cy.get('div[class="q-item__label text-h6"]').eq(2).should('have.text', '#3');
});
it('should show error if not have video list', () => {
cy.get('div[class="q-item__label text-h6"]').eq(0).click();
cy.get('.q-notification__message').should('have.text', 'No videos available');
});
it('should show select time and video if have video list', () => {
cy.intercept(
{
method: 'GET',
url: '/api/Boxings/*',
},
[
"2022-01-01T01-01-00.mp4",
"2022-02-02T02-02-00.mp4",
"2022-03-03T03-03-00.mp4",
]
).as('getVideoList');
cy.get('.q-list > :nth-child(3)').click();
cy.get('.q-list > :nth-child(1)').should('be.visible');
cy.get('.q-list > :nth-child(2)').should('be.visible');
});
});