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();
{{ rating }} / 10
-
+
+
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';