diff --git a/src/pages/AboutPage.vue b/src/pages/AboutPage.vue index 237c1d6a..6016cc3e 100644 --- a/src/pages/AboutPage.vue +++ b/src/pages/AboutPage.vue @@ -57,6 +57,7 @@ email to discuss projects and ideas. While I’m not always available for freelance or long-term work, please don’t hesitate to reach out anytime.

+ @@ -90,6 +91,7 @@ import SideNavPartial from '@partials/SideNavPartial.vue'; import WidgetSocialPartial from '@partials/WidgetSocialPartial.vue'; import WidgetSkillsPartial from '@partials/WidgetSkillsPartial.vue'; import WidgetSkillsSkeletonPartial from '@partials/WidgetSkillsSkeletonPartial.vue'; +import AboutConnectSkeletonPartial from '@partials/AboutConnectSkeletonPartial.vue'; import { useSeo, SITE_NAME, ABOUT_IMAGE, siteUrlFor, buildKeywords, PERSON_JSON_LD } from '@/support/seo'; import { useApiStore } from '@api/store.ts'; diff --git a/src/pages/ResumePage.vue b/src/pages/ResumePage.vue index d4d45ab8..4cbd2ec0 100644 --- a/src/pages/ResumePage.vue +++ b/src/pages/ResumePage.vue @@ -30,12 +30,15 @@
- - - - - - + +
@@ -68,6 +71,7 @@ import WidgetLangPartial from '@partials/WidgetLangPartial.vue'; import WidgetSkillsPartial from '@partials/WidgetSkillsPartial.vue'; import WidgetSkillsSkeletonPartial from '@partials/WidgetSkillsSkeletonPartial.vue'; import RecommendationPartial from '@partials/RecommendationPartial.vue'; +import ResumePageSkeletonPartial from '@partials/ResumePageSkeletonPartial.vue'; import { ref, onMounted } from 'vue'; import { useApiStore } from '@api/store.ts'; diff --git a/src/partials/AboutConnectSkeletonPartial.vue b/src/partials/AboutConnectSkeletonPartial.vue new file mode 100644 index 00000000..3bc33ad4 --- /dev/null +++ b/src/partials/AboutConnectSkeletonPartial.vue @@ -0,0 +1,7 @@ + diff --git a/src/partials/ResumePageSkeletonPartial.vue b/src/partials/ResumePageSkeletonPartial.vue new file mode 100644 index 00000000..d516ec42 --- /dev/null +++ b/src/partials/ResumePageSkeletonPartial.vue @@ -0,0 +1,79 @@ + diff --git a/tests/pages/AboutPage.test.ts b/tests/pages/AboutPage.test.ts index 146bbfdc..7ca77ea2 100644 --- a/tests/pages/AboutPage.test.ts +++ b/tests/pages/AboutPage.test.ts @@ -1,6 +1,6 @@ import { mount, flushPromises } from '@vue/test-utils'; import { faker } from '@faker-js/faker'; -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect, vi, afterEach } from 'vitest'; import AboutPage from '@pages/AboutPage.vue'; import type { ProfileResponse, ProfileSkillResponse } from '@api/response/index.ts'; @@ -28,6 +28,10 @@ vi.mock('@api/store.ts', () => ({ useApiStore: () => ({ getProfile }) })); vi.mock('@api/http-error.ts', () => ({ debugError: vi.fn() })); describe('AboutPage', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + it('shows formatted nickname', async () => { const wrapper = mount(AboutPage, { global: { @@ -46,6 +50,24 @@ describe('AboutPage', () => { expect(wrapper.find('h1').text()).toContain(formatted); }); + it('renders skeleton while loading the profile', () => { + getProfile.mockReturnValueOnce(new Promise(() => {})); + + const wrapper = mount(AboutPage, { + global: { + stubs: { + SideNavPartial: true, + HeaderPartial: true, + WidgetSocialPartial: true, + WidgetSkillsPartial: true, + FooterPartial: true, + }, + }, + }); + + expect(wrapper.find('[data-testid="about-connect-skeleton"]').exists()).toBe(true); + }); + it('handles profile errors gracefully', async () => { const error = new Error('fail'); getProfile.mockRejectedValueOnce(error); diff --git a/tests/pages/ResumePage.test.ts b/tests/pages/ResumePage.test.ts index d37455fb..d3b09bec 100644 --- a/tests/pages/ResumePage.test.ts +++ b/tests/pages/ResumePage.test.ts @@ -1,6 +1,6 @@ import { mount, flushPromises } from '@vue/test-utils'; import { faker } from '@faker-js/faker'; -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect, vi, afterEach } from 'vitest'; import ResumePage from '@pages/ResumePage.vue'; import type { ProfileResponse, ProfileSkillResponse, EducationResponse, ExperienceResponse, RecommendationsResponse } from '@api/response/index.ts'; @@ -64,6 +64,10 @@ vi.mock('@api/store.ts', () => ({ useApiStore: () => ({ getProfile, getExperienc vi.mock('@api/http-error.ts', () => ({ debugError: vi.fn() })); describe('ResumePage', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + it('fetches data on mount', async () => { const wrapper = mount(ResumePage, { global: { @@ -87,6 +91,27 @@ describe('ResumePage', () => { expect(wrapper.find('h1').text()).toContain('My resume'); }); + it('renders skeleton while the resume data is loading', () => { + getProfile.mockReturnValueOnce(new Promise(() => {})); + getExperience.mockReturnValueOnce(new Promise(() => {})); + getRecommendations.mockReturnValueOnce(new Promise(() => {})); + getEducation.mockReturnValueOnce(new Promise(() => {})); + + const wrapper = mount(ResumePage, { + global: { + stubs: { + SideNavPartial: true, + HeaderPartial: true, + FooterPartial: true, + WidgetLangPartial: true, + WidgetSkillsPartial: true, + }, + }, + }); + + expect(wrapper.find('[data-testid="resume-page-skeleton"]').exists()).toBe(true); + }); + it('handles fetch failures', async () => { const error = new Error('oops'); getProfile.mockRejectedValueOnce(error);