Bruno Predot

Ajout et paramétrage de @nuxt/test-utils avec vitest et toutes ses dépendances.

Factorisation en ajoutant les composants :  Loader, MovieCard.
... ... @@ -11,4 +11,6 @@
- Ajout composant MovieCommentForm.
- Ajout composant MovieCommentList.
- Ajout dépendance TinyMCE.
- Ajout composant TinyMceFieldEditor.
\ No newline at end of file
- Ajout composant TinyMceFieldEditor.
- Ajout composant Loader.
- Ajout composant MovieCard.
\ No newline at end of file
... ...
<script lang="ts" setup>
//#region --Props--.
import { useDateFormat } from "@vueuse/core";
import { FilmIcon } from "lucide-vue-next";
//#endregion
//#region --Props--.
defineProps({
movie: {
type: Object,
required: true,
nullable: false,
},
});
//#endregion
</script>
<template>
<section
class="bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-transform duration-300 hover:scale-105 cursor-pointer"
@click="navigateTo(`/movies/${movie.id}`)"
>
<div class="relative pb-[150%]">
<img
v-if="movie.poster_path"
:alt="movie.title"
:src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
class="absolute inset-0 w-full h-full object-cover"
/>
<div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center">
<FilmIcon :size="48" class="text-gray-500" />
</div>
<div
class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold"
>
{{ movie.vote_average.toFixed(1) }}
</div>
</div>
<div class="p-4">
<h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2>
<p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p>
</div>
</section>
</template>
<style scoped></style>
... ...
... ... @@ -3,9 +3,8 @@
import { onBeforeUnmount, ref } from "vue";
import { useTMDB } from "~/composables/tMDB";
import { Movie } from "~/models/movie";
import { FilmIcon, SearchXIcon } from "lucide-vue-next";
import { SearchXIcon } from "lucide-vue-next";
import type { MovieInterface } from "~/interfaces/movie";
import { useDateFormat } from "@vueuse/core";
//#endregion
//#region --Declaration--.
... ... @@ -99,7 +98,7 @@ function createIntersectionObserver() {
if (entry.isIntersecting && !isLoadingMore.value && currentPage.value < totalPages.value) {
if (searchQuery.value) {
// Continue searching query if already active.
search(searchQuery.value, currentPage.value + 1)
search(searchQuery.value, currentPage.value + 1);
} else {
// Continue fetching popular movies.
fetchMovies(currentPage.value + 1);
... ... @@ -117,7 +116,7 @@ function handleSearchEvent(event: string) {
}
function handleClearSearchEvent() {
searchQuery.value = '';
searchQuery.value = "";
currentPage.value = 1;
// Fetch popular movies after clear.
fetchMovies(1);
... ... @@ -159,36 +158,18 @@ onBeforeUnmount(() => {
@event:search="handleSearchEvent"
@event:clear_search="handleClearSearchEvent"
/>
<!-- Loading Skeleton -->
<ui-components-skeleton-movies-loader v-if="isInitialLoading" :is-initial-loading="isInitialLoading" :skeleton-number="20" />
<ui-components-skeleton-movies-loader
v-if="isInitialLoading"
:is-initial-loading="isInitialLoading"
:skeleton-number="20"
/>
<!-- Liste des films -->
<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">
<div
v-for="movie in movies"
:key="movie.id"
class="bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-transform duration-300 hover:scale-105 cursor-pointer"
@click="navigateTo(`/movies/${movie.id}`)"
>
<div class="relative pb-[150%]">
<img
v-if="movie.poster_path"
:alt="movie.title"
:src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
class="absolute inset-0 w-full h-full object-cover"
/>
<div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center">
<FilmIcon :size="48" class="text-gray-500" />
</div>
<div
class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold"
>
{{ movie.vote_average.toFixed(1) }}
</div>
</div>
<div class="p-4">
<h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2>
<p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p>
</div>
<div v-for="movie in movies" :key="movie.id">
<movie-card :movie="movie" />
</div>
</div>
... ... @@ -200,9 +181,7 @@ onBeforeUnmount(() => {
</section>
<!-- Loader pour le chargement de plus de films -->
<section v-if="isLoadingMore && !isInitialLoading" class="flex justify-center mt-8">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</section>
<ui-components-loader :is-initial-loading="isInitialLoading" :is-loading="isLoadingMore" />
<!-- Élément observé pour le défilement infini -->
<div ref="loadMoreTrigger" class="h-10 mt-4" />
... ...
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import HelloWorld from './HelloWorld.vue'
describe('HelloWorld', () => {
it('component renders Hello world properly', () => {
const wrapper = mount(HelloWorld)
expect(wrapper.text()).toContain('Hello world')
})
})
... ...
<script setup lang="ts">
</script>
<template>
<p>Hello world</p>
</template>
<style scoped lang="scss">
</style>
\ No newline at end of file
... ...
<script lang="ts" setup>
//#region --Props--.
defineProps({
isLoading: {
type: Boolean,
required: true,
nullable: false,
},
isInitialLoading: {
type: Boolean,
required: false,
nullable: false,
default: false,
},
});
//#endregion
</script>
<template>
<section v-if="isLoading && !isInitialLoading" class="flex justify-center mt-8">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</section>
</template>
<style scoped></style>
\ No newline at end of file
... ...
... ... @@ -25,6 +25,7 @@ export default defineNuxtConfig({
"@nuxt/eslint",
"@nuxt/icon",
"@nuxt/image",
"@nuxt/test-utils/module",
[
"@pinia/nuxt",
{
... ...
This diff is collapsed. Click to expand it.
... ... @@ -12,20 +12,21 @@
"lint:js": "eslint --ext \".ts,.vue\" .",
"lint:prettier": "prettier --write .",
"lint": "npm run lint:js && npm run lint:prettier",
"format": "prettier --write \"{components,pages,plugins,middleware,layouts,composables,assets}/**/*.{js,jsx,ts,tsx,vue,html,css,scss,json,md}\""
"format": "prettier --write \"{components,pages,plugins,middleware,layouts,composables,assets}/**/*.{js,jsx,ts,tsx,vue,html,css,scss,json,md}\"",
"test": "vitest"
},
"dependencies": {
"@nuxt/eslint": "^1.3.0",
"@nuxt/icon": "^1.12.0",
"@nuxt/image": "^1.10.0",
"@nuxt/scripts": "^0.11.6",
"@nuxt/test-utils": "^3.17.2",
"@nuxt/ui": "^2.22.0",
"@pinia-orm/nuxt": "^1.10.2",
"@pinia/nuxt": "^0.9.0",
"@tinymce/tinymce-vue": "^5.1.1",
"@types/vuelidate": "^0.7.22",
"@unhead/vue": "^2.0.8",
"@vitejs/plugin-vue": "^5.2.3",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/core": "^13.1.0",
... ... @@ -40,9 +41,15 @@
"vuetify-nuxt-module": "^0.18.6"
},
"devDependencies": {
"@nuxt/test-utils": "^3.17.2",
"@nuxtjs/tailwindcss": "^6.13.2",
"@vue/test-utils": "^2.4.6",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"prettier": "^3.5.3"
"happy-dom": "^17.4.4",
"jsdom": "^26.1.0",
"playwright-core": "^1.52.0",
"prettier": "^3.5.3",
"vitest": "^3.1.2"
}
}
... ...
... ... @@ -200,10 +200,11 @@ onMounted(() => {
<span class="font-semibold">Têtes d'affiche:</span>
{{
movie.credit.cast
.slice(0, 10)
.slice(0, 15)
.map((person) => person.name)
.join(", ")
}}
<span v-if="movie.credit.cast.length > 15">..</span>
</div>
</div>
<!-- Comments form. -->
... ...
// vite.config.js
import vue from '@vitejs/plugin-vue'
export default {
plugins: [vue()],
test: {
globals: true,
environment: "jsdom",
// Additional test configurations can be added here
},
}
\ No newline at end of file
... ...
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
/**
* Documentation here : https://nuxt.com/docs/getting-started/testing
* any custom Vitest config you require
*/
test: {
environment: 'nuxt',
// you can optionally set Nuxt-specific environment options
// environmentOptions: {
// nuxt: {
// rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
// domEnvironment: 'happy-dom', // 'happy-dom' (default) or 'jsdom'
// overrides: {
// // other Nuxt config you want to pass
// }
// }
// }
}
})
... ...