From a2f17b19ecdbe8606d3b225348232df846cc2f65 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 6 Dec 2024 14:46:03 +0800 Subject: [PATCH 1/9] feat(client): add `onContentUpdated` hooks --- packages/client/src/components/Content.ts | 9 ++++++-- .../client/src/composables/contentUpdated.ts | 21 +++++++++++++++++++ packages/client/src/composables/index.ts | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/composables/contentUpdated.ts diff --git a/packages/client/src/components/Content.ts b/packages/client/src/components/Content.ts index e58b46214d..41fb0fdc7b 100644 --- a/packages/client/src/components/Content.ts +++ b/packages/client/src/components/Content.ts @@ -1,5 +1,5 @@ import { computed, defineAsyncComponent, defineComponent, h } from 'vue' -import { usePageComponent } from '../composables/index.js' +import { runCallbacks, usePageComponent } from '../composables/index.js' import { resolveRoute } from '../router/index.js' /** @@ -26,6 +26,11 @@ export const Content = defineComponent({ ) }) - return () => h(ContentComponent.value) + return () => + h(ContentComponent.value, { + onVnodeMounted: runCallbacks, + onVnodeUpdated: runCallbacks, + onVnodeBeforeUnmount: runCallbacks, + }) }, }) diff --git a/packages/client/src/composables/contentUpdated.ts b/packages/client/src/composables/contentUpdated.ts new file mode 100644 index 0000000000..1b495719ac --- /dev/null +++ b/packages/client/src/composables/contentUpdated.ts @@ -0,0 +1,21 @@ +import { onUnmounted } from 'vue' + +let contentUpdatedCallbacks: (() => unknown)[] = [] + +/** + * Register callback that is called every time the markdown content is updated + * in the DOM. + */ +export const onContentUpdated = (fn: () => unknown): void => { + contentUpdatedCallbacks.push(fn) + onUnmounted(() => { + contentUpdatedCallbacks = contentUpdatedCallbacks.filter((f) => f !== fn) + }) +} + +/** + * Call all registered callbacks + */ +export const runCallbacks = (): void => { + contentUpdatedCallbacks.forEach((fn) => fn()) +} diff --git a/packages/client/src/composables/index.ts b/packages/client/src/composables/index.ts index c834060742..290b3b41cf 100644 --- a/packages/client/src/composables/index.ts +++ b/packages/client/src/composables/index.ts @@ -1,3 +1,4 @@ export * from './clientData.js' export * from './clientDataUtils.js' export * from './updateHead.js' +export * from './contentUpdated.js' From ee7a5ca41d5ef1062cd0a6d6ff470dfb81034bcd Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 13 Dec 2024 00:46:16 +0800 Subject: [PATCH 2/9] chore: tweak --- packages/client/src/components/Content.ts | 12 +++++-- .../client/src/composables/contentHooks.ts | 31 +++++++++++++++++++ .../client/src/composables/contentUpdated.ts | 21 ------------- packages/client/src/composables/index.ts | 2 +- 4 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 packages/client/src/composables/contentHooks.ts delete mode 100644 packages/client/src/composables/contentUpdated.ts diff --git a/packages/client/src/components/Content.ts b/packages/client/src/components/Content.ts index 41fb0fdc7b..59f7665976 100644 --- a/packages/client/src/components/Content.ts +++ b/packages/client/src/components/Content.ts @@ -28,9 +28,15 @@ export const Content = defineComponent({ return () => h(ContentComponent.value, { - onVnodeMounted: runCallbacks, - onVnodeUpdated: runCallbacks, - onVnodeBeforeUnmount: runCallbacks, + onVnodeMounted: () => { + runCallbacks('mounted') + }, + onVnodeUpdated: () => { + runCallbacks('change') + }, + onVnodeBeforeUnmount: () => { + runCallbacks('beforeUnmount') + }, }) }, }) diff --git a/packages/client/src/composables/contentHooks.ts b/packages/client/src/composables/contentHooks.ts new file mode 100644 index 0000000000..dbc1fb3180 --- /dev/null +++ b/packages/client/src/composables/contentHooks.ts @@ -0,0 +1,31 @@ +import { onUnmounted } from 'vue' + +type LifeCycle = 'beforeUnmount' | 'change' | 'mounted' + +const hooks: Record unknown)[]> = { + mounted: [], + beforeUnmount: [], + change: [], +} + +const createHook = + (lifeCycle: LifeCycle) => + (fn: () => unknown): void => { + hooks[lifeCycle].push(fn) + onUnmounted(() => { + hooks[lifeCycle] = hooks[lifeCycle].filter((f) => f !== fn) + }) + } + +export const onContentChange = createHook('change') + +export const onContentMounted = createHook('mounted') + +export const onContentBeforeUnmount = createHook('beforeUnmount') + +/** + * Call all registered callbacks + */ +export const runCallbacks = (lifeCycle: LifeCycle): void => { + hooks[lifeCycle].forEach((fn) => fn()) +} diff --git a/packages/client/src/composables/contentUpdated.ts b/packages/client/src/composables/contentUpdated.ts deleted file mode 100644 index 1b495719ac..0000000000 --- a/packages/client/src/composables/contentUpdated.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { onUnmounted } from 'vue' - -let contentUpdatedCallbacks: (() => unknown)[] = [] - -/** - * Register callback that is called every time the markdown content is updated - * in the DOM. - */ -export const onContentUpdated = (fn: () => unknown): void => { - contentUpdatedCallbacks.push(fn) - onUnmounted(() => { - contentUpdatedCallbacks = contentUpdatedCallbacks.filter((f) => f !== fn) - }) -} - -/** - * Call all registered callbacks - */ -export const runCallbacks = (): void => { - contentUpdatedCallbacks.forEach((fn) => fn()) -} diff --git a/packages/client/src/composables/index.ts b/packages/client/src/composables/index.ts index 290b3b41cf..aa34aa20bb 100644 --- a/packages/client/src/composables/index.ts +++ b/packages/client/src/composables/index.ts @@ -1,4 +1,4 @@ export * from './clientData.js' export * from './clientDataUtils.js' export * from './updateHead.js' -export * from './contentUpdated.js' +export * from './contentHooks.js' From 14f8b467311dc2ff2f3e936602ffaa53468ecae3 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 13 Dec 2024 10:01:44 +0800 Subject: [PATCH 3/9] test: add e2e test --- .../components/MarkdownContentHooks.vue | 46 ++++++++++++ .../.vuepress/theme/client/layouts/Layout.vue | 3 + e2e/docs/content-hooks/content.md | 3 + e2e/tests/content-hooks.spec.ts | 70 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 e2e/docs/.vuepress/theme/client/components/MarkdownContentHooks.vue create mode 100644 e2e/docs/content-hooks/content.md create mode 100644 e2e/tests/content-hooks.spec.ts diff --git a/e2e/docs/.vuepress/theme/client/components/MarkdownContentHooks.vue b/e2e/docs/.vuepress/theme/client/components/MarkdownContentHooks.vue new file mode 100644 index 0000000000..1426d7d702 --- /dev/null +++ b/e2e/docs/.vuepress/theme/client/components/MarkdownContentHooks.vue @@ -0,0 +1,46 @@ + + + diff --git a/e2e/docs/.vuepress/theme/client/layouts/Layout.vue b/e2e/docs/.vuepress/theme/client/layouts/Layout.vue index 07519db48d..7888e994aa 100644 --- a/e2e/docs/.vuepress/theme/client/layouts/Layout.vue +++ b/e2e/docs/.vuepress/theme/client/layouts/Layout.vue @@ -1,5 +1,6 @@ @@ -18,6 +19,8 @@ const siteData = useSiteData()
+ + diff --git a/e2e/docs/content-hooks/content.md b/e2e/docs/content-hooks/content.md new file mode 100644 index 0000000000..a8badd3c11 --- /dev/null +++ b/e2e/docs/content-hooks/content.md @@ -0,0 +1,3 @@ +## title + +content diff --git a/e2e/tests/content-hooks.spec.ts b/e2e/tests/content-hooks.spec.ts new file mode 100644 index 0000000000..c960a0f8cd --- /dev/null +++ b/e2e/tests/content-hooks.spec.ts @@ -0,0 +1,70 @@ +import { expect, test } from '@playwright/test' +import { IS_DEV } from '../utils/env' +import { readSourceMarkdown, writeSourceMarkdown } from '../utils/source' + +let changeCount = 0 + +const updateMarkdownContent = async (): Promise => { + changeCount++ + const content = await readSourceMarkdown('content-hooks/content.md') + await writeSourceMarkdown( + 'content-hooks/content.md', + `${content}\n\nUpdated content`, + ) +} + +const restoreMarkdownContent = async (): Promise => { + changeCount = 0 + await writeSourceMarkdown('content-hooks/content.md', '## title\n\ncontent\n') +} + +if (IS_DEV) { + test.beforeEach(async () => { + await restoreMarkdownContent() + }) + test.afterEach(async () => { + await restoreMarkdownContent() + }) +} + +test('should call content mounted hook', async ({ page }) => { + const mountedLocator = page.locator( + '.markdown-content-hooks .markdown-content-mounted', + ) + await page.goto('content-hooks/content.html') + + await expect(mountedLocator).toHaveText( + 'mounted: /content-hooks/content.html 1', + ) + + // update content but mounted hook should not be called twice + await updateMarkdownContent() + await expect(mountedLocator).toHaveText( + 'mounted: /content-hooks/content.html 1', + ) +}) + +test('should call content change hook', async ({ page }) => { + const changeLocator = page.locator( + '.markdown-content-hooks .markdown-content-change', + ) + await page.goto('content-hooks/content.html') + + await updateMarkdownContent() + await expect(changeLocator).toHaveText(`changedCount: ${changeCount}`) // 1 + + await updateMarkdownContent() + await expect(changeLocator).toHaveText(`changedCount: ${changeCount}`) // 2 +}) + +test('should call content before unmount hook', async ({ page }) => { + const beforeUnmountLocator = page.locator( + '.markdown-content-hooks .markdown-content-before-unmount', + ) + await page.goto('content-hooks/content.html') + await page.locator('.e2e-theme-nav a[href="/"]').click() + + await expect(beforeUnmountLocator).toHaveText( + 'beforeUnmount: /content-hooks/content.html', + ) +}) From 7289707d586e2e4706aa7217d9031aa219fcf365 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 13 Dec 2024 10:16:59 +0800 Subject: [PATCH 4/9] test: update e2e test --- e2e/tests/content-hooks.spec.ts | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/e2e/tests/content-hooks.spec.ts b/e2e/tests/content-hooks.spec.ts index c960a0f8cd..50a43e1c2c 100644 --- a/e2e/tests/content-hooks.spec.ts +++ b/e2e/tests/content-hooks.spec.ts @@ -18,14 +18,12 @@ const restoreMarkdownContent = async (): Promise => { await writeSourceMarkdown('content-hooks/content.md', '## title\n\ncontent\n') } -if (IS_DEV) { - test.beforeEach(async () => { - await restoreMarkdownContent() - }) - test.afterEach(async () => { - await restoreMarkdownContent() - }) -} +test.beforeEach(async () => { + await restoreMarkdownContent() +}) +test.afterAll(async () => { + await restoreMarkdownContent() +}) test('should call content mounted hook', async ({ page }) => { const mountedLocator = page.locator( @@ -44,6 +42,9 @@ test('should call content mounted hook', async ({ page }) => { ) }) +/** + * onContentChange hook should only called in development + */ test('should call content change hook', async ({ page }) => { const changeLocator = page.locator( '.markdown-content-hooks .markdown-content-change', @@ -51,10 +52,14 @@ test('should call content change hook', async ({ page }) => { await page.goto('content-hooks/content.html') await updateMarkdownContent() - await expect(changeLocator).toHaveText(`changedCount: ${changeCount}`) // 1 + await expect(changeLocator).toHaveText( + `changedCount: ${IS_DEV ? changeCount : 0}`, + ) // 1 await updateMarkdownContent() - await expect(changeLocator).toHaveText(`changedCount: ${changeCount}`) // 2 + await expect(changeLocator).toHaveText( + `changedCount: ${IS_DEV ? changeCount : 0}`, + ) // 2 }) test('should call content before unmount hook', async ({ page }) => { From 784a1fcf9fcb0136669a2aff44495968ef43b76e Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 13 Dec 2024 10:47:50 +0800 Subject: [PATCH 5/9] chore: tweak --- e2e/tests/content-hooks.spec.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/e2e/tests/content-hooks.spec.ts b/e2e/tests/content-hooks.spec.ts index 50a43e1c2c..7e96cb3d07 100644 --- a/e2e/tests/content-hooks.spec.ts +++ b/e2e/tests/content-hooks.spec.ts @@ -2,10 +2,7 @@ import { expect, test } from '@playwright/test' import { IS_DEV } from '../utils/env' import { readSourceMarkdown, writeSourceMarkdown } from '../utils/source' -let changeCount = 0 - const updateMarkdownContent = async (): Promise => { - changeCount++ const content = await readSourceMarkdown('content-hooks/content.md') await writeSourceMarkdown( 'content-hooks/content.md', @@ -14,14 +11,13 @@ const updateMarkdownContent = async (): Promise => { } const restoreMarkdownContent = async (): Promise => { - changeCount = 0 await writeSourceMarkdown('content-hooks/content.md', '## title\n\ncontent\n') } test.beforeEach(async () => { await restoreMarkdownContent() }) -test.afterAll(async () => { +test.afterEach(async () => { await restoreMarkdownContent() }) @@ -52,14 +48,10 @@ test('should call content change hook', async ({ page }) => { await page.goto('content-hooks/content.html') await updateMarkdownContent() - await expect(changeLocator).toHaveText( - `changedCount: ${IS_DEV ? changeCount : 0}`, - ) // 1 + await expect(changeLocator).toHaveText(`changedCount: ${IS_DEV ? 1 : 0}`) // 1 await updateMarkdownContent() - await expect(changeLocator).toHaveText( - `changedCount: ${IS_DEV ? changeCount : 0}`, - ) // 2 + await expect(changeLocator).toHaveText(`changedCount: ${IS_DEV ? 2 : 0}`) // 2 }) test('should call content before unmount hook', async ({ page }) => { From 8c366e13c4764a4c7ccfada508d1702ad9761250 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 13 Dec 2024 11:02:44 +0800 Subject: [PATCH 6/9] chore: tweak --- e2e/tests/content-hooks.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests/content-hooks.spec.ts b/e2e/tests/content-hooks.spec.ts index 7e96cb3d07..2d1e1d594f 100644 --- a/e2e/tests/content-hooks.spec.ts +++ b/e2e/tests/content-hooks.spec.ts @@ -17,7 +17,7 @@ const restoreMarkdownContent = async (): Promise => { test.beforeEach(async () => { await restoreMarkdownContent() }) -test.afterEach(async () => { +test.afterAll(async () => { await restoreMarkdownContent() }) From 236ca9864f177400a1b52c9c031f82c67669021d Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 13 Dec 2024 11:04:58 +0800 Subject: [PATCH 7/9] chore: tweak --- e2e/tests/content-hooks.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/e2e/tests/content-hooks.spec.ts b/e2e/tests/content-hooks.spec.ts index 2d1e1d594f..cff9a76c35 100644 --- a/e2e/tests/content-hooks.spec.ts +++ b/e2e/tests/content-hooks.spec.ts @@ -14,9 +14,6 @@ const restoreMarkdownContent = async (): Promise => { await writeSourceMarkdown('content-hooks/content.md', '## title\n\ncontent\n') } -test.beforeEach(async () => { - await restoreMarkdownContent() -}) test.afterAll(async () => { await restoreMarkdownContent() }) From 7aef1739ea3ace46ae694d775f8bf1ca00d939b5 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 13 Dec 2024 11:17:59 +0800 Subject: [PATCH 8/9] chore: tweak --- e2e/tests/content-hooks.spec.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/e2e/tests/content-hooks.spec.ts b/e2e/tests/content-hooks.spec.ts index cff9a76c35..3227d051e7 100644 --- a/e2e/tests/content-hooks.spec.ts +++ b/e2e/tests/content-hooks.spec.ts @@ -27,18 +27,15 @@ test('should call content mounted hook', async ({ page }) => { await expect(mountedLocator).toHaveText( 'mounted: /content-hooks/content.html 1', ) - - // update content but mounted hook should not be called twice - await updateMarkdownContent() - await expect(mountedLocator).toHaveText( - 'mounted: /content-hooks/content.html 1', - ) }) /** * onContentChange hook should only called in development */ test('should call content change hook', async ({ page }) => { + const mountedLocator = page.locator( + '.markdown-content-hooks .markdown-content-mounted', + ) const changeLocator = page.locator( '.markdown-content-hooks .markdown-content-change', ) @@ -49,6 +46,11 @@ test('should call content change hook', async ({ page }) => { await updateMarkdownContent() await expect(changeLocator).toHaveText(`changedCount: ${IS_DEV ? 2 : 0}`) // 2 + + // update content but mounted hook should not be called twice + await expect(mountedLocator).toHaveText( + 'mounted: /content-hooks/content.html 1', + ) }) test('should call content before unmount hook', async ({ page }) => { @@ -56,7 +58,7 @@ test('should call content before unmount hook', async ({ page }) => { '.markdown-content-hooks .markdown-content-before-unmount', ) await page.goto('content-hooks/content.html') - await page.locator('.e2e-theme-nav a[href="/"]').click() + await page.locator('.e2e-theme-nav ul > li > a').nth(0).click() await expect(beforeUnmountLocator).toHaveText( 'beforeUnmount: /content-hooks/content.html', From c443341aa2c9b008b4cfe9213ec152a88f3470a1 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 13 Dec 2024 11:21:21 +0800 Subject: [PATCH 9/9] chore: tweak --- e2e/tests/content-hooks.spec.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/e2e/tests/content-hooks.spec.ts b/e2e/tests/content-hooks.spec.ts index 3227d051e7..fdecd3928c 100644 --- a/e2e/tests/content-hooks.spec.ts +++ b/e2e/tests/content-hooks.spec.ts @@ -27,15 +27,18 @@ test('should call content mounted hook', async ({ page }) => { await expect(mountedLocator).toHaveText( 'mounted: /content-hooks/content.html 1', ) + + // update content but mounted hook should not be called twice + await updateMarkdownContent() + await expect(mountedLocator).toHaveText( + 'mounted: /content-hooks/content.html 1', + ) }) /** * onContentChange hook should only called in development */ test('should call content change hook', async ({ page }) => { - const mountedLocator = page.locator( - '.markdown-content-hooks .markdown-content-mounted', - ) const changeLocator = page.locator( '.markdown-content-hooks .markdown-content-change', ) @@ -46,11 +49,6 @@ test('should call content change hook', async ({ page }) => { await updateMarkdownContent() await expect(changeLocator).toHaveText(`changedCount: ${IS_DEV ? 2 : 0}`) // 2 - - // update content but mounted hook should not be called twice - await expect(mountedLocator).toHaveText( - 'mounted: /content-hooks/content.html 1', - ) }) test('should call content before unmount hook', async ({ page }) => {