From 53cf00fe512539db000825a40be487327d25f298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levi=20=28Nguy=E1=BB=85n=20L=C6=B0=C6=A1ng=20Huy=29?= Date: Fri, 26 Aug 2022 09:40:20 +0700 Subject: [PATCH] fix(useTitle): prevent using `observe` with `titleTemplate` --- packages/core/useTitle/index.md | 16 +++--- packages/core/useTitle/index.test.ts | 85 ++++++++++++++++++++++++++++ packages/core/useTitle/index.ts | 20 ++++--- 3 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 packages/core/useTitle/index.test.ts diff --git a/packages/core/useTitle/index.md b/packages/core/useTitle/index.md index ddb00ac5157..ae6148f1981 100644 --- a/packages/core/useTitle/index.md +++ b/packages/core/useTitle/index.md @@ -48,18 +48,16 @@ const title = useTitle('New Title', { titleTemplate: '%s | My Awesome Website' } ``` ::: warning -When setting `observe` to `true`, the `titleTemplate` must return the exact same value as the input title. -Otherwise, the document title will not be updated. +`observe` is incompatible with `titleTemplate`. ::: ```js -// this will work -const title = useTitle('New Title', { observe: true, titleTemplate: '%s' }) // default value -// this will work -const title = useTitle('New Title', { observe: true, titleTemplate: title => title }) +/* ✅ Will work */ +const title = useTitle('New Title', { observe: true }) -// this won't work +/* ✅ Will work */ +const title = useTitle('New Title', { titleTemplate: '%s - %s' }) + +/* ❌ Will throw an error */ const title = useTitle('New Title', { observe: true, titleTemplate: '%s - %s' }) -// this won't work -const title = useTitle('New Title', { observe: true, titleTemplate: title => `${title} - modified` }) ``` diff --git a/packages/core/useTitle/index.test.ts b/packages/core/useTitle/index.test.ts new file mode 100644 index 00000000000..0c8738e29c9 --- /dev/null +++ b/packages/core/useTitle/index.test.ts @@ -0,0 +1,85 @@ +import { computed, ref } from 'vue-demi' +import { useTitle } from '.' + +describe('useTitle', () => { + it('without param', () => { + const title = useTitle() + expect(title.value).toEqual('') + title.value = 'new title' + expect(title.value).toEqual('new title') + }) + + describe('with writable param', () => { + it('string', () => { + const title = useTitle('old title') + expect(title.value).toEqual('old title') + title.value = 'new title' + expect(title.value).toEqual('new title') + }) + + it('null', () => { + const title = useTitle(null) + expect(title.value).toEqual('') + title.value = 'new title' + expect(title.value).toEqual('new title') + }) + + it('undefined', () => { + const title = useTitle(undefined) + expect(title.value).toEqual('') + title.value = 'new title' + expect(title.value).toEqual('new title') + }) + + describe('ref param', () => { + it('string', () => { + const targetRef = ref('old title') + const title = useTitle(targetRef) + expect(title.value).toEqual('old title') + targetRef.value = 'new title' + expect(title.value).toEqual('new title') + title.value = 'latest title' + expect(title.value).toEqual('latest title') + }) + + it('null', () => { + const targetRef = ref(null) + const title = useTitle(targetRef) + expect(title.value).toEqual(null) + targetRef.value = 'new title' + expect(title.value).toEqual('new title') + title.value = 'latest title' + expect(title.value).toEqual('latest title') + }) + + it('undefined', () => { + const targetRef = ref(undefined) + const title = useTitle(targetRef) + expect(title.value).toEqual(undefined) + targetRef.value = 'new title' + expect(title.value).toEqual('new title') + title.value = 'latest title' + expect(title.value).toEqual('latest title') + }) + }) + }) + + describe('with readonly param', () => { + it('computed', () => { + const condition = ref(false) + const target = computed(() => condition.value ? 'new title' : 'old title') + const title = useTitle(target) + expect(title.value).toEqual('old title') + condition.value = true + expect(title.value).toEqual('new title') + // title.value = '' // typed error + }) + + it('function', () => { + const target = () => 'new title' + const title = useTitle(target) + expect(title.value).toEqual('new title') + // title.value = '' // typed error + }) + }) +}) diff --git a/packages/core/useTitle/index.ts b/packages/core/useTitle/index.ts index b14e81e91ec..39b6081fab2 100644 --- a/packages/core/useTitle/index.ts +++ b/packages/core/useTitle/index.ts @@ -9,12 +9,14 @@ import { defaultDocument } from '../_configurable' export interface UseTitleOptions extends ConfigurableDocument { /** * Observe `document.title` changes using MutationObserve + * Cannot be used together with `titleTemplate` option. * * @default false */ observe?: boolean /** * The template string to parse the title (e.g., '%s | My Website') + * Cannot be used together with `observe` option. * * @default '%s' */ @@ -22,7 +24,7 @@ export interface UseTitleOptions extends ConfigurableDocument { } export function useTitle( - newTitle?: MaybeReadonlyRef, + newTitle: MaybeReadonlyRef, options?: UseTitleOptions, ): ComputedRef @@ -42,6 +44,14 @@ export function useTitle( newTitle: MaybeComputedRef = null, options: UseTitleOptions = {}, ) { + /* + `titleTemplate` that returns the modified input string will make + the `document.title` to be different from the `title.value`, + causing the title to update infinitely if `observe` is set to `true`. + */ + if (options.observe && options.titleTemplate) + throw new Error('Cannot use `observe` and `titleTemplate` together.') + const { document = defaultDocument, observe = false, @@ -50,7 +60,6 @@ export function useTitle( const title: WritableComputedRef = resolveRef(newTitle ?? document?.title ?? null) const isReadonly = newTitle && isFunction(newTitle) - const hasModifiedTitle = isFunction(titleTemplate) ? titleTemplate('') !== '' : titleTemplate !== '%s' function format(t: string) { return isFunction(titleTemplate) @@ -67,12 +76,7 @@ export function useTitle( { immediate: true }, ) - /* - `titleTemplate` that returns the modified input string will make - the `document.title` to be different from the `title.value`, causing the title to update infinitely. - therefore, `observe` should be ignored in this case. - */ - if (observe && document && !isReadonly && !hasModifiedTitle) { + if (observe && document && !isReadonly) { useMutationObserver( document.head?.querySelector('title'), () => {