Merge pull request '232201_dev_to_test' (!59) from dev into test
gitea/salix-front/pipeline/head This commit looks good Details

Reviewed-on: #59
Reviewed-by: Alexandre Riera <alexandre@verdnatura.es>
This commit is contained in:
Alex Moreno 2023-06-01 07:18:40 +00:00
commit 8f5a3b3876
23 changed files with 3329 additions and 6438 deletions

8865
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,40 +15,42 @@
"test:unit:ci": "vitest run" "test:unit:ci": "vitest run"
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "^1.15.11", "@quasar/cli": "^2.2.1",
"axios": "^1.2.1", "@quasar/extras": "^1.16.4",
"pinia": "^2.0.28", "axios": "^1.4.0",
"quasar": "^2.11.7", "chromium": "^3.0.3",
"validator": "^13.7.0", "pinia": "^2.1.3",
"vue": "^3.2.45", "quasar": "^2.12.0",
"validator": "^13.9.0",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
"vue-router": "^4.1.6", "vue-router": "^4.2.1",
"vue-router-mock": "^0.1.9" "vue-router-mock": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^0.8.1", "@intlify/unplugin-vue-i18n": "^0.8.1",
"@pinia/testing": "^0.0.14", "@pinia/testing": "^0.1.2",
"@quasar/app-vite": "^1.2.1", "@quasar/app-vite": "^1.4.3",
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.2.1", "@quasar/quasar-app-extension-testing-unit-vitest": "^0.3.0",
"@vue/test-utils": "^2.0.0", "@vue/test-utils": "^2.3.2",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.14",
"cypress": "^12.2.0", "cypress": "^12.13.0",
"eslint": "^8.30.0", "eslint": "^8.41.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-cypress": "^2.12.1", "eslint-plugin-cypress": "^2.13.3",
"eslint-plugin-vue": "^9.8.0", "eslint-plugin-vue": "^9.14.1",
"postcss": "^8.4.20", "postcss": "^8.4.23",
"prettier": "^2.8.1", "prettier": "^2.8.8",
"vitest": "^0.26.3" "vitest": "^0.31.1"
}, },
"engines": { "engines": {
"node": "^18 || ^16 || ^14.19", "node": "^20 || ^18 || ^16",
"npm": ">= 6.13.4", "npm": ">= 8.1.2",
"yarn": ">= 1.21.1" "yarn": ">= 1.21.1"
}, },
"overrides": { "overrides": {
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"vite": "^4.0.3", "vite": "^4.3.5",
"vitest": "^0.26.3" "vitest": "^0.31.1"
} }
} }

View File

@ -39,16 +39,20 @@ onMounted(async () => {
}); });
async function fetch() { async function fetch() {
const filter = Object.assign({}, $props.filter); try {
if ($props.where) filter.where = $props.where; const filter = Object.assign({}, $props.filter); // eslint-disable-line vue/no-dupe-keys
if ($props.sortBy) filter.order = $props.sortBy; if ($props.where) filter.where = $props.where;
if ($props.limit) filter.limit = $props.limit; if ($props.sortBy) filter.order = $props.sortBy;
if ($props.limit) filter.limit = $props.limit;
const { data } = await axios.get($props.url, { const { data } = await axios.get($props.url, {
params: { filter }, params: { filter },
}); });
emit('onFetch', data); emit('onFetch', data);
} catch (e) {
//
}
} }
const render = () => { const render = () => {

View File

@ -77,7 +77,7 @@ function reset() {
state.set($props.model, originalData.value); state.set($props.model, originalData.value);
hasChanges.value = false; hasChanges.value = false;
} }
// eslint-disable-next-line vue/no-dupe-keys
function filter(value, update, filterOptions) { function filter(value, update, filterOptions) {
update( update(
() => { () => {

View File

@ -11,7 +11,7 @@ const props = defineProps({
}, },
}); });
const item = computed(() => props.item); const item = computed(() => props.item); // eslint-disable-line vue/no-dupe-keys
</script> </script>
<template> <template>
<QItem active-class="text-primary" :to="{ name: item.name }" clickable v-ripple> <QItem active-class="text-primary" :to="{ name: item.name }" clickable v-ripple>

View File

@ -18,7 +18,7 @@ const props = defineProps({
}, },
}); });
const item = computed(() => props.item); const item = computed(() => props.item); // eslint-disable-line vue/no-dupe-keys
const isOpened = computed(() => { const isOpened = computed(() => {
const { matched } = route; const { matched } = route;
const { name } = item.value; const { name } = item.value;

View File

@ -0,0 +1,208 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useSession } from 'src/composables/useSession';
import { useStateStore } from 'stores/useStateStore';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnLogFilter from 'src/components/common/VnLogFilter.vue';
import { toDate } from 'src/filters';
const stateStore = useStateStore();
const route = useRoute();
const session = useSession();
const token = session.getToken();
const { t } = useI18n();
const props = defineProps({
model: {
type: String,
default: null,
},
});
const columns = [
{
name: 'property',
label: 'Property',
field: (row) => t(`properties.${row.property}`),
align: 'left',
},
{
name: 'before',
label: 'Before',
field: (row) => formatValue(row.before),
},
{
name: 'after',
label: 'After',
field: (row) => formatValue(row.after),
},
];
function formatValue(value) {
if (typeof value === 'boolean') {
return value ? t('Yes') : t('No');
}
if (isNaN(value) && !isNaN(Date.parse(value))) {
return toDate(value);
}
if (value === undefined) {
return t('Nothing');
}
return `"${value}"`;
}
function actionColor(action) {
if (action === 'insert') return 'positive';
if (action === 'update') return 'positive';
if (action === 'delete') return 'negative';
}
</script>
<template>
<div class="column items-center">
<QTimeline class="q-pa-md">
<QTimelineEntry heading tag="h4"> {{ t('Audit logs') }} </QTimelineEntry>
<VnPaginate
:data-key="`${props.model}Logs`"
:url="`${props.model}s/${route.params.id}/logs`"
order="id DESC"
:offset="100"
:limit="5"
auto-load
>
<template #body="{ rows }">
<template v-for="log of rows" :key="log.id">
<QTimelineEntry
:avatar="`/api/Images/user/160x160/${log.userFk}/download?access_token=${token}`"
>
<template #subtitle>
{{ log.userName }} -
{{
toDate(log.created, {
dateStyle: 'medium',
timeStyle: 'short',
})
}}
</template>
<template #title>
<QChip :color="actionColor(log.action)">
{{ t(`actions.${log.action}`) }}
</QChip>
{{ t(`models.${log.model}`) }}
</template>
<QTable
:rows="log.changes"
:columns="columns"
row-key="property"
hide-pagination
dense
flat
>
<template #header="propsLabel">
<QTr :props="propsLabel">
<QTh
v-for="col in propsLabel.cols"
:key="col.name"
:props="propsLabel"
>
{{ t(col.label) }}
</QTh>
</QTr>
</template>
</QTable>
</QTimelineEntry>
</template>
</template>
</VnPaginate>
</QTimeline>
</div>
<Teleport v-if="stateStore.isHeaderMounted()" to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn flat @click="stateStore.toggleRightDrawer()" round dense icon="menu">
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
<QDrawer v-model="stateStore.rightDrawer" show-if-above side="right" :width="300">
<QScrollArea class="fit text-grey-8">
<VnLogFilter :data-key="`${props.model}Logs`" />
</QScrollArea>
</QDrawer>
</template>
<style lang="scss" scoped>
.q-timeline {
width: 100%;
max-width: 80em;
}
</style>
<i18n>
en:
actions:
insert: Creates
update: Updates
delete: Deletes
models:
Claim: Claim
ClaimDms: Document
ClaimBeginning: Claimed Sales
ClaimObservation: Observation
properties:
id: ID
claimFk: Claim ID
saleFk: Sale ID
quantity: Quantity
observation: Observation
ticketCreated: Created
created: Created
isChargedToMana: Charged to mana
hasToPickUp: Has to pick Up
dmsFk: Document ID
text: Description
claimStateFk: Claim State
workerFk: Worker
clientFk: Customer
rma: RMA
responsibility: Responsibility
packages: Packages
es:
Audit logs: Registros de auditoría
Property: Propiedad
Before: Antes
After: Después
Yes: Si
Nothing: Nada
actions:
insert: Crea
update: Actualiza
delete: Elimina
models:
Claim: Reclamación
ClaimDms: Documento
ClaimBeginning: Línea reclamada
ClaimObservation: Observación
properties:
id: ID
claimFk: ID reclamación
saleFk: ID linea de venta
quantity: Cantidad
observation: Observación
ticketCreated: Creado
created: Creado
isChargedToMana: Cargado a maná
hasToPickUp: Se debe recoger
dmsFk: ID documento
text: Descripción
claimStateFk: Estado de la reclamación
workerFk: Trabajador
clientFk: Cliente
rma: RMA
responsibility: Responsabilidad
packages: Bultos
</i18n>

View File

@ -36,7 +36,7 @@ const props = defineProps({
const emit = defineEmits(['onUpdate']); const emit = defineEmits(['onUpdate']);
const discount = ref(0); const discount = ref(0); // eslint-disable-line vue/no-dupe-keys
let canceller; let canceller;
onMounted(() => { onMounted(() => {

View File

@ -13,7 +13,6 @@ export function useRole() {
name: data.user.name, name: data.user.name,
nickname: data.user.nickname, nickname: data.user.nickname,
lang: data.user.lang || 'es', lang: data.user.lang || 'es',
darkMode: data.user.userConfig.darkMode,
}; };
state.setUser(userData); state.setUser(userData);
state.setRoles(roles); state.setRoles(roles);

View File

@ -1,5 +1,7 @@
import { useState } from './useState'; import { useState } from './useState';
import { useRole } from './useRole'; import { useRole } from './useRole';
import { useUserConfig } from './useUserConfig';
export function useSession() { export function useSession() {
function getToken() { function getToken() {
@ -36,11 +38,10 @@ export function useSession() {
} }
async function login(token, keepLogin) { async function login(token, keepLogin) {
const { fetch } = useRole();
setToken({ token, keepLogin }); setToken({ token, keepLogin });
await fetch(); await useRole().fetch();
await useUserConfig().fetch();
} }
function isLoggedIn() { function isLoggedIn() {

View File

@ -0,0 +1,17 @@
import axios from 'axios';
import { useState } from './useState';
export function useUserConfig() {
const state = useState();
async function fetch() {
const { data } = await axios.get('UserConfigs/getUserConfig');
const user = state.getUser().value;
user.darkMode = data.darkMode;
state.setUser(user);
}
return {
fetch,
};
}

View File

@ -361,6 +361,7 @@ export default {
list: 'List', list: 'List',
basicData: 'Basic data', basicData: 'Basic data',
summary: 'Summary', summary: 'Summary',
notifications: 'Notifications',
}, },
list: { list: {
name: 'Name', name: 'Name',
@ -394,6 +395,12 @@ export default {
role: 'Role', role: 'Role',
sipExtension: 'Extension', sipExtension: 'Extension',
}, },
notificationsManager: {
activeNotifications: 'Active notifications',
availableNotifications: 'Available notifications',
subscribed: 'Subscribed to the notification',
unsubscribed: 'Unsubscribed from the notification',
},
imageNotFound: 'Image not found', imageNotFound: 'Image not found',
}, },
wagon: { wagon: {

View File

@ -361,6 +361,7 @@ export default {
list: 'Listado', list: 'Listado',
basicData: 'Datos básicos', basicData: 'Datos básicos',
summary: 'Resumen', summary: 'Resumen',
notifications: 'Notificaciones',
}, },
list: { list: {
name: 'Nombre', name: 'Nombre',
@ -394,6 +395,12 @@ export default {
role: 'Rol', role: 'Rol',
sipExtension: 'Extensión', sipExtension: 'Extensión',
}, },
notificationsManager: {
activeNotifications: 'Notificaciones activas',
availableNotifications: 'Notificaciones disponibles',
subscribed: 'Se ha suscrito a la notificación',
unsubscribed: 'Se ha dado de baja de la notificación',
},
imageNotFound: 'No se ha encontrado la imagen', imageNotFound: 'No se ha encontrado la imagen',
}, },
wagon: { wagon: {

View File

@ -418,7 +418,7 @@ function showImportDialog() {
<i18n> <i18n>
en: en:
You are about to remove {count} rows: ' You are about to remove {count} rows: '
You are about to remove <strong>{count}</strong> row | You are about to remove <strong>{count}</strong> row |
You are about to remove <strong>{count}</strong> rows' You are about to remove <strong>{count}</strong> rows'
es: es:
Claimed lines: Líneas reclamadas Claimed lines: Líneas reclamadas
@ -435,6 +435,6 @@ es:
Discount updated: Descuento actualizado Discount updated: Descuento actualizado
Claimed quantity: Cantidad reclamada Claimed quantity: Cantidad reclamada
You are about to remove {count} rows: ' You are about to remove {count} rows: '
Vas a eliminar <strong>{count}</strong> línea | Vas a eliminar <strong>{count}</strong> línea |
Vas a eliminar <strong>{count}</strong> líneas' Vas a eliminar <strong>{count}</strong> líneas'
</i18n> </i18n>

View File

@ -1,201 +1,6 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import VnLog from 'src/components/common/VnLog.vue';
import { useRoute } from 'vue-router';
import { useSession } from 'src/composables/useSession';
import { useStateStore } from 'stores/useStateStore';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import ClaimLogFilter from './ClaimLogFilter.vue';
import { toDate } from 'src/filters';
const stateStore = useStateStore();
const route = useRoute();
const session = useSession();
const token = session.getToken();
const { t } = useI18n();
const columns = [
{
name: 'property',
label: 'Property',
field: (row) => t(`properties.${row.property}`),
align: 'left',
},
{
name: 'before',
label: 'Before',
field: (row) => formatValue(row.before),
},
{
name: 'after',
label: 'After',
field: (row) => formatValue(row.after),
},
];
function formatValue(value) {
if (typeof value === 'boolean') {
return value ? t('Yes') : t('No');
}
if (isNaN(value) && !isNaN(Date.parse(value))) {
return toDate(value);
}
if (value === undefined) {
return t('Nothing');
}
return `"${value}"`;
}
function actionColor(action) {
if (action === 'insert') return 'positive';
if (action === 'update') return 'positive';
if (action === 'delete') return 'negative';
}
</script> </script>
<template> <template>
<div class="column items-center"> <VnLog model="Claim"></VnLog>
<QTimeline class="q-pa-md">
<QTimelineEntry heading tag="h4"> {{ t('Audit logs') }} </QTimelineEntry>
<VnPaginate
data-key="ClaimLogs"
:url="`Claims/${route.params.id}/logs`"
order="id DESC"
:offset="100"
:limit="5"
auto-load
>
<template #body="{ rows }">
<template v-for="log of rows" :key="log.id">
<QTimelineEntry
:avatar="`/api/Images/user/160x160/${log.userFk}/download?access_token=${token}`"
>
<template #subtitle>
{{ log.userName }} -
{{
toDate(log.created, {
dateStyle: 'medium',
timeStyle: 'short',
})
}}
</template>
<template #title>
<QChip :color="actionColor(log.action)">
{{ t(`actions.${log.action}`) }}
</QChip>
{{ t(`models.${log.model}`) }}
</template>
<QTable
:rows="log.changes"
:columns="columns"
row-key="property"
hide-pagination
dense
flat
>
<template #header="props">
<QTr :props="props">
<QTh
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ t(col.label) }}
</QTh>
</QTr>
</template>
</QTable>
</QTimelineEntry>
</template>
</template>
</VnPaginate>
</QTimeline>
</div>
<Teleport v-if="stateStore.isHeaderMounted()" to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn flat @click="stateStore.toggleRightDrawer()" round dense icon="menu">
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
<QDrawer v-model="stateStore.rightDrawer" show-if-above side="right" :width="300">
<QScrollArea class="fit text-grey-8">
<ClaimLogFilter data-key="ClaimLogs" />
</QScrollArea>
</QDrawer>
</template> </template>
<style lang="scss" scoped>
.q-timeline {
width: 100%;
max-width: 80em;
}
</style>
<i18n>
en:
actions:
insert: Creates
update: Updates
delete: Deletes
models:
Claim: Claim
ClaimDms: Document
ClaimBeginning: Claimed Sales
ClaimObservation: Observation
properties:
id: ID
claimFk: Claim ID
saleFk: Sale ID
quantity: Quantity
observation: Observation
ticketCreated: Created
created: Created
isChargedToMana: Charged to mana
hasToPickUp: Has to pick Up
dmsFk: Document ID
text: Description
claimStateFk: Claim State
workerFk: Worker
clientFk: Customer
rma: RMA
responsibility: Responsibility
packages: Packages
es:
Audit logs: Registros de auditoría
Property: Propiedad
Before: Antes
After: Después
Yes: Si
Nothing: Nada
actions:
insert: Crea
update: Actualiza
delete: Elimina
models:
Claim: Reclamación
ClaimDms: Documento
ClaimBeginning: Línea reclamada
ClaimObservation: Observación
properties:
id: ID
claimFk: ID reclamación
saleFk: ID linea de venta
quantity: Cantidad
observation: Observación
ticketCreated: Creado
created: Creado
isChargedToMana: Cargado a maná
hasToPickUp: Se debe recoger
dmsFk: ID documento
text: Descripción
claimStateFk: Estado de la reclamación
workerFk: Trabajador
clientFk: Cliente
rma: RMA
responsibility: Responsabilidad
packages: Bultos
</i18n>

View File

@ -0,0 +1,142 @@
<script setup>
import axios from 'axios';
import { useQuasar } from 'quasar';
import { computed, onMounted, onUpdated, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const entityId = computed(() => $props.id || route.params.id);
onMounted(() => fetch());
onUpdated(() => fetch());
const route = useRoute();
const { t } = useI18n();
const quasar = useQuasar();
const notifications = ref([]);
async function fetch() {
try {
await axios
.get(`NotificationSubscriptions/${entityId.value}/getList`)
.then(async (res) => {
if (res.data) {
notifications.value = res.data;
}
});
} catch (e) {
//
}
}
async function disableNotification(notification) {
await axios
.delete(`NotificationSubscriptions/${notification.id}`)
.catch(() => (notification.active = true))
.then((res) => {
if (res.data) {
notification.id = null;
notification.active = false;
quasar.notify({
type: 'positive',
message: t('worker.notificationsManager.unsubscribed'),
});
}
});
}
async function toggleNotification(notification) {
if (!notification.active) {
await disableNotification(notification);
} else {
await axios
.post(`NotificationSubscriptions`, {
notificationFk: notification.notificationFk,
userFk: entityId.value,
})
.catch(() => (notification.active = false))
.then((res) => {
if (res.data) {
notification.id = res.data.id;
quasar.notify({
type: 'positive',
message: t('worker.notificationsManager.subscribed'),
});
}
});
}
}
</script>
<template>
<QPage>
<QCard class="q-pa-md">
<QList>
<div
v-show="
notifications.filter(
(notification) => notification.active == true
).length
"
>
<QItemLabel header class="text-h6">
{{ t('worker.notificationsManager.activeNotifications') }}
</QItemLabel>
<QItem>
<div
v-for="notification in notifications.filter(
(notification) => notification.active == true
)"
:key="notification.id"
>
<QChip
:key="notification.id"
:label="notification.name"
text-color="white"
color="primary"
class="q-mr-sm"
removable
@remove="disableNotification(notification)"
/>
</div>
</QItem>
</div>
<div v-show="notifications.length">
<QItemLabel header class="text-h6">
{{ t('worker.notificationsManager.availableNotifications') }}
</QItemLabel>
<div class="row">
<QItem
class="col-3"
:key="notification.notificationFk"
v-for="notification in notifications"
>
<QItemSection>
<QItemLabel>{{ notification.name }}</QItemLabel>
<QItemLabel caption>{{
notification.description
}}</QItemLabel>
</QItemSection>
<QItemSection side top>
<QToggle
checked-icon="check"
unchecked-icon="close"
indeterminate-icon="block"
v-model="notification.active"
@update:model-value="toggleNotification(notification)"
/>
</QItemSection>
</QItem>
</div>
</div>
</QList>
</QCard>
</QPage>
</template>

View File

@ -10,11 +10,11 @@ import { i18n } from 'src/boot/i18n';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
import { useUserConfig } from 'src/composables/useUserConfig';
import { toLowerCamel } from 'src/filters'; import { toLowerCamel } from 'src/filters';
const state = useState(); const state = useState();
const session = useSession(); const session = useSession();
const role = useRole();
const { t } = i18n.global; const { t } = i18n.global;
const createHistory = process.env.SERVER const createHistory = process.env.SERVER
@ -53,12 +53,13 @@ export default route(function (/* { store, ssrContext } */) {
if (isLoggedIn()) { if (isLoggedIn()) {
const stateRoles = state.getRoles().value; const stateRoles = state.getRoles().value;
if (stateRoles.length === 0) { if (stateRoles.length === 0) {
await role.fetch(); await useRole().fetch();
await useUserConfig().fetch();
} }
const matches = to.matched; const matches = to.matched;
const hasRequiredRoles = matches.every((route) => { const hasRequiredRoles = matches.every((route) => {
const meta = route.meta; const meta = route.meta;
if (meta && meta.roles) return role.hasAny(meta.roles); if (meta && meta.roles) return useRole().hasAny(meta.roles);
return true; return true;
}); });

View File

@ -11,7 +11,7 @@ export default {
redirect: { name: 'WorkerMain' }, redirect: { name: 'WorkerMain' },
menus: { menus: {
main: ['WorkerList'], main: ['WorkerList'],
card: [], // card: ['WorkerNotificationsManager'],
}, },
children: [ children: [
{ {
@ -42,9 +42,19 @@ export default {
path: 'summary', path: 'summary',
meta: { meta: {
title: 'summary', title: 'summary',
icon: 'launch',
}, },
component: () => import('src/pages/Worker/Card/WorkerSummary.vue'), component: () => import('src/pages/Worker/Card/WorkerSummary.vue'),
}, },
// {
// name: 'WorkerNotificationsManager',
// path: 'notifications',
// meta: {
// title: 'notifications',
// icon: 'notifications',
// },
// component: () => import('src/pages/Worker/Card/WorkerNotificationsManager.vue'),
// },
], ],
}, },
], ],

View File

@ -0,0 +1,27 @@
describe('WorkerNotificationsManager', () => {
beforeEach(() => {
const workerId = 1110;
cy.viewport(1280, 720);
cy.login('salesBoss');
cy.visit(`/#/worker/${workerId}/notifications`);
});
it('should unsubscribe 2 notifications, check the unsubscription has been saved, subscribe to other one and should check the data has been saved', () => {
cy.get('.q-chip').should('have.length', 3);
cy.get('.q-toggle__thumb').eq(0).click();
cy.get('.q-notification__message').should('have.text', 'Unsubscribed from the notification');
cy.get('.q-chip > .q-icon').eq(0).click();
cy.reload();
cy.get('.q-chip').should('have.length', 1);
cy.get('.q-toggle__thumb').should('have.length', 3).eq(0).click();
cy.get('.q-notification__message').should('have.text', 'Subscribed to the notification');
cy.get('.q-toggle__thumb').should('have.length', 3).eq(1).click();
cy.get('.q-notification__message').should('have.text', 'Subscribed to the notification');
cy.reload();
cy.get('.q-chip').should('have.length', 3);
});
});

View File

@ -0,0 +1,75 @@
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import VnLog from 'src/components/common/VnLog.vue';
describe('VnLog', () => {
let vm;
beforeAll(() => {
vm = createWrapper(VnLog, {
global: {
stubs: ['FetchData', 'VnPaginate'],
mocks: {
fetch: vi.fn(),
},
},
propsData: {
model: "Claim",
},
}).vm;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('formatValue()', () => {
it('should return Yes if has a true boolean', async () => {
const result = vm.formatValue(true);
expect(result).toEqual('Yes');
});
it('should return No if has a true boolean', async () => {
const result = vm.formatValue(false);
expect(result).toEqual('No');
});
it('should return Nothing if has no params', async () => {
const result = vm.formatValue();
expect(result).toEqual('Nothing');
});
it('should return a string from a string value', async () => {
const result = vm.formatValue('Something');
expect(result).toEqual(`"Something"`);
});
it('should call to format a date', async () => {
vi.mock('src/filters', () => ({
toDate: ()=>{
return "Date formatted"
},
}));
const result = vm.formatValue('01-01-1970');
expect(result).toEqual("Date formatted");
});
});
describe('actionColor()', () => {
it('should return positive if insert', async () => {
const result = vm.actionColor('insert');
expect(result).toEqual('positive');
});
it('should return positive if update', async () => {
const result = vm.actionColor('update');
expect(result).toEqual('positive');
});
it('should return negative if delete', async () => {
const result = vm.actionColor('delete');
expect(result).toEqual('negative');
});
});
});

View File

@ -23,21 +23,18 @@ describe('useRole', () => {
name: `T'Challa`, name: `T'Challa`,
nickname: 'Black Panther', nickname: 'Black Panther',
lang: 'en', lang: 'en',
userConfig: {
darkMode: false,
},
}; };
const expectedUser = { const expectedUser = {
id: 999, id: 999,
name: `T'Challa`, name: `T'Challa`,
nickname: 'Black Panther', nickname: 'Black Panther',
lang: 'en', lang: 'en',
darkMode: false,
}; };
const expectedRoles = ['salesPerson', 'admin']; const expectedRoles = ['salesPerson', 'admin'];
vi.spyOn(axios, 'get').mockResolvedValue({ vi.spyOn(axios, 'get')
.mockResolvedValueOnce({
data: { roles: rolesData, user: fetchedUser }, data: { roles: rolesData, user: fetchedUser },
}); })
vi.spyOn(role.state, 'setUser'); vi.spyOn(role.state, 'setUser');
vi.spyOn(role.state, 'setRoles'); vi.spyOn(role.state, 'setRoles');

View File

@ -0,0 +1,100 @@
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import WorkerNotificationsManager from 'src/pages/Worker/Card/WorkerNotificationsManager.vue';
describe('WorkerNotificationsManager', () => {
let vm;
const entityId = 1110;
beforeAll(() => {
vm = createWrapper(WorkerNotificationsManager, {
propsData: {
id: entityId,
},
}).vm;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('fetch()', () => {
it('should fetch notification subscriptions and role mappings', async () => {
vi.spyOn(axios, 'get')
.mockResolvedValueOnce({
data: [
{
id: 1,
name: 'Name 1',
description: 'Description 1',
notificationFk: 1,
active: true
},
],
});
await vm.fetch();
expect(axios.get).toHaveBeenCalledWith(`NotificationSubscriptions/${entityId}/getList`);
expect(vm.notifications).toEqual([
{
id: 1,
notificationFk: 1,
name: 'Name 1',
description: 'Description 1',
active: true,
},
]);
});
});
describe('disableNotification()', () => {
it('should disable the notification', async () => {
vi.spyOn(axios, 'delete').mockResolvedValue({ data: { count: 1 } });
vi.spyOn(vm.quasar, 'notify');
const subscriptionId = 1;
vm.notifications = [{ id: 1, active: true }];
await vm.disableNotification(vm.notifications[0]);
expect(axios.delete).toHaveBeenCalledWith(
`NotificationSubscriptions/${subscriptionId}`
);
expect(vm.notifications[0].id).toBeNull();
expect(vm.notifications[0].id).toBeFalsy();
expect(vm.quasar.notify).toHaveBeenCalledWith(
expect.objectContaining({ type: 'positive' })
);
});
});
describe('toggleNotification()', () => {
it('should activate the notification', async () => {
vi.spyOn(axios, 'post').mockResolvedValue({
data: { id: 1, notificationFk: 1 },
});
vm.notifications = [{ id: null, active: true, notificationFk: 1 }];
await vm.toggleNotification(vm.notifications[0]);
expect(axios.post).toHaveBeenCalledWith('NotificationSubscriptions', {
notificationFk: 1,
userFk: entityId,
});
expect(vm.notifications[0].id).toBe(1);
expect(vm.notifications[0].active).toBeTruthy();
expect(vm.quasar.notify).toHaveBeenCalledWith(
expect.objectContaining({ type: 'positive' })
);
});
it('should disable the notification', async () => {
vi.spyOn(vm, 'disableNotification');
vm.notifications = [{ id: 1, active: false, notificationFk: 1 }];
await vm.toggleNotification(vm.notifications[0]);
expect(vm.notifications[0].id).toBe(null);
expect(vm.notifications[0].active).toBeFalsy();
});
});
});