From 5086348d7afe46c35d1822147b33efa2470553d4 Mon Sep 17 00:00:00 2001 From: Gus Date: Thu, 9 Oct 2025 15:34:26 +0800 Subject: [PATCH 1/5] Add follow widget to post page sidebar --- src/pages/PostPage.vue | 2 ++ tests/pages/PostPage.test.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/pages/PostPage.vue b/src/pages/PostPage.vue index d0c8c080..12cac029 100644 --- a/src/pages/PostPage.vue +++ b/src/pages/PostPage.vue @@ -107,6 +107,7 @@ @@ -134,6 +135,7 @@ import SideNavPartial from '@partials/SideNavPartial.vue'; import type { PostResponse } from '@api/response/index.ts'; import { siteUrlFor, useSeoFromPost } from '@/support/seo'; import WidgetSponsorPartial from '@partials/WidgetSponsorPartial.vue'; +import WidgetSocialPartial from '@partials/WidgetSocialPartial.vue'; import { onMounted, ref, computed, watch, nextTick, watchEffect } from 'vue'; import { initializeHighlighter, renderMarkdown } from '@/support/markdown.ts'; import CoverImageLoader from '@components/CoverImageLoader.vue'; diff --git a/tests/pages/PostPage.test.ts b/tests/pages/PostPage.test.ts index 98de0b3f..c2849ef4 100644 --- a/tests/pages/PostPage.test.ts +++ b/tests/pages/PostPage.test.ts @@ -65,6 +65,7 @@ describe('PostPage', () => { HeaderPartial: true, FooterPartial: true, WidgetSponsorPartial: true, + WidgetSocialPartial: true, WidgetSkillsPartial: true, RouterLink: { template: '' }, }, @@ -87,6 +88,7 @@ describe('PostPage', () => { HeaderPartial: true, FooterPartial: true, WidgetSponsorPartial: true, + WidgetSocialPartial: true, WidgetSkillsPartial: true, RouterLink: { template: '' }, }, @@ -108,6 +110,7 @@ describe('PostPage', () => { HeaderPartial: true, FooterPartial: true, WidgetSponsorPartial: true, + WidgetSocialPartial: true, WidgetSkillsPartial: true, RouterLink: { template: '' }, }, @@ -129,6 +132,7 @@ describe('PostPage', () => { HeaderPartial: true, FooterPartial: true, WidgetSponsorPartial: true, + WidgetSocialPartial: true, WidgetSkillsPartial: true, RouterLink: { template: '' }, }, @@ -140,4 +144,33 @@ describe('PostPage', () => { expect(wrapper.find('[data-testid="post-page-skeleton"]').exists()).toBe(false); expect(wrapper.text()).toContain("We couldn't load this post."); }); + + it('renders the follow widget above the sponsor widget', async () => { + const wrapper = mount(PostPage, { + global: { + stubs: { + SideNavPartial: true, + HeaderPartial: true, + FooterPartial: true, + WidgetSponsorPartial: true, + WidgetSocialPartial: true, + WidgetSkillsPartial: true, + RouterLink: { template: '' }, + }, + }, + }); + + await flushPromises(); + + const aside = wrapper.find('aside'); + expect(aside.exists()).toBe(true); + + const asideHtml = aside.html(); + const socialIndex = asideHtml.indexOf('widget-social-partial-stub'); + const sponsorIndex = asideHtml.indexOf('widget-sponsor-partial-stub'); + + expect(socialIndex).toBeGreaterThan(-1); + expect(sponsorIndex).toBeGreaterThan(-1); + expect(socialIndex).toBeLessThan(sponsorIndex); + }); }); From 4da16e9e3bd29dc46ea990aedf0d829e5b6c9e5f Mon Sep 17 00:00:00 2001 From: Gus Date: Thu, 9 Oct 2025 15:40:20 +0800 Subject: [PATCH 2/5] Add back to top links to main pages --- src/pages/HomePage.vue | 7 ++++++- src/pages/PostPage.vue | 7 ++++++- tests/pages/HomePage.test.ts | 23 +++++++++++++++++++++++ tests/pages/PostPage.test.ts | 21 +++++++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/pages/HomePage.vue b/src/pages/HomePage.vue index 20a95d29..896b8c0d 100644 --- a/src/pages/HomePage.vue +++ b/src/pages/HomePage.vue @@ -5,7 +5,7 @@
-
+
@@ -37,6 +37,10 @@
+
+ +
+
@@ -56,6 +60,7 @@ import WidgetSponsorPartial from '@partials/WidgetSponsorPartial.vue'; import FeaturedProjectsPartial from '@partials/FeaturedProjectsPartial.vue'; import WidgetSkillsSkeletonPartial from '@partials/WidgetSkillsSkeletonPartial.vue'; import WidgetOullinPartial from '@partials/WidgetOullinPartial.vue'; +import BackToTopLink from '@partials/BackToTopLink.vue'; import { onMounted, ref } from 'vue'; import { useApiStore } from '@api/store.ts'; diff --git a/src/pages/PostPage.vue b/src/pages/PostPage.vue index 12cac029..915ffb6f 100644 --- a/src/pages/PostPage.vue +++ b/src/pages/PostPage.vue @@ -90,7 +90,7 @@ -

{{ post.title }}

+

{{ post.title }}

@@ -113,6 +113,10 @@
+
+ +
+ @@ -136,6 +140,7 @@ import type { PostResponse } from '@api/response/index.ts'; import { siteUrlFor, useSeoFromPost } from '@/support/seo'; import WidgetSponsorPartial from '@partials/WidgetSponsorPartial.vue'; import WidgetSocialPartial from '@partials/WidgetSocialPartial.vue'; +import BackToTopLink from '@partials/BackToTopLink.vue'; import { onMounted, ref, computed, watch, nextTick, watchEffect } from 'vue'; import { initializeHighlighter, renderMarkdown } from '@/support/markdown.ts'; import CoverImageLoader from '@components/CoverImageLoader.vue'; diff --git a/tests/pages/HomePage.test.ts b/tests/pages/HomePage.test.ts index 182eb807..6308a1f5 100644 --- a/tests/pages/HomePage.test.ts +++ b/tests/pages/HomePage.test.ts @@ -71,4 +71,27 @@ describe('HomePage', () => { const { debugError } = await import('@api/http-error.ts'); expect(debugError).toHaveBeenCalledWith(error); }); + + it('renders a back to top link targeting the home container', async () => { + const wrapper = mount(HomePage, { + global: { + stubs: { + SideNavPartial: true, + HeaderPartial: true, + HeroPartial: true, + FooterPartial: true, + ArticlesListPartial: true, + FeaturedProjectsPartial: true, + TalksPartial: true, + WidgetSponsorPartial: true, + WidgetSkillsPartial: true, + }, + }, + }); + + await flushPromises(); + + const backToTopLink = wrapper.find('a[href="#home-top"]'); + expect(backToTopLink.exists()).toBe(true); + }); }); diff --git a/tests/pages/PostPage.test.ts b/tests/pages/PostPage.test.ts index c2849ef4..75b8ccb7 100644 --- a/tests/pages/PostPage.test.ts +++ b/tests/pages/PostPage.test.ts @@ -173,4 +173,25 @@ describe('PostPage', () => { expect(sponsorIndex).toBeGreaterThan(-1); expect(socialIndex).toBeLessThan(sponsorIndex); }); + + it('renders a back to top link targeting the post header', async () => { + const wrapper = mount(PostPage, { + global: { + stubs: { + SideNavPartial: true, + HeaderPartial: true, + FooterPartial: true, + WidgetSponsorPartial: true, + WidgetSocialPartial: true, + WidgetSkillsPartial: true, + RouterLink: { template: '' }, + }, + }, + }); + + await flushPromises(); + + const backToTopLink = wrapper.find('a[href="#post-top"]'); + expect(backToTopLink.exists()).toBe(true); + }); }); From 4b99c10e7c1a90af93dd50a035524342e8092ae5 Mon Sep 17 00:00:00 2001 From: Gus Date: Thu, 9 Oct 2025 15:43:17 +0800 Subject: [PATCH 3/5] Enhance designation styling in recommendations --- src/partials/RecommendationPartial.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/partials/RecommendationPartial.vue b/src/partials/RecommendationPartial.vue index aa0e4e64..b800baa9 100644 --- a/src/partials/RecommendationPartial.vue +++ b/src/partials/RecommendationPartial.vue @@ -16,7 +16,7 @@
{{ item.person.full_name }}
{{ item.person.company }}
-
+
{{ item.person.designation }}
From 516c50a0537f10d69856eb26d206a730e386b6ee Mon Sep 17 00:00:00 2001 From: Gus Date: Thu, 9 Oct 2025 15:45:57 +0800 Subject: [PATCH 4/5] Refactor PostPage test mounting --- tests/pages/PostPage.test.ts | 99 ++++++++---------------------------- 1 file changed, 21 insertions(+), 78 deletions(-) diff --git a/tests/pages/PostPage.test.ts b/tests/pages/PostPage.test.ts index 75b8ccb7..6f29e88b 100644 --- a/tests/pages/PostPage.test.ts +++ b/tests/pages/PostPage.test.ts @@ -52,25 +52,28 @@ vi.mock('@/public.ts', () => ({ getReadingTime: () => '', })); +const mountComponent = () => + mount(PostPage, { + global: { + stubs: { + SideNavPartial: true, + HeaderPartial: true, + FooterPartial: true, + WidgetSponsorPartial: true, + WidgetSocialPartial: true, + WidgetSkillsPartial: true, + RouterLink: { template: '' }, + }, + }, + }); + describe('PostPage', () => { beforeEach(() => { vi.clearAllMocks(); }); it('fetches post on mount', async () => { - const wrapper = mount(PostPage, { - global: { - stubs: { - SideNavPartial: true, - HeaderPartial: true, - FooterPartial: true, - WidgetSponsorPartial: true, - WidgetSocialPartial: true, - WidgetSkillsPartial: true, - RouterLink: { template: '' }, - }, - }, - }); + const wrapper = mountComponent(); const skeleton = wrapper.find('[data-testid="post-page-skeleton"]'); expect(skeleton.exists()).toBe(true); expect(skeleton.classes()).toContain('min-h-[25rem]'); @@ -81,19 +84,7 @@ describe('PostPage', () => { }); it('initializes highlight.js on mount', async () => { - const wrapper = mount(PostPage, { - global: { - stubs: { - SideNavPartial: true, - HeaderPartial: true, - FooterPartial: true, - WidgetSponsorPartial: true, - WidgetSocialPartial: true, - WidgetSkillsPartial: true, - RouterLink: { template: '' }, - }, - }, - }); + const wrapper = mountComponent(); await flushPromises(); const highlightCore = await import('highlight.js/lib/core'); expect(initializeHighlighter).toHaveBeenCalledWith(highlightCore.default); @@ -103,19 +94,7 @@ describe('PostPage', () => { it('processes markdown content', async () => { const DOMPurify = await import('dompurify'); - const wrapper = mount(PostPage, { - global: { - stubs: { - SideNavPartial: true, - HeaderPartial: true, - FooterPartial: true, - WidgetSponsorPartial: true, - WidgetSocialPartial: true, - WidgetSkillsPartial: true, - RouterLink: { template: '' }, - }, - }, - }); + const wrapper = mountComponent(); await flushPromises(); expect(renderMarkdown).toHaveBeenCalledWith(post.content); expect(DOMPurify.default.sanitize).toHaveBeenCalled(); @@ -125,19 +104,7 @@ describe('PostPage', () => { it('handles post errors gracefully', async () => { const error = new Error('fail'); getPost.mockRejectedValueOnce(error); - const wrapper = mount(PostPage, { - global: { - stubs: { - SideNavPartial: true, - HeaderPartial: true, - FooterPartial: true, - WidgetSponsorPartial: true, - WidgetSocialPartial: true, - WidgetSkillsPartial: true, - RouterLink: { template: '' }, - }, - }, - }); + const wrapper = mountComponent(); await flushPromises(); const { debugError } = await import('@api/http-error.ts'); expect(debugError).toHaveBeenCalledWith(error); @@ -146,19 +113,7 @@ describe('PostPage', () => { }); it('renders the follow widget above the sponsor widget', async () => { - const wrapper = mount(PostPage, { - global: { - stubs: { - SideNavPartial: true, - HeaderPartial: true, - FooterPartial: true, - WidgetSponsorPartial: true, - WidgetSocialPartial: true, - WidgetSkillsPartial: true, - RouterLink: { template: '' }, - }, - }, - }); + const wrapper = mountComponent(); await flushPromises(); @@ -175,19 +130,7 @@ describe('PostPage', () => { }); it('renders a back to top link targeting the post header', async () => { - const wrapper = mount(PostPage, { - global: { - stubs: { - SideNavPartial: true, - HeaderPartial: true, - FooterPartial: true, - WidgetSponsorPartial: true, - WidgetSocialPartial: true, - WidgetSkillsPartial: true, - RouterLink: { template: '' }, - }, - }, - }); + const wrapper = mountComponent(); await flushPromises(); From 6bd0f5b78ad192995d2f86721a7b6d464a443935 Mon Sep 17 00:00:00 2001 From: Gus Date: Thu, 9 Oct 2025 15:52:38 +0800 Subject: [PATCH 5/5] Improve PostPage sidebar order test --- tests/pages/PostPage.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/pages/PostPage.test.ts b/tests/pages/PostPage.test.ts index 6f29e88b..9438f0c9 100644 --- a/tests/pages/PostPage.test.ts +++ b/tests/pages/PostPage.test.ts @@ -120,13 +120,13 @@ describe('PostPage', () => { const aside = wrapper.find('aside'); expect(aside.exists()).toBe(true); - const asideHtml = aside.html(); - const socialIndex = asideHtml.indexOf('widget-social-partial-stub'); - const sponsorIndex = asideHtml.indexOf('widget-sponsor-partial-stub'); + const container = aside.find('div'); + expect(container.exists()).toBe(true); - expect(socialIndex).toBeGreaterThan(-1); - expect(sponsorIndex).toBeGreaterThan(-1); - expect(socialIndex).toBeLessThan(sponsorIndex); + const children = container.element.children; + expect(children.length).toBeGreaterThanOrEqual(2); + expect(children[0].tagName).toBe('WIDGET-SOCIAL-PARTIAL-STUB'); + expect(children[1].tagName).toBe('WIDGET-SPONSOR-PARTIAL-STUB'); }); it('renders a back to top link targeting the post header', async () => {