瀏覽代碼

fix conflicts

Olivier Massot 1 年之前
父節點
當前提交
ae68f1c4ac
共有 47 個文件被更改,包括 532 次插入330 次删除
  1. 2 0
      composables/layout/useMenu.ts
  2. 65 5
      config/abilities/pages/parameters.yaml
  3. 12 2
      lang/fr.json
  4. 28 0
      middleware/routing.global.ts
  5. 4 0
      pages/cmf_licence_structure.vue
  6. 1 0
      pages/parameters.vue
  7. 4 0
      pages/parameters/attendances.vue
  8. 4 0
      pages/parameters/bulletin.vue
  9. 4 0
      pages/parameters/education_notation.vue
  10. 4 0
      pages/parameters/education_timings/index.vue
  11. 4 0
      pages/parameters/general_parameters.vue
  12. 4 0
      pages/parameters/intranet.vue
  13. 4 0
      pages/parameters/residence_areas/index.vue
  14. 4 0
      pages/parameters/sms.vue
  15. 4 0
      pages/parameters/super_admin.vue
  16. 4 0
      pages/parameters/teaching.vue
  17. 4 0
      pages/parameters/website.vue
  18. 0 16
      pages/poc.vue
  19. 3 3
      pages/subscription.vue
  20. 43 0
      services/layout/menuBuilder/abstractMenuBuilder.ts
  21. 1 5
      services/layout/menuBuilder/accountMenuBuilder.ts
  22. 14 111
      services/layout/menuBuilder/parametersMenuBuilder.ts
  23. 4 0
      services/layout/menuComposer.ts
  24. 6 3
      services/rights/abilityBuilder.ts
  25. 69 1
      tests/units/services/layout/menuBuilder/abstractMenuBuilder.test.ts
  26. 6 1
      tests/units/services/layout/menuBuilder/accessMenuBuilder.test.ts
  27. 31 0
      tests/units/services/layout/menuBuilder/accountMenuBuilder.test.ts
  28. 6 1
      tests/units/services/layout/menuBuilder/admin2iosMenuBuilder.test.ts
  29. 6 1
      tests/units/services/layout/menuBuilder/agendaMenuBuilder.test.ts
  30. 6 1
      tests/units/services/layout/menuBuilder/billingMenuBuilder.test.ts
  31. 6 1
      tests/units/services/layout/menuBuilder/communicationMenuBuilder.test.ts
  32. 6 1
      tests/units/services/layout/menuBuilder/configurationMenuBuilder.test.ts
  33. 6 1
      tests/units/services/layout/menuBuilder/cotisationsMenuBuilder.test.ts
  34. 7 2
      tests/units/services/layout/menuBuilder/donorsMenuBuilder.test.ts
  35. 6 1
      tests/units/services/layout/menuBuilder/educationalMenuBuilder.test.ts
  36. 7 2
      tests/units/services/layout/menuBuilder/equipmentMenuBuilder.test.ts
  37. 6 1
      tests/units/services/layout/menuBuilder/mainMenuBuilder.test.ts
  38. 7 2
      tests/units/services/layout/menuBuilder/myAccessesMenuBuilder.test.ts
  39. 6 1
      tests/units/services/layout/menuBuilder/myFamilyMenuBuilder.test.ts
  40. 94 0
      tests/units/services/layout/menuBuilder/parametersMenuBuilder.test.ts
  41. 0 104
      tests/units/services/layout/menuBuilder/parametersMenuBuilder.test.ts.off
  42. 0 56
      tests/units/services/layout/menuBuilder/rewardsMenuBuilder.test.ts
  43. 6 1
      tests/units/services/layout/menuBuilder/statsMenuBuilder.test.ts
  44. 6 2
      tests/units/services/layout/menuBuilder/websiteAdminMenuBuilder.test.ts
  45. 6 1
      tests/units/services/layout/menuBuilder/websiteListMenuBuilder.test.ts
  46. 9 1
      tests/units/services/layout/menuComposer.test.ts
  47. 3 3
      tests/units/services/rights/abilityBuilder.test.ts

+ 2 - 0
composables/layout/useMenu.ts

@@ -23,6 +23,7 @@ export const useMenu = () => {
   const organizationProfile = useOrganizationProfileStore()
   const accessProfile = useAccessProfileStore()
   const layoutState = useLayoutStore()
+  const router = useRouter()
 
   /**
    * Construct all Menus
@@ -36,6 +37,7 @@ export const useMenu = () => {
       ability,
       organizationProfile,
       accessProfile as AccessProfile,
+      router,
       layoutState,
     )
   }

+ 65 - 5
config/abilities/pages/parameters.yaml

@@ -26,7 +26,7 @@ parameters_page:
         parameters: [{ action: 'read', subject: 'general-config' }],
       }
 
-parameters_communication_page:
+parameters_general_page:
   action: 'display'
   conditions:
     - { function: organizationHasAnyModule, parameters: ['GeneralConfig'] }
@@ -35,7 +35,36 @@ parameters_communication_page:
         parameters: [{ action: 'read', subject: 'general-config' }],
       }
 
-parameters_student_page:
+parameters_website_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['GeneralConfig'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'general-config' }],
+      }
+
+parameters_teaching_page:
+  action: 'display'
+  conditions:
+    - { function: organizationIsSchool }
+    - { function: organizationHasAnyModule, parameters: ['GeneralConfig'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'general-config' }],
+      }
+
+parameters_intranet_page:
+  action: 'display'
+  conditions:
+    - { function: organizationIsSchool }
+    - { function: organizationHasAnyModule, parameters: ['GeneralConfig'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'general-config' }],
+      }
+
+parameters_education_notation_page:
   action: 'display'
   conditions:
     - { function: organizationIsSchool }
@@ -45,7 +74,7 @@ parameters_student_page:
         parameters: [{ action: 'read', subject: 'general-config' }],
       }
 
-parameters_education_page:
+parameters_bulletin_page:
   action: 'display'
   conditions:
     - { function: organizationIsSchool }
@@ -55,7 +84,7 @@ parameters_education_page:
         parameters: [{ action: 'read', subject: 'general-config' }],
       }
 
-parameters_bills_page:
+parameters_education_timings_page:
   action: 'display'
   conditions:
     - { function: organizationIsSchool }
@@ -65,7 +94,38 @@ parameters_bills_page:
         parameters: [{ action: 'read', subject: 'general-config' }],
       }
 
-parameters_secure_page:
+parameters_residence_areas_page:
+  action: 'display'
+  conditions:
+    - { function: organizationIsSchool }
+    - { function: organizationHasAnyModule, parameters: ['GeneralConfig'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'general-config' }],
+      }
+
+parameters_attendances_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['GeneralConfig'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'general-config' }],
+      }
+
+parameters_sms_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAllModules,
+        parameters: ['GeneralConfig', 'Sms'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'general-config' }],
+      }
+
+parameters_super_admin_page:
   action: 'display'
   conditions:
     - { function: organizationHasAnyModule, parameters: ['GeneralConfig'] }

+ 12 - 2
lang/fr.json

@@ -190,6 +190,7 @@
   "usernameSMS": "Nom d'utilisateur SMS",
   "smsSenderName": "Personnaliser le nom de l'expéditeur SMS",
   "attendance": "Absences",
+  "parameters_attendances_page": "Absences",
   "attendanceBookingReason": "Motif d'absence / retard",
   "attendanceBookingReasons": "Motifs d'absence / retard",
   "new_attendance_booking_reason": "Nouveau motif d'absence / retard",
@@ -480,6 +481,7 @@
   "right_menu": "Droits version 5.9",
   "tree_menu": "Gestion de l'arbre",
   "website": "Site internet",
+  "parameters_website_page": "Site internet",
   "website_breadcrumbs": "Site internet",
   "websiteList": "Site(s) internet",
   "advanced_modification": "Administration site internet",
@@ -547,7 +549,7 @@
   "my_documents": "Mes documents",
   "my_profile": "Mon profil",
   "adherent_list": "Liste des adhérents avec leurs coordonnées",
-  "my_subscription": "Mon abonnement",
+  "subscription_page": "Mon abonnement",
   "my_bills": "Mes factures",
   "print_my_licence": "Imprimer ma licence CMF",
   "logout": "Se déconnecter",
@@ -614,22 +616,29 @@
   "end_date_of_courses": "Date de fin des cours",
   "show_adherents_list_and_their_coordinates": "Afficher la liste des adhérents et leurs coordonnées",
   "students_are_also_association_members": "Les élèves sont adhérents également de l'association",
-  "general_parameters": "Paramètres généraux",
+  "parameters_general_page": "Paramètres généraux",
   "general_parameters_breadcrumbs": "Paramètres généraux",
   "teaching": "Enseignements",
+  "parameters_teaching_page": "Enseignements",
   "teaching_breadcrumbs": "Enseignements",
   "intranet_access": "Accès intranet (professeurs, élèves...)",
+  "parameters_intranet_page": "Accès intranet",
   "intranet_breadcrumbs": "Accès intranet",
   "educationNotations": "Suivi pédagogique",
+  "parameters_education_notation_page": "Suivi pédagogique",
   "education_notation_breadcrumbs": "Suivi pédagogique",
   "bulletin": "Bulletins",
+  "parameters_bulletin_page": "Bulletins",
   "bulletin_breadcrumbs": "Bulletins",
   "educationTimings": "Durée des cours (en minutes)",
   "new_education_timing": "Nouvelle durée de cours",
   "residenceAreas": "Zones de résidence",
+  "parameters_residence_areas_page": "Zones de résidence",
+  "parameters_sms_page": "Option SMS",
   "sms_option": "Option SMS",
   "sms_breadcrumbs": "SMS",
   "super_admin": "Compte super-admin",
+  "parameters_super_admin_page": "Compte super-admin",
   "super_admin_breadcrumbs": "Compte super-admin",
   "an_error_happened": "Une erreur s'est produite",
   "your_opentalent_website_address_is": "L'adresse de votre site Opentalent est",
@@ -673,6 +682,7 @@
   "EUROPE_ZURICH": "Europe/Zurich",
   "EUROPE_PARIS": "Europe/Paris",
   "licenceQrCode": "QrCode pour la licence",
+  "parameters_education_timings_page": "Durée des cours",
   "education_timings_breadcrumbs": "Durée des cours",
   "create_a_new_residence_area": "Créer une nouvelle zone de résidence",
   "residence_areas_breadcrumbs": "Zones de résidence",

+ 28 - 0
middleware/routing.global.ts

@@ -0,0 +1,28 @@
+import { useAbility } from '@casl/vue'
+import { useRedirect } from '~/composables/utils/useRedirect'
+
+/**
+ * Détermine les autorisations d'accès aux pages
+ */
+export default defineNuxtRouteMiddleware((to, _) => {
+  const ability = useAbility()
+  const candidates = to.matched.map((route) => route.name)
+
+  const restrictedPages = ability.rules
+    .filter((rule) => rule.action.toString() === 'display')
+    .map((rule) => rule.subject.toString())
+
+  candidates.forEach((routeName) => {
+    const name: string = routeName?.toString() ?? ''
+
+    if (
+      name &&
+      restrictedPages.includes(name) &&
+      !ability.can('display', name)
+    ) {
+      console.error('No right to see this page')
+      const { redirectToHome } = useRedirect()
+      redirectToHome()
+    }
+  })
+})

+ 4 - 0
pages/cmf_licence_structure.vue

@@ -41,6 +41,10 @@ import { useDownloadFile } from '~/composables/utils/useDownloadFile'
 const { em } = useEntityManager()
 const { getRef } = useEntityFetch()
 
+definePageMeta({
+  name: 'cmf_licence_page',
+})
+
 const sseStore = useSseStore()
 const async = () => {
   return sseStore.connected

+ 1 - 0
pages/parameters.vue

@@ -13,6 +13,7 @@
  * @see https://nuxt.com/docs/guide/directory-structure/layouts#overriding-a-layout-on-a-per-page-basis
  */
 definePageMeta({
+  name: 'parameters_page',
   layout: false,
 })
 </script>

+ 4 - 0
pages/parameters/attendances.vue

@@ -99,6 +99,10 @@ import UrlUtils from '~/services/utils/urlUtils'
 import AttendanceBookingReason from '~/models/Booking/AttendanceBookingReason'
 import AttendanceBookingReasonRepository from '~/stores/repositories/AttendanceBookingReasonRepository'
 
+definePageMeta({
+  name: 'parameters_attendances_page',
+})
+
 const { fetch } = useEntityFetch()
 
 const i18n = useI18n()

+ 4 - 0
pages/parameters/bulletin.vue

@@ -79,6 +79,10 @@ import Parameters from '~/models/Organization/Parameters'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 
+definePageMeta({
+  name: 'parameters_bulletin_page',
+})
+
 const { fetch } = useEntityFetch()
 
 const organizationProfile = useOrganizationProfileStore()

+ 4 - 0
pages/parameters/education_notation.vue

@@ -64,6 +64,10 @@ import Parameters from '~/models/Organization/Parameters'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 
+definePageMeta({
+  name: 'parameters_education_notation_page',
+})
+
 const { fetch } = useEntityFetch()
 
 const organizationProfile = useOrganizationProfileStore()

+ 4 - 0
pages/parameters/education_timings/index.vue

@@ -60,6 +60,10 @@ import EducationTimingsRepository from '~/stores/repositories/EducationTimingsRe
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 import UrlUtils from '~/services/utils/urlUtils'
 
+definePageMeta({
+  name: 'parameters_education_timings_page',
+})
+
 const organizationProfile = useOrganizationProfileStore()
 
 if (organizationProfile.parametersId === null) {

+ 4 - 0
pages/parameters/general_parameters.vue

@@ -92,6 +92,10 @@ import Parameters from '~/models/Organization/Parameters'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 
+definePageMeta({
+  name: 'parameters_general_page',
+})
+
 const { fetch } = useEntityFetch()
 
 const organizationProfile = useOrganizationProfileStore()

+ 4 - 0
pages/parameters/intranet.vue

@@ -56,6 +56,10 @@ import Parameters from '~/models/Organization/Parameters'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 
+definePageMeta({
+  name: 'parameters_intranet_page',
+})
+
 const { fetch } = useEntityFetch()
 
 const organizationProfile = useOrganizationProfileStore()

+ 4 - 0
pages/parameters/residence_areas/index.vue

@@ -60,6 +60,10 @@ import ResidenceArea from '~/models/Billing/ResidenceArea'
 import ResidenceAreasRepository from '~/stores/repositories/ResidenceAreasRepository'
 import UrlUtils from '~/services/utils/urlUtils'
 
+definePageMeta({
+  name: 'parameters_residence_areas_page',
+})
+
 const residenceAreasRepo = useRepo(ResidenceAreasRepository)
 
 const { fetchCollection } = useEntityFetch()

+ 4 - 0
pages/parameters/sms.vue

@@ -40,6 +40,10 @@ import Parameters from '~/models/Organization/Parameters'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 
+definePageMeta({
+  name: 'parameters_sms_page',
+})
+
 const { fetch } = useEntityFetch()
 
 const organizationProfile = useOrganizationProfileStore()

+ 4 - 0
pages/parameters/super_admin.vue

@@ -45,6 +45,10 @@ import { useAccessProfileStore } from '~/stores/accessProfile'
 import AdminAccess from '~/models/Access/AdminAccess'
 import { useValidationUtils } from '~/composables/utils/useValidationUtils'
 
+definePageMeta({
+  name: 'parameters_super_admin_page',
+})
+
 const { fetch } = useEntityFetch()
 
 const accessProfile = useAccessProfileStore()

+ 4 - 0
pages/parameters/teaching.vue

@@ -58,6 +58,10 @@ import type { AnyJson } from '~/types/data'
 import { useEnumFetch } from '~/composables/data/useEnumFetch'
 import ApiResource from '~/models/ApiResource'
 
+definePageMeta({
+  name: 'parameters_teaching_page',
+})
+
 const organizationProfile = useOrganizationProfileStore()
 
 if (organizationProfile.parametersId === null) {

+ 4 - 0
pages/parameters/website.vue

@@ -146,6 +146,10 @@ import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import Subdomain from '~/models/Organization/Subdomain'
 import ApiResource from '~/models/ApiResource'
 
+definePageMeta({
+  name: 'parameters_website_page',
+})
+
 const { fetch, fetchCollection } = useEntityFetch()
 
 const organizationProfile = useOrganizationProfileStore()

+ 0 - 16
pages/poc.vue

@@ -1,16 +0,0 @@
-<template>
-  <main>
-    <div class="pa-8">
-      <h1>POC</h1>
-      <NuxtPage />
-    </div>
-  </main>
-</template>
-
-<script setup lang="ts"></script>
-
-<style scoped lang="scss">
-h1 {
-  color: rgb(var(--v-theme-primary));
-}
-</style>

+ 3 - 3
pages/subscription.vue

@@ -481,9 +481,9 @@ import MobytUserStatus from '~/models/Organization/MobytUserStatus'
 
 const ability = useAbility()
 
-if (!ability.can('display', 'subscription_page')) {
-  throw new Error('Forbidden')
-}
+definePageMeta({
+  name: 'subscription_page',
+})
 
 const showDolibarrPanel = computed(
   () =>

+ 43 - 0
services/layout/menuBuilder/abstractMenuBuilder.ts

@@ -1,5 +1,6 @@
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability'
+import type { Router } from 'vue-router'
 import type {
   IconItem,
   MenuBuilder,
@@ -21,6 +22,7 @@ abstract class AbstractMenuBuilder implements MenuBuilder {
   protected ability: AnyAbility
   protected organizationProfile: organizationState
   protected accessProfile: AccessProfile
+  protected router: Router
 
   /**
    * Nom court désignant le menu que construit ce builder
@@ -32,11 +34,13 @@ abstract class AbstractMenuBuilder implements MenuBuilder {
     ability: AnyAbility,
     organizationProfile: organizationState,
     accessProfile: AccessProfile,
+    router: Router,
   ) {
     this.runtimeConfig = runtimeConfig
     this.ability = ability
     this.organizationProfile = organizationProfile
     this.accessProfile = accessProfile
+    this.router = router
   }
 
   /**
@@ -80,6 +84,7 @@ abstract class AbstractMenuBuilder implements MenuBuilder {
    * @param {string} label Titre qui sera traduit
    * @param to
    * @param type
+   * @param noWarning
    * @return {MenuItem}
    */
   protected createItem(
@@ -90,6 +95,12 @@ abstract class AbstractMenuBuilder implements MenuBuilder {
   ): MenuItem {
     let url: string
 
+    if (type === MENU_LINK_TYPE.INTERNAL) {
+      console.warn(
+        "'createItem()' should not be used for internal links, use 'addChildItemIfAllowed()'",
+      )
+    }
+
     switch (type) {
       case MENU_LINK_TYPE.V1:
         // eslint-disable-next-line no-case-declarations
@@ -119,5 +130,37 @@ abstract class AbstractMenuBuilder implements MenuBuilder {
     )
     return builder.build()
   }
+
+  /**
+   * Make a list of MenuItems according to user abilities
+   *
+   * @param items
+   * @protected
+   */
+  protected makeChildren(
+    items: Array<{ pageName: string; icon?: string }>,
+  ): MenuItems {
+    const children: MenuItems = []
+
+    items.forEach((item) => {
+      const { pageName, icon } = item
+
+      if (this.ability.can('display', pageName)) {
+        const to = this.router.resolve({ name: pageName })
+        if (!to) {
+          throw new Error('unknown page name : ' + pageName)
+        }
+        children.push({
+          icon: icon ? { name: icon } : undefined,
+          label: pageName,
+          to: to.href,
+          type: MENU_LINK_TYPE.INTERNAL,
+          active: false,
+        })
+      }
+    })
+
+    return children
+  }
 }
 export default AbstractMenuBuilder

+ 1 - 5
services/layout/menuBuilder/accountMenuBuilder.ts

@@ -136,11 +136,7 @@ export default class AccountMenuBuilder extends AbstractMenuBuilder {
       )
     }
 
-    if (this.ability.can('display', 'subscription_page')) {
-      children.push(
-        this.createItem('my_subscription', undefined, `/subscription`),
-      )
-    }
+    children.push(...this.makeChildren([{ pageName: 'subscription_page' }]))
 
     if (this.ability.can('display', 'my_bills_page')) {
       children.push(

+ 14 - 111
services/layout/menuBuilder/parametersMenuBuilder.ts

@@ -11,117 +11,20 @@ export default class ParametersMenuBuilder extends AbstractMenuBuilder {
    * Construit le menu Header Configuration, ou null si aucune page accessible
    */
   build(): MenuGroup | null {
-    const children: MenuItems = []
-
-    if (!this.ability.can('display', 'parameters_page')) {
-      return null
-    }
-
-    children.push(
-      this.createItem(
-        'general_parameters',
-        { name: 'fas fa-gears' },
-        `/parameters/general_parameters`,
-      ),
-    )
-    children.push(
-      this.createItem(
-        'website',
-        { name: 'fas fa-globe-americas' },
-        `/parameters/website`,
-      ),
-    )
-
-    if (this.organizationProfile.isSchool) {
-      children.push(
-        this.createItem(
-          'teaching',
-          { name: 'fas fa-school' },
-          `/parameters/teaching`,
-        ),
-      )
-      children.push(
-        this.createItem(
-          'intranet_breadcrumbs',
-          { name: 'fas fa-arrows-down-to-people' },
-          `/parameters/intranet`,
-        ),
-      )
-      children.push(
-        this.createItem(
-          'educationNotations',
-          { name: 'fas fa-graduation-cap' },
-          `/parameters/education_notation`,
-        ),
-      )
-      children.push(
-        this.createItem(
-          'bulletin',
-          { name: 'fas fa-file-lines' },
-          `/parameters/bulletin`,
-        ),
-      )
-      children.push(
-        this.createItem(
-          'education_timings_breadcrumbs',
-          { name: 'fas fa-clock' },
-          `/parameters/education_timings`,
-        ),
-      )
-      children.push(
-        this.createItem(
-          'residenceAreas',
-          { name: 'fas fa-location-dot' },
-          `/parameters/residence_areas`,
-        ),
-      )
-    }
-
-    children.push(
-      this.createItem(
-        'attendance',
-        { name: 'fas fa-user-times' },
-        `/parameters/attendances`,
-      ),
-    )
-
-    if (this.organizationProfile.hasModule('Sms')) {
-      children.push(
-        this.createItem(
-          'sms_option',
-          { name: 'fas fa-mobile' },
-          `/parameters/sms`,
-        ),
-      )
-    }
-
-    children.push(
-      this.createItem(
-        'super_admin',
-        { name: 'fas fa-user-gear' },
-        `/parameters/super_admin`,
-      ),
-    )
-
-    // Voir nouveau découpage?
-    // if (this.ability.can('display', 'parameters_page')) {
-    //   children.push(this.createItem('general_params', {name: 'fas fa-cogs'},`/parameters`, MENU_LINK_TYPE.V1))
-    // }
-    // if (this.ability.can('display', 'parameters_communication_page')) {
-    //   children.push(this.createItem('communication_params', {name: 'fas fa-comments'},`/parameters/communication`, MENU_LINK_TYPE.V1))
-    // }
-    // if (this.ability.can('display', 'parameters_student_page')) {
-    //   children.push(this.createItem('students_params', {name: 'fas fa-users'},`/parameters/student`, MENU_LINK_TYPE.V1))
-    // }
-    // if (this.ability.can('display', 'parameters_education_page')) {
-    //   children.push(this.createItem('education_params', {name: 'fas fa-graduation-cap'},`/parameters/education`, MENU_LINK_TYPE.V1))
-    // }
-    // if (this.ability.can('display', 'parameters_bills_page')) {
-    //   children.push(this.createItem('bills_params', {name: 'fas fa-euro-sign'},`/parameters/billing`, MENU_LINK_TYPE.V1))
-    // }
-    // if (this.ability.can('display', 'parameters_secure_page')) {
-    //   children.push(this.createItem('secure_params', {name: 'fas fa-lock'},`/parameters/secure`, MENU_LINK_TYPE.V1))
-    // }
+    // prettier-ignore
+    const children: MenuItems = this.makeChildren([
+      { pageName: 'parameters_general_page', icon: 'fas fa-gears' },
+      { pageName: 'parameters_website_page', icon: 'fas fa-globe-americas' },
+      { pageName: 'parameters_teaching_page', icon: 'fas fa-school' },
+      { pageName: 'parameters_intranet_page', icon: 'fas fa-arrows-down-to-people' },
+      { pageName: 'parameters_education_notation_page', icon: 'fas fa-graduation-cap' },
+      { pageName: 'parameters_bulletin_page', icon: 'fas fa-file-lines' },
+      { pageName: 'parameters_education_timings_page', icon: 'fas fa-clock' },
+      { pageName: 'parameters_residence_areas_page', icon: 'fas fa-location-dot' },
+      { pageName: 'parameters_attendances_page', icon: 'fas fa-user-times' },
+      { pageName: 'parameters_sms_page', icon: 'fas fa-mobile' },
+      { pageName: 'parameters_super_admin_page', icon: 'fas fa-user-gear' },
+    ])
 
     if (children.length > 0) {
       return this.createGroup('parameters', undefined, children)

+ 4 - 0
services/layout/menuComposer.ts

@@ -1,5 +1,6 @@
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability'
+import type { Router } from 'vue-router'
 import MainMenuBuilder from '~/services/layout/menuBuilder/mainMenuBuilder'
 import type {
   AccessProfile,
@@ -36,6 +37,7 @@ export default class MenuComposer {
    * @param ability
    * @param organizationProfile
    * @param accessProfile
+   * @param router
    * @param layoutState
    */
   public static build(
@@ -43,6 +45,7 @@ export default class MenuComposer {
     ability: AnyAbility,
     organizationProfile: organizationState,
     accessProfile: AccessProfile,
+    router: Router,
     layoutState: LayoutState,
   ) {
     for (const builderClass of MenuComposer.builders) {
@@ -52,6 +55,7 @@ export default class MenuComposer {
         ability,
         organizationProfile,
         accessProfile,
+        router,
       )
       const menuName = builder.getMenuName()
       const menu = builder.build()

+ 6 - 3
services/rights/abilityBuilder.ts

@@ -91,9 +91,12 @@ class AbilityBuilder {
           conditions = [conditions]
         }
 
-        if (this.hasConfigAbility(conditions as Array<Condition>, subject)) {
-          abilitiesByConfig.push({ action, subject })
-        }
+        const hasAbility = this.hasConfigAbility(
+          conditions as Array<Condition>,
+          subject,
+        )
+
+        abilitiesByConfig.push({ action, subject, inverted: !hasAbility })
       },
     )
 

+ 69 - 1
tests/units/services/layout/menuBuilder/abstractMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability'
+import type { Router } from 'vue-router'
 import AbstractMenuBuilder from '~/services/layout/menuBuilder/abstractMenuBuilder'
 import type { IconItem, MenuGroup, MenuItem, MenuItems } from '~/types/layout'
 import { MENU_LINK_TYPE } from '~/types/enum/layout'
@@ -34,6 +35,10 @@ class TestableAbstractMenuBuilder extends AbstractMenuBuilder {
   public buildSubmenu(menuBuilder: typeof AbstractMenuBuilder) {
     return super.buildSubmenu(menuBuilder)
   }
+
+  public makeChildren(items: Array<{ pageName: string; icon?: string }>) {
+    return super.makeChildren(items)
+  }
 }
 
 let runtimeConfig: RuntimeConfig
@@ -41,18 +46,22 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: TestableAbstractMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   menuBuilder = new TestableAbstractMenuBuilder(
     runtimeConfig,
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 
@@ -160,3 +169,62 @@ describe('buildSubmenu', () => {
     })
   })
 })
+
+describe('makeChildren', () => {
+  test('simple call', () => {
+    ability.can = vi.fn(() => true)
+    // @ts-ignore
+    router.resolve = vi.fn(() => {
+      return { href: 'foo' }
+    })
+
+    const children: MenuItems = menuBuilder.makeChildren([
+      { pageName: 'foo_page', icon: 'fas fa-dog' },
+    ])
+
+    expect(children).toEqual([
+      {
+        label: 'foo_page',
+        icon: { name: 'fas fa-dog' },
+        to: 'foo',
+        type: 0,
+        active: false,
+      },
+    ])
+  })
+  test('no icon', () => {
+    ability.can = vi.fn(() => true)
+    // @ts-ignore
+    router.resolve = vi.fn(() => {
+      return { href: 'foo' }
+    })
+
+    const children: MenuItems = menuBuilder.makeChildren([
+      { pageName: 'foo_page' },
+    ])
+
+    expect(children).toEqual([
+      { label: 'foo_page', icon: undefined, to: 'foo', type: 0, active: false },
+    ])
+  })
+  test('not allowed', () => {
+    ability.can = vi.fn(() => false)
+
+    const children: MenuItems = menuBuilder.makeChildren([
+      { pageName: 'foo_page' },
+    ])
+
+    expect(children).toEqual([])
+  })
+  test('unknown page name', () => {
+    ability.can = vi.fn(() => true)
+    // @ts-ignore
+    router.resolve = vi.fn(() => {
+      return null
+    })
+
+    expect(() =>
+      menuBuilder.makeChildren([{ pageName: 'foo_page' }]),
+    ).toThrowError()
+  })
+})

+ 6 - 1
tests/units/services/layout/menuBuilder/accessMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect, beforeEach, vi } from 'vitest'
+import { describe, test, expect, beforeEach, vi } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import AccessMenuBuilder from '~/services/layout/menuBuilder/accessMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: AccessMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 31 - 0
tests/units/services/layout/menuBuilder/accountMenuBuilder.test.ts

@@ -1,6 +1,7 @@
 import { describe, expect, test, beforeEach, vi } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import AccountMenuBuilder from '~/services/layout/menuBuilder/accountMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -12,12 +13,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: AccountMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as any as AccessProfile
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
   accessProfile.id = 123
@@ -27,6 +31,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 
@@ -39,6 +44,10 @@ describe('getMenuName', () => {
 describe('build', () => {
   test('has all items (mister)', () => {
     ability.can = vi.fn(() => true)
+    // @ts-ignore
+    router.resolve = vi.fn(() => {
+      return { href: 'foo ' }
+    })
 
     // Should return a MenuGroup
     const result = menuBuilder.build() as MenuGroup
@@ -286,6 +295,28 @@ describe('build', () => {
     })
   })
 
+  test('has only rights for menu subscription', () => {
+    ability.can = vi.fn(
+      (action: string, subject: string) =>
+        action === 'display' && subject === 'subscription_page',
+    )
+    // @ts-ignore
+    router.resolve = vi.fn(() => {
+      return { href: 'subscription' }
+    })
+
+    // @ts-ignore
+    expect(menuBuilder.build().children[0]).toEqual({
+      label: 'subscription_page',
+      icon: undefined,
+      to: 'subscription',
+      type: MENU_LINK_TYPE.INTERNAL,
+      active: false,
+    })
+
+    expect(router.resolve).toHaveBeenCalledWith({ name: 'subscription_page' })
+  })
+
   test('has only rights for menu my_bills', () => {
     ability.can = vi.fn(
       (action: string, subject: string) =>

+ 6 - 1
tests/units/services/layout/menuBuilder/admin2iosMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect, beforeEach, vi } from 'vitest'
+import { describe, test, expect, beforeEach, vi } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import Admin2iosMenuBuilder from '~/services/layout/menuBuilder/admin2iosMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: Admin2iosMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 1
tests/units/services/layout/menuBuilder/agendaMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import AgendaMenuBuilder from '~/services/layout/menuBuilder/agendaMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: AgendaMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 1
tests/units/services/layout/menuBuilder/billingMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import BillingMenuBuilder from '~/services/layout/menuBuilder/billingMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: BillingMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 1
tests/units/services/layout/menuBuilder/communicationMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import CommunicationMenuBuilder from '~/services/layout/menuBuilder/communicationMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: CommunicationMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 1
tests/units/services/layout/menuBuilder/configurationMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, vi, expect, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import ConfigurationMenuBuilder from '~/services/layout/menuBuilder/configurationMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: ConfigurationMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 1
tests/units/services/layout/menuBuilder/cotisationsMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import CotisationsMenuBuilder from '~/services/layout/menuBuilder/cotisationsMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: CotisationsMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 7 - 2
tests/units/services/layout/menuBuilder/donorsMenuBuilder.test.ts

@@ -1,9 +1,10 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import DonorsMenuBuilder from '~/services/layout/menuBuilder/donorsMenuBuilder'
-import type { MenuGroup, MenuItem } from '~/types/layout'
+import type { MenuItem } from '~/types/layout'
 import { MENU_LINK_TYPE } from '~/types/enum/layout'
 
 let runtimeConfig: RuntimeConfig
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: DonorsMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 1
tests/units/services/layout/menuBuilder/educationalMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import EducationalMenuBuilder from '~/services/layout/menuBuilder/educationalMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: EducationalMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 7 - 2
tests/units/services/layout/menuBuilder/equipmentMenuBuilder.test.ts

@@ -1,9 +1,10 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import EquipmentMenuBuilder from '~/services/layout/menuBuilder/equipmentMenuBuilder'
-import type { MenuGroup, MenuItem } from '~/types/layout'
+import type { MenuItem } from '~/types/layout'
 import { MENU_LINK_TYPE } from '~/types/enum/layout'
 
 let runtimeConfig: RuntimeConfig
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: EquipmentMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 1
tests/units/services/layout/menuBuilder/mainMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import MainMenuBuilder from '~/services/layout/menuBuilder/mainMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -24,12 +25,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: MainMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -38,6 +42,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 7 - 2
tests/units/services/layout/menuBuilder/myAccessesMenuBuilder.test.ts

@@ -1,9 +1,10 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import MyAccessesMenuBuilder from '~/services/layout/menuBuilder/myAccessesMenuBuilder'
-import type { MenuGroup, MenuItem } from '~/types/layout'
+import type { MenuGroup } from '~/types/layout'
 import { MENU_LINK_TYPE } from '~/types/enum/layout'
 
 let runtimeConfig: RuntimeConfig
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: MyAccessesMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 1
tests/units/services/layout/menuBuilder/myFamilyMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, expect, test } from 'vitest'
+import { describe, expect, test, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import MyFamilyMenuBuilder from '~/services/layout/menuBuilder/myFamilyMenuBuilder'
 import { GENDER } from '~/types/enum/enums'
@@ -12,12 +13,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: MyFamilyMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -26,6 +30,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 94 - 0
tests/units/services/layout/menuBuilder/parametersMenuBuilder.test.ts

@@ -0,0 +1,94 @@
+import { AssertionError } from 'node:assert'
+import { describe, test, expect, beforeEach, vi } from 'vitest'
+import type { RuntimeConfig } from '@nuxt/schema'
+import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
+import type { AccessProfile, organizationState } from '~/types/interfaces'
+import ParametersMenuBuilder from '~/services/layout/menuBuilder/parametersMenuBuilder'
+import type { IconItem, MenuGroup, MenuItem, MenuItems } from '~/types/layout'
+
+class TestableParametersMenuBuilder extends ParametersMenuBuilder {
+  public createGroup(
+    label: string,
+    icon?: IconItem,
+    children: MenuItems = [],
+    actions: Array<MenuItem> = [],
+  ): MenuGroup {
+    return super.createGroup(label, icon, children, actions)
+  }
+
+  public makeChildren(items: Array<{ pageName: string; icon?: string }>) {
+    return super.makeChildren(items)
+  }
+}
+
+let runtimeConfig: RuntimeConfig
+let ability: AnyAbility
+let organizationProfile: organizationState
+let accessProfile: AccessProfile
+let menuBuilder: TestableParametersMenuBuilder
+let router: Router
+
+beforeEach(() => {
+  runtimeConfig = vi.fn() as any as RuntimeConfig
+  ability = vi.fn() as any as AnyAbility
+  organizationProfile = vi.fn() as any as organizationState
+  accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
+
+  runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
+
+  menuBuilder = new TestableParametersMenuBuilder(
+    runtimeConfig,
+    ability,
+    organizationProfile,
+    accessProfile,
+    router,
+  )
+})
+
+describe('getMenuName', () => {
+  test('validate name', () => {
+    expect(menuBuilder.getMenuName()).toEqual('Parameters')
+  })
+})
+
+describe('build', () => {
+  test('simple call', () => {
+    // @ts-ignore
+    menuBuilder.makeChildren = vi.fn((items) => {
+      if (items.length !== 11) {
+        throw new AssertionError()
+      }
+      return ['foo', 'bar']
+    })
+
+    // @ts-ignore
+    menuBuilder.createGroup = vi.fn(() => {
+      return { children: ['foo', 'bar'] }
+    })
+    const result = menuBuilder.build()
+
+    expect(result).toEqual({ children: ['foo', 'bar'] })
+    expect(menuBuilder.makeChildren).toHaveBeenCalledOnce()
+    expect(menuBuilder.createGroup).toHaveBeenCalledOnce()
+  })
+
+  test('has no items', () => {
+    // @ts-ignore
+    menuBuilder.makeChildren = vi.fn((items) => {
+      if (items.length !== 11) {
+        throw new AssertionError()
+      }
+      return []
+    })
+    menuBuilder.createGroup = vi.fn()
+
+    const result = menuBuilder.build()
+
+    expect(result).toEqual(null)
+    expect(menuBuilder.makeChildren).toHaveBeenCalledOnce()
+    expect(menuBuilder.createGroup).toHaveBeenCalledTimes(0)
+  })
+})

+ 0 - 104
tests/units/services/layout/menuBuilder/parametersMenuBuilder.test.ts.off

@@ -1,104 +0,0 @@
-// TODO: Supprimer l'extension off quand la composition du menu paramètres sera définitive
-import { describe, test, it, expect, beforeEach, vi } from 'vitest'
-import type {RuntimeConfig} from "@nuxt/schema";
-import type {AnyAbility} from "@casl/ability/dist/types";
-import type {AccessProfile, organizationState} from "~/types/interfaces";
-import ParametersMenuBuilder from "~/services/layout/menuBuilder/parametersMenuBuilder";
-import type {MenuGroup} from "~/types/layout";
-import {MENU_LINK_TYPE} from "~/types/enum/layout";
-
-let runtimeConfig: RuntimeConfig
-let ability: AnyAbility
-let organizationProfile: organizationState
-let accessProfile: AccessProfile
-let menuBuilder: ParametersMenuBuilder
-
-beforeEach(()=> {
-    runtimeConfig = vi.fn() as any as RuntimeConfig
-    ability = vi.fn() as any as AnyAbility
-    organizationProfile = vi.fn() as any as organizationState
-    accessProfile = vi.fn() as any as AccessProfile
-
-    runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
-
-    menuBuilder = new ParametersMenuBuilder(runtimeConfig, ability, organizationProfile, accessProfile)
-})
-
-describe('getMenuName', () => {
-    test('validate name', () => {
-        expect(menuBuilder.getMenuName()).toEqual("Parameters")
-    })
-})
-
-describe('build', () => {
-    test('has all items', () => {
-        ability.can = vi.fn(() => true)
-
-        // Should return a MenuGroup
-        const result = menuBuilder.build() as MenuGroup
-
-        expect(result.label).toEqual('parameters')
-        expect(result.icon).toEqual(undefined)
-        // @ts-ignore
-        expect(result.children.length).toEqual(6)
-    })
-
-    test('has no items', () => {
-        ability.can = vi.fn(() => false)
-        expect(menuBuilder.build()).toEqual(null)
-    })
-
-    test('has only rights for menu general_params', () => {
-        ability.can = vi.fn((action: string, subject: string) => action === 'display' && subject === 'parameters_page')
-
-        // @ts-ignore
-        expect(menuBuilder.build().children[0]).toEqual(
-            {label: 'general_params', icon: {name: 'fas fa-cogs'}, to: 'https://mydomain.com/#/parameters', type: MENU_LINK_TYPE.V1, active: false}
-        )
-    })
-
-    test('has only rights for menu communication_params', () => {
-        ability.can = vi.fn((action: string, subject: string) => action === 'display' && subject === 'parameters_communication_page')
-
-        // @ts-ignore
-        expect(menuBuilder.build().children[0]).toEqual(
-            {label: 'communication_params', icon: {name: 'fas fa-comments'}, to: 'https://mydomain.com/#/parameters/communication', type: MENU_LINK_TYPE.V1, active: false}
-        )
-    })
-
-    test('has only rights for menu students_params', () => {
-        ability.can = vi.fn((action: string, subject: string) => action === 'display' && subject === 'parameters_student_page')
-
-        // @ts-ignore
-        expect(menuBuilder.build().children[0]).toEqual(
-            {label: 'students_params', icon: {name: 'fas fa-users'}, to: 'https://mydomain.com/#/parameters/student', type: MENU_LINK_TYPE.V1, active: false}
-        )
-    })
-
-    test('has only rights for menu education_params', () => {
-        ability.can = vi.fn((action: string, subject: string) => action === 'display' && subject === 'parameters_education_page')
-
-        // @ts-ignore
-        expect(menuBuilder.build().children[0]).toEqual(
-            {label: 'education_params', icon: {name: 'fas fa-graduation-cap'}, to: 'https://mydomain.com/#/parameters/education', type: MENU_LINK_TYPE.V1, active: false}
-        )
-    })
-
-    test('has only rights for menu bills_params', () => {
-        ability.can = vi.fn((action: string, subject: string) => action === 'display' && subject === 'parameters_bills_page')
-
-        // @ts-ignore
-        expect(menuBuilder.build().children[0]).toEqual(
-            {label: 'bills_params', icon: {name: 'fas fa-euro-sign'}, to: 'https://mydomain.com/#/parameters/billing', type: MENU_LINK_TYPE.V1, active: false}
-        )
-    })
-
-    test('has only rights for menu secure_params', () => {
-        ability.can = vi.fn((action: string, subject: string) => action === 'display' && subject === 'parameters_secure_page')
-
-        // @ts-ignore
-        expect(menuBuilder.build().children[0]).toEqual(
-            {label: 'secure_params', icon: {name: 'fas fa-lock'}, to: 'https://mydomain.com/#/parameters/secure', type: MENU_LINK_TYPE.V1, active: false}
-        )
-    })
-})

+ 0 - 56
tests/units/services/layout/menuBuilder/rewardsMenuBuilder.test.ts

@@ -1,56 +0,0 @@
-import { describe, test, it, expect } from 'vitest'
-import type { RuntimeConfig } from '@nuxt/schema'
-import type { AnyAbility } from '@casl/ability/dist/types'
-import type { AccessProfile, organizationState } from '~/types/interfaces'
-import RewardsMenuBuilder from '~/services/layout/menuBuilder/rewardsMenuBuilder'
-import type { MenuGroup, MenuItem } from '~/types/layout'
-import { MENU_LINK_TYPE } from '~/types/enum/layout'
-
-let runtimeConfig: RuntimeConfig
-let ability: AnyAbility
-let organizationProfile: organizationState
-let accessProfile: AccessProfile
-let menuBuilder: RewardsMenuBuilder
-
-beforeEach(() => {
-  runtimeConfig = vi.fn() as any as RuntimeConfig
-  ability = vi.fn() as any as AnyAbility
-  organizationProfile = vi.fn() as any as organizationState
-  accessProfile = vi.fn() as any as AccessProfile
-
-  runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
-
-  menuBuilder = new RewardsMenuBuilder(
-    runtimeConfig,
-    ability,
-    organizationProfile,
-    accessProfile,
-  )
-})
-
-describe('getMenuName', () => {
-  test('validate name', () => {
-    expect(menuBuilder.getMenuName()).toEqual('AccessRewards')
-  })
-})
-
-describe('build', () => {
-  test('has all items', () => {
-    ability.can = vi.fn(() => true)
-
-    // Should return a MenuGroup
-    const result = menuBuilder.build() as MenuGroup
-
-    expect(result.label).toEqual('access_rewards')
-    expect(result.icon).toEqual({
-      name: 'fas fa-trophy',
-    })
-    // @ts-ignore
-    expect(result.children.length).toEqual(3)
-  })
-
-  test('has no items', () => {
-    ability.can = vi.fn(() => false)
-    expect(menuBuilder.build()).toEqual(null)
-  })
-})

+ 6 - 1
tests/units/services/layout/menuBuilder/statsMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import StatsMenuBuilder from '~/services/layout/menuBuilder/statsMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: StatsMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 2
tests/units/services/layout/menuBuilder/websiteAdminMenuBuilder.test.ts

@@ -1,9 +1,9 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import WebsiteAdminMenuBuilder from '~/services/layout/menuBuilder/websiteAdminMenuBuilder'
-import type { MenuGroup } from '~/types/layout'
 import { MENU_LINK_TYPE } from '~/types/enum/layout'
 
 let runtimeConfig: RuntimeConfig
@@ -11,12 +11,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: WebsiteAdminMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = vi.fn() as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +28,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 6 - 1
tests/units/services/layout/menuBuilder/websiteListMenuBuilder.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import type { AccessProfile, organizationState } from '~/types/interfaces'
 import WebsiteListMenuBuilder from '~/services/layout/menuBuilder/websiteListMenuBuilder'
 import type { MenuGroup } from '~/types/layout'
@@ -11,12 +12,15 @@ let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
 let menuBuilder: WebsiteListMenuBuilder
+let router: Router
 
 beforeEach(() => {
   runtimeConfig = vi.fn() as any as RuntimeConfig
   ability = vi.fn() as any as AnyAbility
   organizationProfile = {} as any as organizationState
   accessProfile = vi.fn() as any as AccessProfile
+  // @ts-ignore
+  router = vi.fn() as Router
 
   runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
@@ -25,6 +29,7 @@ beforeEach(() => {
     ability,
     organizationProfile,
     accessProfile,
+    router,
   )
 })
 

+ 9 - 1
tests/units/services/layout/menuComposer.test.ts

@@ -1,6 +1,7 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect, vi } from 'vitest'
 import type { RuntimeConfig } from '@nuxt/schema'
 import type { AnyAbility } from '@casl/ability/dist/types'
+import type { Router } from 'vue-router'
 import MenuComposer from '~/services/layout/menuComposer'
 import type {
   AccessProfile,
@@ -22,6 +23,8 @@ describe('build', () => {
     const accessProfile = vi.fn() as AccessProfile
     // @ts-ignore
     const layoutState = vi.fn() as LayoutState
+    // @ts-ignore
+    const router = vi.fn() as Router
 
     layoutState.menus = {}
     layoutState.menusOpened = {}
@@ -70,6 +73,7 @@ describe('build', () => {
       ability,
       organizationProfile,
       accessProfile,
+      router,
       layoutState,
     )
 
@@ -79,6 +83,7 @@ describe('build', () => {
       ability,
       organizationProfile,
       accessProfile,
+      router,
     )
     expect(dummyBuilder1.build).toHaveBeenCalledOnce()
 
@@ -88,6 +93,7 @@ describe('build', () => {
       ability,
       organizationProfile,
       accessProfile,
+      router,
     )
     expect(dummyBuilder2.build).toHaveBeenCalledOnce()
 
@@ -97,6 +103,7 @@ describe('build', () => {
       ability,
       organizationProfile,
       accessProfile,
+      router,
     )
     expect(dummyBuilder3.build).toHaveBeenCalledOnce()
 
@@ -106,6 +113,7 @@ describe('build', () => {
       ability,
       organizationProfile,
       accessProfile,
+      router,
     )
     expect(dummyBuilder4.build).toHaveBeenCalledOnce()
 

+ 3 - 3
tests/units/services/rights/abilityBuilder.test.ts

@@ -1,4 +1,4 @@
-import { describe, test, expect } from 'vitest'
+import { describe, test, expect, vi, beforeEach } from 'vitest'
 import type { MongoAbility } from '@casl/ability/dist/types/Ability'
 import type {
   AbilitiesType,
@@ -110,8 +110,8 @@ describe('buildAbilitiesFromConfig', () => {
     abilityBuilder.hasConfigAbility = vi.fn(() => true)
 
     expect(abilityBuilder.buildAbilitiesFromConfig()).toEqual([
-      { action: 'read', subject: 'subject1' },
-      { action: 'read', subject: 'subject2' },
+      { action: 'read', subject: 'subject1', inverted: false },
+      { action: 'read', subject: 'subject2', inverted: false },
     ])
   })
 })