From ab009f291ddced73ae84230ca438acb2a91302f8 Mon Sep 17 00:00:00 2001 From: Ilya Stepenko <38462532+VampireAotD@users.noreply.github.com> Date: Wed, 9 Oct 2024 00:26:02 +0300 Subject: [PATCH] Refactored code --- .../js/entities/anime-synonym/index.ts | 1 - .../js/entities/anime-synonym/model/index.ts | 1 - .../js/entities/anime-synonym/model/types.ts | 7 - src/resources/js/entities/anime-url/index.ts | 1 - .../js/entities/anime-url/model/index.ts | 1 - .../js/entities/anime-url/model/types.ts | 7 - .../js/entities/anime/model/types.ts | 10 +- .../features/anime/rating/AnimeRating.test.ts | 6 +- .../js/features/anime/rating/AnimeRating.vue | 11 +- src/resources/js/pages/Anime/Show.vue | 12 +- .../js/shared/ui/rating/Rating.test.ts | 123 ++++++++++++++++++ src/resources/js/shared/ui/rating/Rating.vue | 74 +++++++++++ src/resources/js/shared/ui/rating/index.ts | 1 + 13 files changed, 217 insertions(+), 38 deletions(-) delete mode 100644 src/resources/js/entities/anime-synonym/index.ts delete mode 100644 src/resources/js/entities/anime-synonym/model/index.ts delete mode 100644 src/resources/js/entities/anime-synonym/model/types.ts delete mode 100644 src/resources/js/entities/anime-url/index.ts delete mode 100644 src/resources/js/entities/anime-url/model/index.ts delete mode 100644 src/resources/js/entities/anime-url/model/types.ts create mode 100644 src/resources/js/shared/ui/rating/Rating.test.ts create mode 100644 src/resources/js/shared/ui/rating/Rating.vue create mode 100644 src/resources/js/shared/ui/rating/index.ts diff --git a/src/resources/js/entities/anime-synonym/index.ts b/src/resources/js/entities/anime-synonym/index.ts deleted file mode 100644 index 9f8ccadd..00000000 --- a/src/resources/js/entities/anime-synonym/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './model'; diff --git a/src/resources/js/entities/anime-synonym/model/index.ts b/src/resources/js/entities/anime-synonym/model/index.ts deleted file mode 100644 index 7c9d08ca..00000000 --- a/src/resources/js/entities/anime-synonym/model/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { type AnimeSynonym } from './types'; diff --git a/src/resources/js/entities/anime-synonym/model/types.ts b/src/resources/js/entities/anime-synonym/model/types.ts deleted file mode 100644 index 8ea0e947..00000000 --- a/src/resources/js/entities/anime-synonym/model/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Models } from '@/types'; - -type AnimeSynonym = Models.Id & { - name: string; -}; - -export { type AnimeSynonym }; diff --git a/src/resources/js/entities/anime-url/index.ts b/src/resources/js/entities/anime-url/index.ts deleted file mode 100644 index 9f8ccadd..00000000 --- a/src/resources/js/entities/anime-url/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './model'; diff --git a/src/resources/js/entities/anime-url/model/index.ts b/src/resources/js/entities/anime-url/model/index.ts deleted file mode 100644 index 35a663fb..00000000 --- a/src/resources/js/entities/anime-url/model/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { type AnimeUrl } from './types'; diff --git a/src/resources/js/entities/anime-url/model/types.ts b/src/resources/js/entities/anime-url/model/types.ts deleted file mode 100644 index ec532bd8..00000000 --- a/src/resources/js/entities/anime-url/model/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Models } from '@/types'; - -type AnimeUrl = Models.Id & { - url: string; -}; - -export { type AnimeUrl }; diff --git a/src/resources/js/entities/anime/model/types.ts b/src/resources/js/entities/anime/model/types.ts index b4f8ff1b..28b368a4 100644 --- a/src/resources/js/entities/anime/model/types.ts +++ b/src/resources/js/entities/anime/model/types.ts @@ -1,11 +1,17 @@ -import { type AnimeSynonym } from '@/entities/anime-synonym'; -import { type AnimeUrl } from '@/entities/anime-url'; import { type Genre } from '@/entities/genre'; import { type Image } from '@/entities/image'; import { type CountFilter, type RangeFilter } from '@/entities/search'; import { type VoiceActing } from '@/entities/voice-acting'; import { Models } from '@/types'; +type AnimeSynonym = Models.Id & { + name: string; +}; + +type AnimeUrl = Models.Id & { + url: string; +}; + type Anime = Models.Id & Models.Timestamps & Models.IsDeleted & { diff --git a/src/resources/js/features/anime/rating/AnimeRating.test.ts b/src/resources/js/features/anime/rating/AnimeRating.test.ts index 50ec85c7..4f3b7fd8 100644 --- a/src/resources/js/features/anime/rating/AnimeRating.test.ts +++ b/src/resources/js/features/anime/rating/AnimeRating.test.ts @@ -1,8 +1,7 @@ import { mount } from '@vue/test-utils'; import { beforeEach, describe, expect, it } from 'vitest'; -import PrimeVue from 'primevue/config'; -import Rating from 'primevue/rating'; +import { Rating } from '@/shared/ui/rating'; import AnimeRating from './AnimeRating.vue'; @@ -12,9 +11,6 @@ describe('AnimeRating test (AnimeRating.vue)', () => { beforeEach(() => { wrapper = mount(AnimeRating, { props: { modelValue: 5 }, - global: { - plugins: [PrimeVue], - }, }); }); diff --git a/src/resources/js/features/anime/rating/AnimeRating.vue b/src/resources/js/features/anime/rating/AnimeRating.vue index 5066348d..249fc442 100644 --- a/src/resources/js/features/anime/rating/AnimeRating.vue +++ b/src/resources/js/features/anime/rating/AnimeRating.vue @@ -1,5 +1,5 @@ @@ -7,13 +7,8 @@ const rating = defineModel(); diff --git a/src/resources/js/pages/Anime/Show.vue b/src/resources/js/pages/Anime/Show.vue index 76bf46cf..acb43943 100644 --- a/src/resources/js/pages/Anime/Show.vue +++ b/src/resources/js/pages/Anime/Show.vue @@ -8,15 +8,17 @@ import { AnimeRating } from '@/features/anime/rating'; import { ExternalLink } from '@/shared/ui/external-link'; import { AuthenticatedLayout } from '@/widgets/layouts'; -const props = defineProps<{ anime: Anime }>(); +type Props = { + anime: Anime; +}; + +const props = defineProps(); const rating = computed(() => props.anime.rating); const links = computed(() => props.anime.urls.map((link) => link.url)); const synonyms = computed(() => props.anime.synonyms.map((synonym) => synonym.name)); -const genres = computed((): string => - props.anime.genres.map((genre) => genre.name).join(', ') -); -const voiceActingList = computed((): string => +const genres = computed(() => props.anime.genres.map((genre) => genre.name).join(', ')); +const voiceActingList = computed(() => props.anime.voice_acting.map((voiceActing) => voiceActing.name).join(', ') ); diff --git a/src/resources/js/shared/ui/rating/Rating.test.ts b/src/resources/js/shared/ui/rating/Rating.test.ts new file mode 100644 index 00000000..2e8b4f04 --- /dev/null +++ b/src/resources/js/shared/ui/rating/Rating.test.ts @@ -0,0 +1,123 @@ +import { mount } from '@vue/test-utils'; +import { describe, expect, it } from 'vitest'; + +import Rating from './Rating.vue'; + +describe('Rating test (Rating.vue)', () => { + const createWrapper = (props = {}, options = {}) => { + return mount(Rating, { + props: { + maxRating: 5, + modelValue: 0, + ...props, + }, + global: { + stubs: { + Star: true, + }, + }, + ...options, + }); + }; + + it('Renders the correct number of stars', () => { + const wrapper = createWrapper({ maxRating: 8 }); + expect(wrapper.findAll('.relative').length).toBe(8); + }); + + it('Sets the initial rating correctly', async () => { + const wrapper = createWrapper({ modelValue: 3 }); + const filledStars = wrapper.findAll('.absolute'); + expect(filledStars[2].attributes('style')).toContain('width: 100%'); + expect(filledStars[3].attributes('style')).toContain('width: 0%'); + }); + + it('Updates rating on click', async () => { + const wrapper = createWrapper(); + await wrapper.findAll('.relative')[2].trigger('click'); + expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([3]); + }); + + it('Handles precision correctly', async () => { + const wrapper = createWrapper({ precision: 0.5 }); + + await wrapper.vm.handleHover( + { + offsetX: 15, + currentTarget: { offsetWidth: 30 }, + }, + 3 + ); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.hoverRating).toBe(2.5); + + await wrapper.vm.handleHover( + { + offsetX: 25, + currentTarget: { offsetWidth: 30 }, + }, + 3 + ); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.hoverRating).toBe(3); + + await wrapper.setProps({ precision: 0.1 }); + await wrapper.vm.handleHover( + { + offsetX: 13, + currentTarget: { offsetWidth: 30 }, + }, + 3 + ); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.hoverRating).toBe(2.5); + + await wrapper.vm.handleHover( + { + offsetX: 17, + currentTarget: { offsetWidth: 30 }, + }, + 3 + ); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.hoverRating).toBe(2.6); + }); + + it('Handles hover correctly', async () => { + const wrapper = createWrapper(); + await wrapper.vm.handleHover( + { + offsetX: 15, + currentTarget: { offsetWidth: 30 }, + }, + 3 + ); + await wrapper.vm.$nextTick(); + + const filledStars = wrapper.findAll('.absolute'); + expect(filledStars[2].attributes('style')).toContain('width: 100%'); + expect(filledStars[3].attributes('style')).toContain('width: 0%'); + expect(wrapper.vm.hoverRating).toBe(3); + }); + + it('Respects readonly prop', async () => { + const wrapper = createWrapper({ readonly: true }); + await wrapper.findAll('.relative')[2].trigger('click'); + expect(wrapper.emitted('update:modelValue')).toBeFalsy(); + }); + + it('Uses custom icons when provided', () => { + const wrapper = createWrapper( + {}, + { + slots: { + 'empty-icon': '', + 'filled-icon': '', + }, + } + ); + + expect(wrapper.find('.empty-custom-icon').exists()).toBeTruthy(); + expect(wrapper.find('.filled-custom-icon').exists()).toBeTruthy(); + }); +}); diff --git a/src/resources/js/shared/ui/rating/Rating.vue b/src/resources/js/shared/ui/rating/Rating.vue new file mode 100644 index 00000000..d3c58002 --- /dev/null +++ b/src/resources/js/shared/ui/rating/Rating.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/resources/js/shared/ui/rating/index.ts b/src/resources/js/shared/ui/rating/index.ts new file mode 100644 index 00000000..67791e46 --- /dev/null +++ b/src/resources/js/shared/ui/rating/index.ts @@ -0,0 +1 @@ +export { default as Rating } from './Rating.vue';