-
{{ talk.title }}
+
@@ -42,9 +47,11 @@ import { ref, onMounted } from 'vue';
import { useApiStore } from '@api/store.ts';
import { debugError } from '@api/http-error.ts';
import type { TalksResponse } from '@api/response/index.ts';
+import TalkCardSkeletonPartial from '@partials/TalkCardSkeletonPartial.vue';
const apiStore = useApiStore();
const talks = ref
([]);
+const isLoadingTalks = ref(true);
onMounted(async () => {
try {
@@ -55,6 +62,8 @@ onMounted(async () => {
}
} catch (error) {
debugError(error);
+ } finally {
+ isLoadingTalks.value = false;
}
});
diff --git a/src/partials/WidgetSkillsSkeletonPartial.vue b/src/partials/WidgetSkillsSkeletonPartial.vue
new file mode 100644
index 00000000..f5deb912
--- /dev/null
+++ b/src/partials/WidgetSkillsSkeletonPartial.vue
@@ -0,0 +1,16 @@
+
+
+
diff --git a/tests/pages/ProjectsPage.test.ts b/tests/pages/ProjectsPage.test.ts
index 7573d044..4f1a583c 100644
--- a/tests/pages/ProjectsPage.test.ts
+++ b/tests/pages/ProjectsPage.test.ts
@@ -1,8 +1,10 @@
import { mount, flushPromises } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { faker } from '@faker-js/faker';
-import { describe, it, expect, vi } from 'vitest';
+import { describe, it, expect, vi, beforeEach } from 'vitest';
import ProjectsPage from '@pages/ProjectsPage.vue';
import type { ProfileResponse, ProfileSkillResponse, ProjectsResponse } from '@api/response/index.ts';
+import ProjectCardSkeletonPartial from '@partials/ProjectCardSkeletonPartial.vue';
const skills: ProfileSkillResponse[] = [
{
@@ -36,8 +38,16 @@ const projects: ProjectsResponse[] = [
},
];
-const getProfile = vi.fn<[], Promise<{ data: ProfileResponse }>>(() => Promise.resolve({ data: profile }));
-const getProjects = vi.fn<[], Promise<{ version: string; data: ProjectsResponse[] }>>(() => Promise.resolve({ version: '1.0.0', data: projects }));
+const getProfile = vi.fn<[], Promise<{ data: ProfileResponse }>>();
+const getProjects = vi.fn<[], Promise<{ version: string; data: ProjectsResponse[] }>>();
+
+beforeEach(() => {
+ getProfile.mockReset();
+ getProjects.mockReset();
+
+ getProfile.mockResolvedValue({ data: profile });
+ getProjects.mockResolvedValue({ version: '1.0.0', data: projects });
+});
vi.mock('@api/store.ts', () => ({ useApiStore: () => ({ getProfile, getProjects }) }));
vi.mock('@api/http-error.ts', () => ({ debugError: vi.fn() }));
@@ -64,6 +74,32 @@ describe('ProjectsPage', () => {
expect(wrapper.text()).toContain(projects[0].title);
});
+ it('renders static skeletons when no projects are returned', async () => {
+ getProjects.mockResolvedValueOnce({ version: '1.0.0', data: [] });
+
+ const wrapper = mount(ProjectsPage, {
+ global: {
+ stubs: {
+ SideNavPartial: true,
+ HeaderPartial: true,
+ WidgetSponsorPartial: true,
+ WidgetSkillsPartial: true,
+ FooterPartial: true,
+ ProjectCardPartial: true,
+ },
+ },
+ });
+
+ await flushPromises();
+ await nextTick();
+
+ const skeletons = wrapper.findAllComponents(ProjectCardSkeletonPartial);
+ expect(skeletons).toHaveLength(4);
+ skeletons.forEach((skeleton) => {
+ expect(skeleton.classes()).not.toContain('animate-pulse');
+ });
+ });
+
it('handles API errors', async () => {
const error = new Error('oops');
getProfile.mockRejectedValueOnce(error);
@@ -80,6 +116,7 @@ describe('ProjectsPage', () => {
},
});
await flushPromises();
+ await nextTick();
const { debugError } = await import('@api/http-error.ts');
expect(debugError).toHaveBeenCalledWith(error);
});