Ajout et paramétrage de @nuxt/test-utils avec vitest et toutes ses dépendances.
Factorisation en ajoutant les composants : Loader, MovieCard.
Showing
12 changed files
with
153 additions
and
38 deletions
| @@ -12,3 +12,5 @@ | @@ -12,3 +12,5 @@ | ||
| 12 | - Ajout composant MovieCommentList. | 12 | - Ajout composant MovieCommentList. |
| 13 | - Ajout dépendance TinyMCE. | 13 | - Ajout dépendance TinyMCE. |
| 14 | - Ajout composant TinyMceFieldEditor. | 14 | - Ajout composant TinyMceFieldEditor. |
| 15 | +- Ajout composant Loader. | ||
| 16 | +- Ajout composant MovieCard. |
components/MovieCard.vue
0 → 100644
| 1 | +<script lang="ts" setup> | ||
| 2 | +//#region --Props--. | ||
| 3 | +import { useDateFormat } from "@vueuse/core"; | ||
| 4 | +import { FilmIcon } from "lucide-vue-next"; | ||
| 5 | +//#endregion | ||
| 6 | + | ||
| 7 | +//#region --Props--. | ||
| 8 | +defineProps({ | ||
| 9 | + movie: { | ||
| 10 | + type: Object, | ||
| 11 | + required: true, | ||
| 12 | + nullable: false, | ||
| 13 | + }, | ||
| 14 | +}); | ||
| 15 | +//#endregion | ||
| 16 | +</script> | ||
| 17 | + | ||
| 18 | +<template> | ||
| 19 | + <section | ||
| 20 | + class="bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-transform duration-300 hover:scale-105 cursor-pointer" | ||
| 21 | + @click="navigateTo(`/movies/${movie.id}`)" | ||
| 22 | + > | ||
| 23 | + <div class="relative pb-[150%]"> | ||
| 24 | + <img | ||
| 25 | + v-if="movie.poster_path" | ||
| 26 | + :alt="movie.title" | ||
| 27 | + :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`" | ||
| 28 | + class="absolute inset-0 w-full h-full object-cover" | ||
| 29 | + /> | ||
| 30 | + <div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center"> | ||
| 31 | + <FilmIcon :size="48" class="text-gray-500" /> | ||
| 32 | + </div> | ||
| 33 | + <div | ||
| 34 | + class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold" | ||
| 35 | + > | ||
| 36 | + {{ movie.vote_average.toFixed(1) }} | ||
| 37 | + </div> | ||
| 38 | + </div> | ||
| 39 | + <div class="p-4"> | ||
| 40 | + <h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2> | ||
| 41 | + <p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p> | ||
| 42 | + </div> | ||
| 43 | + </section> | ||
| 44 | +</template> | ||
| 45 | + | ||
| 46 | +<style scoped></style> |
| @@ -3,9 +3,8 @@ | @@ -3,9 +3,8 @@ | ||
| 3 | import { onBeforeUnmount, ref } from "vue"; | 3 | import { onBeforeUnmount, ref } from "vue"; |
| 4 | import { useTMDB } from "~/composables/tMDB"; | 4 | import { useTMDB } from "~/composables/tMDB"; |
| 5 | import { Movie } from "~/models/movie"; | 5 | import { Movie } from "~/models/movie"; |
| 6 | -import { FilmIcon, SearchXIcon } from "lucide-vue-next"; | 6 | +import { SearchXIcon } from "lucide-vue-next"; |
| 7 | import type { MovieInterface } from "~/interfaces/movie"; | 7 | import type { MovieInterface } from "~/interfaces/movie"; |
| 8 | -import { useDateFormat } from "@vueuse/core"; | ||
| 9 | //#endregion | 8 | //#endregion |
| 10 | 9 | ||
| 11 | //#region --Declaration--. | 10 | //#region --Declaration--. |
| @@ -99,7 +98,7 @@ function createIntersectionObserver() { | @@ -99,7 +98,7 @@ function createIntersectionObserver() { | ||
| 99 | if (entry.isIntersecting && !isLoadingMore.value && currentPage.value < totalPages.value) { | 98 | if (entry.isIntersecting && !isLoadingMore.value && currentPage.value < totalPages.value) { |
| 100 | if (searchQuery.value) { | 99 | if (searchQuery.value) { |
| 101 | // Continue searching query if already active. | 100 | // Continue searching query if already active. |
| 102 | - search(searchQuery.value, currentPage.value + 1) | 101 | + search(searchQuery.value, currentPage.value + 1); |
| 103 | } else { | 102 | } else { |
| 104 | // Continue fetching popular movies. | 103 | // Continue fetching popular movies. |
| 105 | fetchMovies(currentPage.value + 1); | 104 | fetchMovies(currentPage.value + 1); |
| @@ -117,7 +116,7 @@ function handleSearchEvent(event: string) { | @@ -117,7 +116,7 @@ function handleSearchEvent(event: string) { | ||
| 117 | } | 116 | } |
| 118 | 117 | ||
| 119 | function handleClearSearchEvent() { | 118 | function handleClearSearchEvent() { |
| 120 | - searchQuery.value = ''; | 119 | + searchQuery.value = ""; |
| 121 | currentPage.value = 1; | 120 | currentPage.value = 1; |
| 122 | // Fetch popular movies after clear. | 121 | // Fetch popular movies after clear. |
| 123 | fetchMovies(1); | 122 | fetchMovies(1); |
| @@ -159,36 +158,18 @@ onBeforeUnmount(() => { | @@ -159,36 +158,18 @@ onBeforeUnmount(() => { | ||
| 159 | @event:search="handleSearchEvent" | 158 | @event:search="handleSearchEvent" |
| 160 | @event:clear_search="handleClearSearchEvent" | 159 | @event:clear_search="handleClearSearchEvent" |
| 161 | /> | 160 | /> |
| 161 | + | ||
| 162 | <!-- Loading Skeleton --> | 162 | <!-- Loading Skeleton --> |
| 163 | - <ui-components-skeleton-movies-loader v-if="isInitialLoading" :is-initial-loading="isInitialLoading" :skeleton-number="20" /> | 163 | + <ui-components-skeleton-movies-loader |
| 164 | + v-if="isInitialLoading" | ||
| 165 | + :is-initial-loading="isInitialLoading" | ||
| 166 | + :skeleton-number="20" | ||
| 167 | + /> | ||
| 168 | + | ||
| 164 | <!-- Liste des films --> | 169 | <!-- Liste des films --> |
| 165 | <div v-else-if="movies.length > 0" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> | 170 | <div v-else-if="movies.length > 0" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> |
| 166 | - <div | 171 | + <div v-for="movie in movies" :key="movie.id"> |
| 167 | - v-for="movie in movies" | 172 | + <movie-card :movie="movie" /> |
| 168 | - :key="movie.id" | ||
| 169 | - class="bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-transform duration-300 hover:scale-105 cursor-pointer" | ||
| 170 | - @click="navigateTo(`/movies/${movie.id}`)" | ||
| 171 | - > | ||
| 172 | - <div class="relative pb-[150%]"> | ||
| 173 | - <img | ||
| 174 | - v-if="movie.poster_path" | ||
| 175 | - :alt="movie.title" | ||
| 176 | - :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`" | ||
| 177 | - class="absolute inset-0 w-full h-full object-cover" | ||
| 178 | - /> | ||
| 179 | - <div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center"> | ||
| 180 | - <FilmIcon :size="48" class="text-gray-500" /> | ||
| 181 | - </div> | ||
| 182 | - <div | ||
| 183 | - class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold" | ||
| 184 | - > | ||
| 185 | - {{ movie.vote_average.toFixed(1) }} | ||
| 186 | - </div> | ||
| 187 | - </div> | ||
| 188 | - <div class="p-4"> | ||
| 189 | - <h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2> | ||
| 190 | - <p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p> | ||
| 191 | - </div> | ||
| 192 | </div> | 173 | </div> |
| 193 | </div> | 174 | </div> |
| 194 | 175 | ||
| @@ -200,9 +181,7 @@ onBeforeUnmount(() => { | @@ -200,9 +181,7 @@ onBeforeUnmount(() => { | ||
| 200 | </section> | 181 | </section> |
| 201 | 182 | ||
| 202 | <!-- Loader pour le chargement de plus de films --> | 183 | <!-- Loader pour le chargement de plus de films --> |
| 203 | - <section v-if="isLoadingMore && !isInitialLoading" class="flex justify-center mt-8"> | 184 | + <ui-components-loader :is-initial-loading="isInitialLoading" :is-loading="isLoadingMore" /> |
| 204 | - <div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" /> | ||
| 205 | - </section> | ||
| 206 | 185 | ||
| 207 | <!-- Élément observé pour le défilement infini --> | 186 | <!-- Élément observé pour le défilement infini --> |
| 208 | <div ref="loadMoreTrigger" class="h-10 mt-4" /> | 187 | <div ref="loadMoreTrigger" class="h-10 mt-4" /> |
components/test/HelloWorld.spec.ts
0 → 100644
| 1 | +import { describe, it, expect } from 'vitest' | ||
| 2 | +import { mount } from '@vue/test-utils' | ||
| 3 | + | ||
| 4 | +import HelloWorld from './HelloWorld.vue' | ||
| 5 | + | ||
| 6 | +describe('HelloWorld', () => { | ||
| 7 | + it('component renders Hello world properly', () => { | ||
| 8 | + const wrapper = mount(HelloWorld) | ||
| 9 | + expect(wrapper.text()).toContain('Hello world') | ||
| 10 | + }) | ||
| 11 | +}) |
components/test/HelloWorld.vue
0 → 100644
components/ui-components/Loader.vue
0 → 100644
| 1 | +<script lang="ts" setup> | ||
| 2 | +//#region --Props--. | ||
| 3 | +defineProps({ | ||
| 4 | + isLoading: { | ||
| 5 | + type: Boolean, | ||
| 6 | + required: true, | ||
| 7 | + nullable: false, | ||
| 8 | + }, | ||
| 9 | + isInitialLoading: { | ||
| 10 | + type: Boolean, | ||
| 11 | + required: false, | ||
| 12 | + nullable: false, | ||
| 13 | + default: false, | ||
| 14 | + }, | ||
| 15 | +}); | ||
| 16 | +//#endregion | ||
| 17 | +</script> | ||
| 18 | + | ||
| 19 | +<template> | ||
| 20 | + <section v-if="isLoading && !isInitialLoading" class="flex justify-center mt-8"> | ||
| 21 | + <div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" /> | ||
| 22 | + </section> | ||
| 23 | +</template> | ||
| 24 | + | ||
| 25 | +<style scoped></style> |
This diff is collapsed. Click to expand it.
| @@ -12,20 +12,21 @@ | @@ -12,20 +12,21 @@ | ||
| 12 | "lint:js": "eslint --ext \".ts,.vue\" .", | 12 | "lint:js": "eslint --ext \".ts,.vue\" .", |
| 13 | "lint:prettier": "prettier --write .", | 13 | "lint:prettier": "prettier --write .", |
| 14 | "lint": "npm run lint:js && npm run lint:prettier", | 14 | "lint": "npm run lint:js && npm run lint:prettier", |
| 15 | - "format": "prettier --write \"{components,pages,plugins,middleware,layouts,composables,assets}/**/*.{js,jsx,ts,tsx,vue,html,css,scss,json,md}\"" | 15 | + "format": "prettier --write \"{components,pages,plugins,middleware,layouts,composables,assets}/**/*.{js,jsx,ts,tsx,vue,html,css,scss,json,md}\"", |
| 16 | + "test": "vitest" | ||
| 16 | }, | 17 | }, |
| 17 | "dependencies": { | 18 | "dependencies": { |
| 18 | "@nuxt/eslint": "^1.3.0", | 19 | "@nuxt/eslint": "^1.3.0", |
| 19 | "@nuxt/icon": "^1.12.0", | 20 | "@nuxt/icon": "^1.12.0", |
| 20 | "@nuxt/image": "^1.10.0", | 21 | "@nuxt/image": "^1.10.0", |
| 21 | "@nuxt/scripts": "^0.11.6", | 22 | "@nuxt/scripts": "^0.11.6", |
| 22 | - "@nuxt/test-utils": "^3.17.2", | ||
| 23 | "@nuxt/ui": "^2.22.0", | 23 | "@nuxt/ui": "^2.22.0", |
| 24 | "@pinia-orm/nuxt": "^1.10.2", | 24 | "@pinia-orm/nuxt": "^1.10.2", |
| 25 | "@pinia/nuxt": "^0.9.0", | 25 | "@pinia/nuxt": "^0.9.0", |
| 26 | "@tinymce/tinymce-vue": "^5.1.1", | 26 | "@tinymce/tinymce-vue": "^5.1.1", |
| 27 | "@types/vuelidate": "^0.7.22", | 27 | "@types/vuelidate": "^0.7.22", |
| 28 | "@unhead/vue": "^2.0.8", | 28 | "@unhead/vue": "^2.0.8", |
| 29 | + "@vitejs/plugin-vue": "^5.2.3", | ||
| 29 | "@vuelidate/core": "^2.0.3", | 30 | "@vuelidate/core": "^2.0.3", |
| 30 | "@vuelidate/validators": "^2.0.4", | 31 | "@vuelidate/validators": "^2.0.4", |
| 31 | "@vueuse/core": "^13.1.0", | 32 | "@vueuse/core": "^13.1.0", |
| @@ -40,9 +41,15 @@ | @@ -40,9 +41,15 @@ | ||
| 40 | "vuetify-nuxt-module": "^0.18.6" | 41 | "vuetify-nuxt-module": "^0.18.6" |
| 41 | }, | 42 | }, |
| 42 | "devDependencies": { | 43 | "devDependencies": { |
| 44 | + "@nuxt/test-utils": "^3.17.2", | ||
| 43 | "@nuxtjs/tailwindcss": "^6.13.2", | 45 | "@nuxtjs/tailwindcss": "^6.13.2", |
| 46 | + "@vue/test-utils": "^2.4.6", | ||
| 44 | "eslint-config-prettier": "^10.1.2", | 47 | "eslint-config-prettier": "^10.1.2", |
| 45 | "eslint-plugin-prettier": "^5.2.6", | 48 | "eslint-plugin-prettier": "^5.2.6", |
| 46 | - "prettier": "^3.5.3" | 49 | + "happy-dom": "^17.4.4", |
| 50 | + "jsdom": "^26.1.0", | ||
| 51 | + "playwright-core": "^1.52.0", | ||
| 52 | + "prettier": "^3.5.3", | ||
| 53 | + "vitest": "^3.1.2" | ||
| 47 | } | 54 | } |
| 48 | } | 55 | } |
| @@ -200,10 +200,11 @@ onMounted(() => { | @@ -200,10 +200,11 @@ onMounted(() => { | ||
| 200 | <span class="font-semibold">Têtes d'affiche:</span> | 200 | <span class="font-semibold">Têtes d'affiche:</span> |
| 201 | {{ | 201 | {{ |
| 202 | movie.credit.cast | 202 | movie.credit.cast |
| 203 | - .slice(0, 10) | 203 | + .slice(0, 15) |
| 204 | .map((person) => person.name) | 204 | .map((person) => person.name) |
| 205 | .join(", ") | 205 | .join(", ") |
| 206 | }} | 206 | }} |
| 207 | + <span v-if="movie.credit.cast.length > 15">..</span> | ||
| 207 | </div> | 208 | </div> |
| 208 | </div> | 209 | </div> |
| 209 | <!-- Comments form. --> | 210 | <!-- Comments form. --> |
vite.config.ts
0 → 100644
vitest.config.m.ts
0 → 100644
| 1 | +import { defineVitestConfig } from '@nuxt/test-utils/config' | ||
| 2 | + | ||
| 3 | +export default defineVitestConfig({ | ||
| 4 | + /** | ||
| 5 | + * Documentation here : https://nuxt.com/docs/getting-started/testing | ||
| 6 | + * any custom Vitest config you require | ||
| 7 | + */ | ||
| 8 | + test: { | ||
| 9 | + environment: 'nuxt', | ||
| 10 | + // you can optionally set Nuxt-specific environment options | ||
| 11 | + // environmentOptions: { | ||
| 12 | + // nuxt: { | ||
| 13 | + // rootDir: fileURLToPath(new URL('./playground', import.meta.url)), | ||
| 14 | + // domEnvironment: 'happy-dom', // 'happy-dom' (default) or 'jsdom' | ||
| 15 | + // overrides: { | ||
| 16 | + // // other Nuxt config you want to pass | ||
| 17 | + // } | ||
| 18 | + // } | ||
| 19 | + // } | ||
| 20 | + } | ||
| 21 | +}) |
-
Please register or login to post a comment