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
154 additions
and
39 deletions
@@ -11,4 +11,6 @@ | @@ -11,4 +11,6 @@ | ||
11 | - Ajout composant MovieCommentForm. | 11 | - Ajout composant MovieCommentForm. |
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