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);