浏览代码

Merge branch 'release/1.1.0'

Olivier Massot 3 年之前
父节点
当前提交
fd81a18e7c

+ 8 - 8
.gitlab-ci.yml

@@ -35,8 +35,8 @@ chrome:
   artifacts:
   artifacts:
     when: on_failure
     when: on_failure
     paths:
     paths:
-      - test/cypress/videos/**/*.mp4
-      - test/cypress/screenshots/**/*.png
+      - tests/cypress/videos/**/*.mp4
+      - tests/cypress/screenshots/**/*.png
     expire_in: 3 day
     expire_in: 3 day
 
 
 chrome-mobile:
 chrome-mobile:
@@ -48,8 +48,8 @@ chrome-mobile:
   artifacts:
   artifacts:
     when: on_failure
     when: on_failure
     paths:
     paths:
-      - test/cypress/videos/**/*.mp4
-      - test/cypress/screenshots/**/*.png
+      - tests/cypress/videos/**/*.mp4
+      - tests/cypress/screenshots/**/*.png
     expire_in: 3 day
     expire_in: 3 day
 
 
 firefox:
 firefox:
@@ -61,8 +61,8 @@ firefox:
   artifacts:
   artifacts:
     when: on_failure
     when: on_failure
     paths:
     paths:
-      - test/cypress/videos/**/*.mp4
-      - test/cypress/screenshots/**/*.png
+      - tests/cypress/videos/**/*.mp4
+      - tests/cypress/screenshots/**/*.png
     expire_in: 3 day
     expire_in: 3 day
 
 
 firefox-mobile:
 firefox-mobile:
@@ -74,6 +74,6 @@ firefox-mobile:
   artifacts:
   artifacts:
     when: on_failure
     when: on_failure
     paths:
     paths:
-      - test/cypress/videos/**/*.mp4
-      - test/cypress/screenshots/**/*.png
+      - tests/cypress/videos/**/*.mp4
+      - tests/cypress/screenshots/**/*.png
     expire_in: 3 day
     expire_in: 3 day

+ 12 - 2
README.md

@@ -27,13 +27,13 @@ Si le fichier .env n'existe pas, on créé un symlink vers le .env.xxx voulu sou
     ln -s .env.xxx .env
     ln -s .env.xxx .env
 
 
 Le projet frames n'utilisant pas le SSR, on n'a pas besoin de PM2 pour faire tourner un service.
 Le projet frames n'utilisant pas le SSR, on n'a pas besoin de PM2 pour faire tourner un service.
-On lance la commande:
+On lance la commande :
 
 
     yarn deploy
     yarn deploy
 
 
 > Cette commande est un alias qui équivaut à lancer les commandes suivantes: `git pull && yarn install && yarn build`
 > Cette commande est un alias qui équivaut à lancer les commandes suivantes: `git pull && yarn install && yarn build`
 
 
-S'il n'existe pas encore, il faut aussi créer un fichier dist/.htaccess contenant les lignes suivantes:
+S'il n'existe pas encore, il faut aussi créer un fichier dist/.htaccess contenant les lignes suivantes :
 
 
     <ifModule mod_rewrite.c>
     <ifModule mod_rewrite.c>
       RewriteEngine On
       RewriteEngine On
@@ -42,6 +42,16 @@ S'il n'existe pas encore, il faut aussi créer un fichier dist/.htaccess contena
       RewriteRule (.*) index.html [QSA,L]
       RewriteRule (.*) index.html [QSA,L]
     </ifModule>
     </ifModule>
 
 
+## Revenir à une version précédente
+
+Pour revenir à une version précédente:
+
+    git reset --hard <id du commit>
+    yarn build
+
+> **/!\ Non testé: ** Si la version précédente implique des version différentes des node_modules, il faudra sans doute supprimer le 
+> répertoire node_modules et le fichier yarn.lock, et relancer un `yarn install`
+
 ## Run tests
 ## Run tests
 
 
 To run unit tests:
 To run unit tests:

+ 9 - 8
components/Ui/Map/Structures.vue

@@ -97,8 +97,8 @@ export default Vue.extend({
   async mounted () {
   async mounted () {
     const defaultCenter: L.LatLngTuple = [46.71, 1.94]
     const defaultCenter: L.LatLngTuple = [46.71, 1.94]
     const defaultZoom: number = 5.5
     const defaultZoom: number = 5.5
-    const layerSource: string = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png'
-    const attribution: string = '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
+    const layerSource: string = 'https://{s}.tile.osm.org/{z}/{x}/{y}.png'
+    const attribution: string = '&copy; <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
 
 
     const options: L.MapOptions = { scrollWheelZoom: false, zoomSnap: 0.25 }
     const options: L.MapOptions = { scrollWheelZoom: false, zoomSnap: 0.25 }
 
 
@@ -133,10 +133,10 @@ export default Vue.extend({
       // populate new layer
       // populate new layer
       const clusters = L.markerClusterGroup()
       const clusters = L.markerClusterGroup()
       for (const s of this.structures) {
       for (const s of this.structures) {
-        if (!s.latitude || !s.longitude) { continue }
+        if (!s.mapAddress || !s.mapAddress.latitude || !s.mapAddress.longitude) { continue }
 
 
-        const marker = L.marker([s.latitude, s.longitude])
-        marker.bindPopup(`<b>${s.name}</b><br/>${s.postalCode} ${s.addressCity}<br/><a href="${s.website}" target="_blank">${s.website}</a>`)
+        const marker = L.marker([s.mapAddress.latitude, s.mapAddress.longitude])
+        marker.bindPopup(`<b>${s.name}</b><br/>${s.mapAddress.postalCode} ${s.mapAddress.addressCity}<br/><a href="${s.website}" target="_blank">${s.website}</a>`)
         clusters.addLayer(marker)
         clusters.addLayer(marker)
       }
       }
       this.map.addLayer(clusters)
       this.map.addLayer(clusters)
@@ -167,12 +167,13 @@ export default Vue.extend({
       const coords: L.LatLngBoundsLiteral = this.structures.filter(
       const coords: L.LatLngBoundsLiteral = this.structures.filter(
         (s: Structure) => {
         (s: Structure) => {
           return (
           return (
-            s.latitude && typeof s.latitude !== 'undefined' &&
-            s.longitude && typeof s.latitude !== 'undefined'
+            s.mapAddress !== null &&
+            s.mapAddress.latitude && typeof s.mapAddress.latitude !== 'undefined' &&
+            s.mapAddress.longitude && typeof s.mapAddress.latitude !== 'undefined'
           )
           )
         }
         }
       ).map(
       ).map(
-        (s: Structure) => { return [s.latitude as number, s.longitude as number] as L.LatLngTuple }
+        (s: Structure) => { return [(s.mapAddress as Address).latitude as number, (s.mapAddress as Address).longitude as number] as L.LatLngTuple }
       )
       )
 
 
       if (!(coords.length > 0)) {
       if (!(coords.length > 0)) {

+ 2 - 2
components/Ui/Search/Address.vue

@@ -91,10 +91,10 @@ export default Vue.extend({
   },
   },
   data () {
   data () {
     return {
     return {
-      model: null as Address | null,
+      model: null as UiSearchAddressItem | null,
       search: null,
       search: null,
       features: [] as Array<Feature>,
       features: [] as Array<Feature>,
-      items: [] as Array<Address>,
+      items: [] as Array<UiSearchAddressItem>,
       loading: false,
       loading: false,
       errorMsg: '',
       errorMsg: '',
       geolocalized: false
       geolocalized: false

+ 6 - 6
cypress.json

@@ -1,11 +1,11 @@
 {
 {
   "baseUrl": "http://localhost:3004",
   "baseUrl": "http://localhost:3004",
-  "fixturesFolder": "test/cypress/fixtures",
-  "integrationFolder": "test/cypress/integration",
-  "pluginsFile": "test/cypress/plugins/index.js",
-  "screenshotsFolder": "test/cypress/screenshots",
-  "supportFile": "test/cypress/support/index.js",
-  "videosFolder": "test/cypress/videos",
+  "fixturesFolder": "tests/cypress/fixtures",
+  "integrationFolder": "tests/cypress/integration",
+  "pluginsFile": "tests/cypress/plugins/index.js",
+  "screenshotsFolder": "tests/cypress/screenshots",
+  "supportFile": "tests/cypress/support/index.js",
+  "videosFolder": "tests/cypress/videos",
   "env": {
   "env": {
     "apiUrl": "http://nginx"
     "apiUrl": "http://nginx"
   },
   },

+ 10 - 4
jest.config.js

@@ -3,7 +3,8 @@ module.exports = {
   moduleNameMapper: {
   moduleNameMapper: {
     '^@/(.*)$': '<rootDir>/$1',
     '^@/(.*)$': '<rootDir>/$1',
     '^~/(.*)$': '<rootDir>/$1',
     '^~/(.*)$': '<rootDir>/$1',
-    '^vue$': 'vue/dist/vue.common.js'
+    '^vue$': 'vue/dist/vue.common.js',
+    '\\.(css|sass)$': 'identity-obj-proxy'
   },
   },
   moduleFileExtensions: [
   moduleFileExtensions: [
     'ts',
     'ts',
@@ -19,8 +20,13 @@ module.exports = {
   collectCoverage: true,
   collectCoverage: true,
   collectCoverageFrom: [
   collectCoverageFrom: [
     '<rootDir>/components/**/*.vue',
     '<rootDir>/components/**/*.vue',
-    '<rootDir>/pages/**/*.vue'
+    '<rootDir>/pages/**/*.vue',
+    '<rootDir>/services/**/*.ts'
   ],
   ],
-  testEnvironment: 'jsdom',
-  setupFiles: ['<rootDir>/tests/unit/index.ts']
+  setupFiles: ['<rootDir>/tests/unit/jest.setup.ts'],
+  testPathIgnorePatterns: [
+    'node_modules',
+    'tests/cypress'
+  ],
+  testEnvironment: 'jsdom'
 }
 }

+ 1 - 0
lang/fr-FR.js

@@ -34,6 +34,7 @@ export default (_context, _locale) => {
     geolocation_not_supported: 'La géolocalisation n\'est pas supportée par votre navigateur',
     geolocation_not_supported: 'La géolocalisation n\'est pas supportée par votre navigateur',
     show_tel: 'Montrer le numéro de téléphone',
     show_tel: 'Montrer le numéro de téléphone',
     show_email: 'Montrer l\'adresse e-mail',
     show_email: 'Montrer l\'adresse e-mail',
+    see_national_network: 'Voir le réseau national',
     BIG_BAND: 'Big band',
     BIG_BAND: 'Big band',
     BRASS_BAND: 'Brass band',
     BRASS_BAND: 'Brass band',
     ORCHESTRA_CLASS: "Classe d'orchestre",
     ORCHESTRA_CLASS: "Classe d'orchestre",

+ 1 - 1
nuxt.config.js

@@ -84,7 +84,7 @@ export default {
     theme: {
     theme: {
       themes: {
       themes: {
         light: {
         light: {
-          primary: '#0aa5ec'
+          primary: '#e4611b'
         }
         }
       },
       },
       options: {
       options: {

+ 2 - 0
package.json

@@ -39,6 +39,7 @@
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@babel/eslint-parser": "^7.14.7",
     "@babel/eslint-parser": "^7.14.7",
+    "@nuxt/test-utils": "^0.2.2",
     "@nuxt/types": "^2.15.7",
     "@nuxt/types": "^2.15.7",
     "@nuxt/typescript-build": "^2.1.0",
     "@nuxt/typescript-build": "^2.1.0",
     "@nuxtjs/eslint-config-typescript": "^6.0.1",
     "@nuxtjs/eslint-config-typescript": "^6.0.1",
@@ -53,6 +54,7 @@
     "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-nuxt": "^2.0.0",
     "eslint-plugin-nuxt": "^2.0.0",
     "eslint-plugin-vue": "^7.12.1",
     "eslint-plugin-vue": "^7.12.1",
+    "identity-obj-proxy": "^3.0.0",
     "jest": "^27.0.5",
     "jest": "^27.0.5",
     "ts-jest": "^27.0.3",
     "ts-jest": "^27.0.3",
     "vue-jest": "^3.0.4"
     "vue-jest": "^3.0.4"

+ 25 - 7
pages/structures/_id.vue

@@ -4,7 +4,7 @@
       <v-layout>
       <v-layout>
         <v-btn
         <v-btn
           v-if="parent"
           v-if="parent"
-          :to="{path: '/structures', query: { parent: parent, view: view, theme: theme }}"
+          :to="{path: '/structures', query: { parent: parent, view: view, theme: theme, hideTitle: hideTitle }}"
           nuxt
           nuxt
           plain
           plain
         >
         >
@@ -37,6 +37,9 @@
           <a v-if="structure.twitter" :href="structure.twitter" class="twitter" target="_blank" title="$t('find_us_on') + ' Twitter'">
           <a v-if="structure.twitter" :href="structure.twitter" class="twitter" target="_blank" title="$t('find_us_on') + ' Twitter'">
             <font-awesome-icon class="icon social-icon" :icon="['fab', 'twitter']" />
             <font-awesome-icon class="icon social-icon" :icon="['fab', 'twitter']" />
           </a>
           </a>
+          <a v-if="structure.youtube" :href="structure.youtube" class="youtube" target="_blank" title="$t('find_us_on') + ' Youtube'">
+            <font-awesome-icon class="icon social-icon" :icon="['fab', 'youtube']" />
+          </a>
         </div>
         </div>
       </v-layout>
       </v-layout>
 
 
@@ -74,7 +77,7 @@
           <v-btn
           <v-btn
             v-for="article in structure.articles"
             v-for="article in structure.articles"
             :key="article.id"
             :key="article.id"
-            :href="(article.link.match(/https?:\/\/.*/) ? '' : 'https://') + article.link "
+            :href="article.link ? ((article.link.match(/https?:\/\/.*/) ? '' : 'https://') + article.link) : ''"
             target="_blank"
             target="_blank"
             small
             small
             color="primary"
             color="primary"
@@ -118,14 +121,26 @@
           </div>
           </div>
 
 
           <table>
           <table>
-            <tr>
+            <tr
+              v-if="structure.mapAddress && structure.mapAddress.postalCode !== structure.postalAddress.postalCode && structure.mapAddress.streetAddress !== structure.postalAddress.streetAddress"
+            >
               <td>
               <td>
                 <font-awesome-icon class="icon" :icon="['fas', 'map-marker-alt']" />
                 <font-awesome-icon class="icon" :icon="['fas', 'map-marker-alt']" />
               </td>
               </td>
               <td class="mx-2">
               <td class="mx-2">
-                <span v-if="structure.streetAddress">{{ structure.streetAddress }}<br></span>
-                <span v-if="structure.postalCode">{{ structure.postalCode }} </span>
-                {{ structure.addressCity }}
+                <span v-if="structure.mapAddress.streetAddress" style="white-space: pre-line;">{{ structure.mapAddress.streetAddress }}<br></span>
+                <span v-if="structure.mapAddress.postalCode">{{ structure.mapAddress.postalCode }} </span>
+                {{ structure.mapAddress.addressCity }}
+              </td>
+            </tr>
+            <tr v-if="structure.postalAddress">
+              <td>
+                <font-awesome-icon class="icon" :icon="['fas', 'envelope']" />
+              </td>
+              <td class="mx-2">
+                <span v-if="structure.postalAddress.streetAddress" style="white-space: pre-line;">{{ structure.postalAddress.streetAddress }}<br></span>
+                <span v-if="structure.postalAddress.postalCode">{{ structure.postalAddress.postalCode }} </span>
+                {{ structure.postalAddress.addressCity }}
               </td>
               </td>
             </tr>
             </tr>
             <tr>
             <tr>
@@ -210,7 +225,9 @@
                 >
                 >
                   <l-popup>
                   <l-popup>
                     <b>{{ structure.name }}</b><br>
                     <b>{{ structure.name }}</b><br>
-                    {{ structure.postalCode }} {{ structure.addressCity }}<br>
+                    <span v-if="structure.mapAddress">
+                      {{ structure.mapAddress.postalCode }} {{ structure.mapAddress.addressCity }}
+                    </span><br>
                     <a :href="structure.website" target="_blank">{{ structure.website }}</a>
                     <a :href="structure.website" target="_blank">{{ structure.website }}</a>
                   </l-popup>
                   </l-popup>
                 </l-marker>
                 </l-marker>
@@ -242,6 +259,7 @@ export default Vue.extend({
       parent: this.$route.query.parent ? parseInt(this.$route.query.parent as string) : null,
       parent: this.$route.query.parent ? parseInt(this.$route.query.parent as string) : null,
       view: this.$route.query.view ?? 'map',
       view: this.$route.query.view ?? 'map',
       theme: this.$route.query.theme ?? 'orange',
       theme: this.$route.query.theme ?? 'orange',
+      hideTitle: this.$route.query.hideTitle ?? false,
       showTel: false,
       showTel: false,
       showMail: false
       showMail: false
     }
     }

+ 40 - 19
pages/structures/index.vue

@@ -5,9 +5,10 @@
     <!-- Header -->
     <!-- Header -->
     <v-row>
     <v-row>
       <v-layout>
       <v-layout>
-        <h2 class="flex">
+        <h2 v-if="!hideTitle">
           {{ $t("member_companies") }}
           {{ $t("member_companies") }}
         </h2>
         </h2>
+        <div class="flex" />
         <v-btn-toggle mandatory dense :value="mapview ? 0 : 1" @change="viewChanged">
         <v-btn-toggle mandatory dense :value="mapview ? 0 : 1" @change="viewChanged">
           <v-btn>
           <v-btn>
             {{ $t("map") }}
             {{ $t("map") }}
@@ -197,7 +198,7 @@
                     outlined
                     outlined
                     :class="'structure-card pa-3 d-flex ' + ((mapview || $vuetify.breakpoint.smAndDown) ? 'flex-column' : 'flex-row align-items-center')"
                     :class="'structure-card pa-3 d-flex ' + ((mapview || $vuetify.breakpoint.smAndDown) ? 'flex-column' : 'flex-row align-items-center')"
                   >
                   >
-                    <div class="d-flex justify-center max-w100" >
+                    <div class="d-flex justify-center max-w100">
                       <v-img
                       <v-img
                         v-if="structure.logoId"
                         v-if="structure.logoId"
                         :src="'https://api.opentalent.fr/app.php/_internal/secure/files/' + structure.logoId"
                         :src="'https://api.opentalent.fr/app.php/_internal/secure/files/' + structure.logoId"
@@ -208,11 +209,10 @@
                         :contain="true"
                         :contain="true"
                         style="margin: 12px;"
                         style="margin: 12px;"
                       />
                       />
-                      <div v-else style="height: 104px; width: 264px"></div>
+                      <div v-else style="height: 104px; width: 264px" />
                     </div>
                     </div>
 
 
                     <div :class="'d-flex flex-column' + (listview ? ' flex-grow-1' : '')">
                     <div :class="'d-flex flex-column' + (listview ? ' flex-grow-1' : '')">
-
                       <v-card-title class="title">
                       <v-card-title class="title">
                         <nuxt-link :to="{path: '/structures/' + structure.id, query: { parent: parent, view: view, theme: theme }}">
                         <nuxt-link :to="{path: '/structures/' + structure.id, query: { parent: parent, view: view, theme: theme }}">
                           {{ structure.name }}
                           {{ structure.name }}
@@ -221,14 +221,14 @@
 
 
                       <v-card-text class="infos">
                       <v-card-text class="infos">
                         <table>
                         <table>
-                          <tr>
+                          <tr v-if="structure.mapAddress">
                             <td>
                             <td>
                               <font-awesome-icon class="icon" :icon="['fas', 'map-marker-alt']" />
                               <font-awesome-icon class="icon" :icon="['fas', 'map-marker-alt']" />
                             </td>
                             </td>
                             <td>
                             <td>
-                              <span v-if="structure.streetAddress">{{ structure.streetAddress }}<br></span>
-                              <span v-if="structure.postalCode" class="postalCode">{{ structure.postalCode }} </span>
-                              {{ structure.addressCity }}
+                              <span v-if="structure.mapAddress.streetAddress" style="white-space: pre-line;">{{ structure.mapAddress.streetAddress }}<br></span>
+                              <span v-if="structure.mapAddress.postalCode" class="postalCode">{{ structure.mapAddress.postalCode }} </span>
+                              {{ structure.mapAddress.addressCity }}
                             </td>
                             </td>
                           </tr>
                           </tr>
                           <tr>
                           <tr>
@@ -274,7 +274,7 @@
                     <v-card-actions :class="listview ? 'align-self-end' : 'justify-end'">
                     <v-card-actions :class="listview ? 'align-self-end' : 'justify-end'">
                       <v-btn
                       <v-btn
                         class="see"
                         class="see"
-                        :to="{path: '/structures/' + structure.id, query: { parent: parent, view: view, theme: theme }}"
+                        :to="{path: '/structures/' + structure.id, query: { parent: parent, view: view, theme: theme, hideTitle: hideTitle }}"
                         nuxt
                         nuxt
                       >
                       >
                         <span style="margin-right: 6px;">{{ $t("see_more") }}</span>
                         <span style="margin-right: 6px;">{{ $t("see_more") }}</span>
@@ -298,6 +298,17 @@
         </div>
         </div>
       </v-col>
       </v-col>
     </v-row>
     </v-row>
+    <div class="d-flex w100 justify-center">
+      <v-btn
+        v-if="!parentIsCmf"
+        :href="cmfStructuresPageUrl"
+        target="_blank"
+        class="my-auto ma-4"
+      >
+        <font-awesome-icon :icon="['fas', 'external-link-alt']" class="mr-2" />
+        {{ $t('see_national_network') }}
+      </v-btn>
+    </div>
   </LayoutContainer>
   </LayoutContainer>
 </template>
 </template>
 
 
@@ -310,6 +321,7 @@ import sphericDistance from '@/services/utils/geo'
 import StructuresProvider from '~/services/data/StructuresProvider'
 import StructuresProvider from '~/services/data/StructuresProvider'
 
 
 const CMF_ID = 12097
 const CMF_ID = 12097
+const CMF_STRUCTURES_PAGE_URL = 'https://www.cmf-musique.org/la-cmf/la-cmf-en-region/cmf-en-region/'
 
 
 export default Vue.extend({
 export default Vue.extend({
   validate ({ query }) {
   validate ({ query }) {
@@ -330,6 +342,7 @@ export default Vue.extend({
       parent: parseInt(this.$route.query.parent as string),
       parent: parseInt(this.$route.query.parent as string),
       view: this.$route.query.view ?? 'map',
       view: this.$route.query.view ?? 'map',
       theme: this.$route.query.theme ?? 'orange',
       theme: this.$route.query.theme ?? 'orange',
+      hideTitle: this.$route.query.hideTitle === 'true',
       structures: [] as Array<Structure>,
       structures: [] as Array<Structure>,
       filteredStructures: [] as Array<Structure>,
       filteredStructures: [] as Array<Structure>,
       federations: [] as Array<{ id: number | null, name: string | null }>,
       federations: [] as Array<{ id: number | null, name: string | null }>,
@@ -371,6 +384,9 @@ export default Vue.extend({
     parentIsCmf (): Boolean {
     parentIsCmf (): Boolean {
       return this.parent === CMF_ID
       return this.parent === CMF_ID
     },
     },
+    cmfStructuresPageUrl (): string {
+      return CMF_STRUCTURES_PAGE_URL
+    },
     onMapFilteredStructures (): Array<Structure> {
     onMapFilteredStructures (): Array<Structure> {
       if (this.mapview && this.mapBoundsFilterStarted) {
       if (this.mapview && this.mapBoundsFilterStarted) {
         return this.filteredStructures.filter((s) => {
         return this.filteredStructures.filter((s) => {
@@ -469,12 +485,15 @@ export default Vue.extend({
      */
      */
     matchLocationFilter (structure: Structure): boolean {
     matchLocationFilter (structure: Structure): boolean {
       if (!this.locationFilter) { return true }
       if (!this.locationFilter) { return true }
-      if (!structure.latitude || !structure.longitude) { return false }
+      if (structure.mapAddress === null || !structure.mapAddress.latitude || !structure.mapAddress.longitude) { return false }
 
 
       const radius = Number(this.distanceFilter) ?? 5
       const radius = Number(this.distanceFilter) ?? 5
 
 
       return sphericDistance(
       return sphericDistance(
-        this.locationFilter.latitude, this.locationFilter.longitude, structure.latitude, structure.longitude
+        this.locationFilter.latitude,
+        this.locationFilter.longitude,
+        structure.mapAddress.latitude,
+        structure.mapAddress.longitude
       ) <= radius
       ) <= radius
     },
     },
     /**
     /**
@@ -493,10 +512,12 @@ export default Vue.extend({
      */
      */
     matchDepartmentFilter (structure: Structure): boolean {
     matchDepartmentFilter (structure: Structure): boolean {
       if (!this.departmentFilter) { return true }
       if (!this.departmentFilter) { return true }
-      return structure.postalCode !== null &&
+      return structure.mapAddress !== null &&
+        typeof structure.mapAddress.postalCode !== 'undefined' &&
+        structure.mapAddress.postalCode !== null &&
         (
         (
-          structure.postalCode.startsWith(this.departmentFilter) ||
-          (['2A', '2B'].includes(this.departmentFilter) && structure.postalCode.startsWith('20'))
+          structure.mapAddress.postalCode.startsWith(this.departmentFilter) ||
+          (['2A', '2B'].includes(this.departmentFilter) && structure.mapAddress.postalCode.startsWith('20'))
         )
         )
     },
     },
     /**
     /**
@@ -515,11 +536,11 @@ export default Vue.extend({
      */
      */
     matchMapBounds (structure: Structure): boolean {
     matchMapBounds (structure: Structure): boolean {
       if (!this.mapBoundsFilter) { return true }
       if (!this.mapBoundsFilter) { return true }
-      if (!(structure.latitude && structure.longitude)) { return false }
-      return this.mapBoundsFilter.getSouth() <= structure.latitude &&
-             structure.latitude <= this.mapBoundsFilter.getNorth() &&
-             this.mapBoundsFilter.getWest() <= structure.longitude &&
-             structure.longitude <= this.mapBoundsFilter.getEast()
+      if (structure.mapAddress === null || !(structure.mapAddress.latitude && structure.mapAddress.longitude)) { return false }
+      return this.mapBoundsFilter.getSouth() <= structure.mapAddress.latitude &&
+             structure.mapAddress.latitude <= this.mapBoundsFilter.getNorth() &&
+             this.mapBoundsFilter.getWest() <= structure.mapAddress.longitude &&
+             structure.mapAddress.longitude <= this.mapBoundsFilter.getEast()
     },
     },
     /**
     /**
      * Does the structure match each of the page filters
      * Does the structure match each of the page filters

+ 13 - 0
services/data/StructuresProvider.ts

@@ -8,10 +8,23 @@ class StructuresProvider extends BaseProvider {
     s.n4Id = s.n4Id ? parseInt(s.n4Id) : null
     s.n4Id = s.n4Id ? parseInt(s.n4Id) : null
     s.n5Id = s.n5Id ? parseInt(s.n5Id) : null
     s.n5Id = s.n5Id ? parseInt(s.n5Id) : null
     s.practices = s.practices ? s.practices.split(',') : []
     s.practices = s.practices ? s.practices.split(',') : []
+    s.addresses = ((s.addresses && s.addresses !== '{}') ? JSON.parse('[' + s.addresses + ']') : []) as Array<Address>
+
+    // Define the on-map address according to the chosen priorities
+    s.mapAddress = s.addresses.find((a: Address) => { return a.type === 'ADDRESS_PRACTICE' }) ||
+                   s.addresses.find((a: Address) => { return a.type === 'ADDRESS_HEAD_OFFICE' }) ||
+                   s.addresses.find((a: Address) => { return a.type === 'ADDRESS_CONTACT' }) ||
+                   null
+    // Define the postal address according to the chosen priorities
+    s.postalAddress = s.addresses.find((a: Address) => { return a.type === 'ADDRESS_CONTACT' }) ||
+                      s.addresses.find((a: Address) => { return a.type === 'ADDRESS_HEAD_OFFICE' }) ||
+                      null
+
     s.latitude = s.latitude ? parseFloat(s.latitude) : null
     s.latitude = s.latitude ? parseFloat(s.latitude) : null
     s.longitude = s.longitude ? parseFloat(s.longitude) : null
     s.longitude = s.longitude ? parseFloat(s.longitude) : null
     s.parents = s.parents ? s.parents.split(',').map((i: string) => Number(i)) : []
     s.parents = s.parents ? s.parents.split(',').map((i: string) => Number(i)) : []
     s.articles = (s.articles && s.articles !== '{}') ? JSON.parse('[' + s.articles + ']').reverse() : []
     s.articles = (s.articles && s.articles !== '{}') ? JSON.parse('[' + s.articles + ']').reverse() : []
+    s.articles.sort((a: Article, b: Article) => { return a.date > b.date ? -1 : 1 })
     return s
     return s
   }
   }
 
 

+ 0 - 10
store/README.md

@@ -1,10 +0,0 @@
-# STORE
-
-**This directory is not required, you can delete it if you don't want to use it.**
-
-This directory contains your Vuex Store files.
-Vuex Store option is implemented in the Nuxt.js framework.
-
-Creating a file in this directory automatically activates the option in the framework.
-
-More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).

+ 0 - 2
test/cypress/.gitignore

@@ -1,2 +0,0 @@
-/screenshots/
-/videos/

+ 0 - 21
test/unit/component/Ui/Search/Address.spec.js

@@ -1,21 +0,0 @@
-import { mount } from '@vue/test-utils'
-import Vuetify from 'vuetify'
-import Address from '~/components/Ui/Search/Address'
-
-let wrapper
-let vuetify
-beforeEach(() => {
-  vuetify = new Vuetify()
-  wrapper = mount(Address, {
-    vuetify
-  })
-})
-
-describe('components/ui/search/address', () => {
-  it('an input of 1+ cars shall display results', async () => {})
-  it('position shall be set when a result is clicked', async () => {})
-  it('position shall be cleared when input is cleared', async () => {})
-  it('first result shall be selected when enter key is pressed', async () => {})
-  it('user position shall be set when localize icon is clicked', async () => {})
-  it('error message shall be displayed if the browser does not support localization', async () => {})
-})

+ 0 - 6
test/unit/index.ts

@@ -1,6 +0,0 @@
-import Vuex from 'vuex'
-import Vue from 'vue'
-import Vuetify from 'vuetify'
-
-Vue.use(Vuetify)
-Vue.use(Vuex)

+ 0 - 9
test/unit/pages/structures/index.spec.js

@@ -1,9 +0,0 @@
-import Vuetify from 'vuetify'
-
-let vuetify
-beforeEach(() => {
-  vuetify = new Vuetify()
-})
-
-describe('pages/structures/index.vue', () => {
-})

+ 0 - 0
test/cypress/.eslintrc.json → tests/cypress/.eslintrc.json


+ 0 - 0
test/cypress/fixtures/structure-by-id.json → tests/cypress/fixtures/structure-by-id.json


+ 0 - 0
test/cypress/fixtures/structures.json → tests/cypress/fixtures/structures.json


+ 0 - 0
test/cypress/integration/structures/_id.spec.js → tests/cypress/integration/structures/_id.spec.js


+ 2 - 2
test/cypress/integration/structures/index.spec.js → tests/cypress/integration/structures/index.spec.js

@@ -119,7 +119,7 @@ describe('Test the /structures page', () => {
 
 
   it('The results shall be filtered according to the location filter', () => {
   it('The results shall be filtered according to the location filter', () => {
     cy.getByLabel('input:visible', 'Où ?').type('strasbourg')
     cy.getByLabel('input:visible', 'Où ?').type('strasbourg')
-    cy.get('.v-list-item__title').contains(/Strasbourg \(\d{5}\)/).click()
+    cy.get('.v-list-item__title').contains(/Strasbourg \(\d{5}\)/, { timeout: 8000 }).click()
     cy.get('button').contains('Rechercher').click()
     cy.get('button').contains('Rechercher').click()
 
 
     // we check that there is not a postal code that is not like '67xxx'
     // we check that there is not a postal code that is not like '67xxx'
@@ -172,7 +172,7 @@ describe('Test the /structures page', () => {
   it('Reinitialize shall clear each filter field, display all results, and reset map bounds (in both map and list view)', () => {
   it('Reinitialize shall clear each filter field, display all results, and reset map bounds (in both map and list view)', () => {
     cy.getByLabel('input', 'Quoi ?').type('some long text that will hopefully match no result')
     cy.getByLabel('input', 'Quoi ?').type('some long text that will hopefully match no result')
     cy.getByLabel('input:visible', 'Où ?').type('vesoul')
     cy.getByLabel('input:visible', 'Où ?').type('vesoul')
-    cy.get('.v-list-item div', { timeout: 10000 }).contains(/Vesoul \(\d{5}\)/).click()
+    cy.get('.v-list-item div').contains(/Vesoul \(\d{5}\)/, { timeout: 8000 }).click()
     cy.getByLabel('input:visible', 'Type').click()
     cy.getByLabel('input:visible', 'Type').click()
     cy.get('.v-list-item').contains('Big band').click()
     cy.get('.v-list-item').contains('Big band').click()
     cy.getByLabel('input:visible', 'Département').click()
     cy.getByLabel('input:visible', 'Département').click()

+ 0 - 0
test/cypress/plugins/index.js → tests/cypress/plugins/index.js


+ 0 - 0
test/cypress/support/commands.js → tests/cypress/support/commands.js


+ 0 - 0
test/cypress/support/index.js → tests/cypress/support/index.js


+ 0 - 0
test/cypress/support/leaflet.js → tests/cypress/support/leaflet.js


+ 0 - 0
test/cypress/support/vuetify.js → tests/cypress/support/vuetify.js


+ 0 - 0
test/unit/component/Ui/Map/Structures.spec.js → tests/unit/component/Ui/Map/Structures.spec.js


+ 42 - 0
tests/unit/component/Ui/Search/Address.spec.js

@@ -0,0 +1,42 @@
+import { mount } from '@vue/test-utils'
+import Vuetify from 'vuetify'
+import Address from '~/components/Ui/Search/Address'
+import response from '~/tests/unit/component/Ui/Search/fixtures/response'
+
+global.fetch = jest.fn(() =>
+  Promise.resolve({
+    json: () => Promise.resolve(
+      response
+    )
+  })
+)
+
+let wrapper
+let vuetify
+
+beforeEach(() => {
+  vuetify = new Vuetify()
+  fetch.mockClear()
+})
+
+describe('components/ui/search/address', () => {
+  it('an input of 1+ cars shall display results', async () => {
+    wrapper = mount(Address, {
+      vuetify
+    })
+
+    const addressSearchBar = wrapper.find('.v-autocomplete')
+    // addressSearchBar.element.search = 'paris'
+    // await addressSearchBar.trigger('click')
+    addressSearchBar.find('input[type="text"]').setValue('paris')
+
+    const resultsList = wrapper.find('v-autocomplete__content')
+    expect(resultsList.exists()).toBeTruthy()
+    expect(resultsList.find('div:contains("Paris")').exists()).toBeTruthy()
+  })
+  it('position shall be set when a result is clicked', async () => {})
+  it('position shall be cleared when input is cleared', async () => {})
+  it('first result shall be selected when enter key is pressed', async () => {})
+  it('user position shall be set when localize icon is clicked', async () => {})
+  it('error message shall be displayed if the browser does not support localization', async () => {})
+})

+ 264 - 0
tests/unit/component/Ui/Search/fixtures/response.js

@@ -0,0 +1,264 @@
+
+export default {
+  type: 'FeatureCollection',
+  version: 'draft',
+  features: [
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.347,
+          48.859
+        ]
+      },
+      properties: {
+        label: 'Paris',
+        score: 0.9704590909090908,
+        id: '75056',
+        type: 'municipality',
+        name: 'Paris',
+        postcode: '75001',
+        citycode: '75056',
+        x: 652089.7,
+        y: 6862305.26,
+        population: 2190327,
+        city: 'Paris',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.67505
+      }
+    },
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.295289,
+          48.841959
+        ]
+      },
+      properties: {
+        label: 'Paris 15e Arrondissement',
+        score: 0.8693736363636363,
+        id: '75115',
+        type: 'municipality',
+        name: 'Paris 15e Arrondissement',
+        postcode: '75015',
+        citycode: '75115',
+        x: 648278.87,
+        y: 6860443.09,
+        population: 233484,
+        city: 'Paris 15e Arrondissement',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.56311
+      }
+    },
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.397152,
+          48.863367
+        ]
+      },
+      properties: {
+        label: 'Paris 20e Arrondissement',
+        score: 0.8685690909090908,
+        id: '75120',
+        type: 'municipality',
+        name: 'Paris 20e Arrondissement',
+        postcode: '75020',
+        citycode: '75120',
+        x: 655772.96,
+        y: 6862761.58,
+        population: 195604,
+        city: 'Paris 20e Arrondissement',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.55426
+      }
+    },
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.348679,
+          48.892045
+        ]
+      },
+      properties: {
+        label: 'Paris 18e Arrondissement',
+        score: 0.8685563636363636,
+        id: '75118',
+        type: 'municipality',
+        name: 'Paris 18e Arrondissement',
+        postcode: '75018',
+        citycode: '75118',
+        x: 652243.19,
+        y: 6865978.62,
+        population: 195060,
+        city: 'Paris 18e Arrondissement',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.55412
+      }
+    },
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.387708,
+          48.887252
+        ]
+      },
+      properties: {
+        label: 'Paris 19e Arrondissement',
+        score: 0.86835,
+        id: '75119',
+        type: 'municipality',
+        name: 'Paris 19e Arrondissement',
+        postcode: '75019',
+        citycode: '75119',
+        x: 655100.72,
+        y: 6865422.77,
+        population: 186393,
+        city: 'Paris 19e Arrondissement',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.55185
+      }
+    },
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.365001,
+          48.830193
+        ]
+      },
+      properties: {
+        label: 'Paris 13e Arrondissement',
+        score: 0.868230909090909,
+        id: '75113',
+        type: 'municipality',
+        name: 'Paris 13e Arrondissement',
+        postcode: '75013',
+        citycode: '75113',
+        x: 653384.64,
+        y: 6859091.43,
+        population: 181552,
+        city: 'Paris 13e Arrondissement',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.55054
+      }
+    },
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.304844,
+          48.887702
+        ]
+      },
+      properties: {
+        label: 'Paris 17e Arrondissement',
+        score: 0.8678736363636362,
+        id: '75117',
+        type: 'municipality',
+        name: 'Paris 17e Arrondissement',
+        postcode: '75017',
+        citycode: '75117',
+        x: 649024.91,
+        y: 6865523.11,
+        population: 167835,
+        city: 'Paris 17e Arrondissement',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.54661
+      }
+    },
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.267032,
+          48.856305
+        ]
+      },
+      properties: {
+        label: 'Paris 16e Arrondissement',
+        score: 0.8678081818181818,
+        id: '75116',
+        type: 'municipality',
+        name: 'Paris 16e Arrondissement',
+        postcode: '75016',
+        citycode: '75116',
+        x: 646219.86,
+        y: 6862057.09,
+        population: 165446,
+        city: 'Paris 16e Arrondissement',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.54589
+      }
+    },
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.381844,
+          48.860162
+        ]
+      },
+      properties: {
+        label: 'Paris 11e Arrondissement',
+        score: 0.8672718181818182,
+        id: '75111',
+        type: 'municipality',
+        name: 'Paris 11e Arrondissement',
+        postcode: '75011',
+        citycode: '75111',
+        x: 654647.14,
+        y: 6862413.89,
+        population: 147017,
+        city: 'Paris 11e Arrondissement',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.53999
+      }
+    },
+    {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [
+          2.415411,
+          48.833494
+        ]
+      },
+      properties: {
+        label: 'Paris 12e Arrondissement',
+        score: 0.8670972727272728,
+        id: '75112',
+        type: 'municipality',
+        name: 'Paris 12e Arrondissement',
+        postcode: '75012',
+        citycode: '75112',
+        x: 657087.89,
+        y: 6859429.89,
+        population: 141494,
+        city: 'Paris 12e Arrondissement',
+        context: '75, Paris, \u00CEle-de-France',
+        importance: 0.53807
+      }
+    }
+  ],
+  attribution: 'BAN',
+  licence: 'ETALAB-2.0',
+  query: 'paris',
+  filters: {
+    type: 'municipality'
+  },
+  limit: 10
+}

+ 15 - 0
tests/unit/jest.setup.ts

@@ -0,0 +1,15 @@
+import Vuex from 'vuex'
+import Vue from 'vue'
+import Vuetify from 'vuetify'
+import { config } from '@vue/test-utils'
+
+Vue.config.silent = true
+
+// Mock Nuxt components
+config.stubs['nuxt-link'] = true // string stabs like '<a><slot /></a>' are now depreciated
+config.stubs['no-ssr'] = true
+config.mocks.$t = (i: any) => i
+config.mocks.localePath = (i: any) => i
+
+Vue.use(Vuetify)
+Vue.use(Vuex)

+ 1 - 0
tsconfig.json

@@ -25,6 +25,7 @@
     },
     },
     "types": [
     "types": [
       "@nuxt/types",
       "@nuxt/types",
+      "@types/jest",
       "@nuxtjs/i18n",
       "@nuxtjs/i18n",
       "nuxt-leaflet",
       "nuxt-leaflet",
       "@nuxtjs/axios",
       "@nuxtjs/axios",

+ 26 - 8
types/interfaces.d.ts

@@ -1,22 +1,40 @@
 /**
 /**
  * Structures data as returned by the API and consumed by the structures page
  * Structures data as returned by the API and consumed by the structures page
  */
  */
+
+interface Address {
+  type: string,
+  // type: 'ADDRESS_PRACTICE' | 'ADDRESS_OTHER' | 'ADDRESS_HEAD_OFFICE' | 'ADDRESS_CONTACT' | 'ADDRESS_BILL',
+  latitude: number,
+  longitude: number,
+  streetAddress: string,
+  postalCode: string,
+  addressCity: string,
+  country: string
+}
+
+interface Article {
+  id: number,
+  date: string,
+  link: string,
+  title: string
+}
+
 interface Structure {
 interface Structure {
   readonly id: number,
   readonly id: number,
   name: string,
   name: string,
   logoId: string | null,
   logoId: string | null,
   principalType: string | null,
   principalType: string | null,
   website: string | null,
   website: string | null,
-  latitude: number | null,
-  longitude: number | null,
-  streetAddress: string | null,
-  postalCode: string | null,
-  addressCity: string | null,
-  country: string | null,
+  mapAddress: Address | null,
+  postalAddress: Address | null,
+  addresses: Array<Address>,
   telphone: string | null,
   telphone: string | null,
   email: string | null,
   email: string | null,
   facebook: string | null,
   facebook: string | null,
   twitter: string | null,
   twitter: string | null,
+  instagram: string | null,
+  youtube: string | null,
   practices: Array<string>,
   practices: Array<string>,
   n1Id: number | null,
   n1Id: number | null,
   n1Name: string | null,
   n1Name: string | null,
@@ -27,7 +45,7 @@ interface Structure {
   parents: Array<number>,
   parents: Array<number>,
   description: string | null,
   description: string | null,
   imageId: string | null,
   imageId: string | null,
-  articles: Array<{id: number, title: string, date: string, link: string}>
+  articles: Array<Article>
 }
 }
 
 
 interface Coordinates {
 interface Coordinates {
@@ -38,7 +56,7 @@ interface Coordinates {
 /**
 /**
  * Items of the UiSearchAddress component
  * Items of the UiSearchAddress component
  */
  */
-interface Address {
+interface UiSearchAddressItem {
   text: string,
   text: string,
   value: Coordinates,
   value: Coordinates,
   disabled?: boolean
   disabled?: boolean

文件差异内容过多而无法显示
+ 471 - 338
yarn.lock


部分文件因为文件数量过多而无法显示