From 1d090d7c820a04842b5721b8a58265c5227d9362 Mon Sep 17 00:00:00 2001 From: AMin Date: Sun, 25 Oct 2026 13:34:57 +0800 Subject: [PATCH 1/2] fix(useFavicon): match the icon relation token --- src/useFavicon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useFavicon.ts b/src/useFavicon.ts index b470cf223e..228648d81f 100644 --- a/src/useFavicon.ts +++ b/src/useFavicon.ts @@ -3,7 +3,7 @@ import { useEffect } from 'react'; const useFavicon = (href: string) => { useEffect(() => { const link: HTMLLinkElement = - document.querySelector("link[rel*='icon']") || document.createElement('link'); + document.querySelector("link[rel~='icon']") || document.createElement('link'); link.type = 'image/x-icon'; link.rel = 'shortcut icon'; link.href = href; From 50864ffe74cb94c50d460177c6b2b10d1e0a11fd Mon Sep 17 00:00:00 2001 From: AMin Date: Sun, 25 Oct 2026 13:36:08 +0800 Subject: [PATCH 2/2] test(useFavicon): preserve touch and mask icons --- tests/useFavicon.test.tsx | 44 +++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/tests/useFavicon.test.tsx b/tests/useFavicon.test.tsx index 495881a0a9..30fe52efb9 100644 --- a/tests/useFavicon.test.tsx +++ b/tests/useFavicon.test.tsx @@ -2,10 +2,7 @@ import { renderHook } from '@testing-library/react-hooks'; import useFavicon from '../src/useFavicon'; afterEach(() => { - const favicon = document.querySelector("link[rel*='icon']"); - if (favicon) { - favicon.remove(); - } + document.querySelectorAll("link[rel*='icon']").forEach((link) => link.remove()); }); describe('useFavicon', () => { @@ -54,4 +51,43 @@ describe('useFavicon', () => { hook.rerender('https://en.wikipedia.org/wiki/Favicon'); expect(favicon.href).toBe('https://en.wikipedia.org/wiki/Favicon'); }); + + it.each(['apple-touch-icon', 'mask-icon'])( + 'should leave %s links unchanged when creating a favicon', + (rel) => { + const link = document.createElement('link'); + link.rel = rel; + link.href = 'https://example.com/special-icon.png'; + document.head.appendChild(link); + + renderHook(() => useFavicon('https://example.com/favicon.ico')); + + expect(link.rel).toBe(rel); + expect(link.href).toBe('https://example.com/special-icon.png'); + expect(document.querySelector('link[rel~="icon"]')!.href).toBe( + 'https://example.com/favicon.ico' + ); + } + ); + + it.each(['icon', 'shortcut icon'])( + 'should find an existing %s link after an Apple touch icon', + (rel) => { + const touchIcon = document.createElement('link'); + touchIcon.rel = 'apple-touch-icon'; + touchIcon.href = 'https://example.com/touch.png'; + const favicon = document.createElement('link'); + favicon.rel = rel; + favicon.href = 'https://example.com/old.ico'; + document.head.appendChild(touchIcon); + document.head.appendChild(favicon); + + renderHook(() => useFavicon('https://example.com/new.ico')); + + expect(favicon.href).toBe('https://example.com/new.ico'); + expect(touchIcon.href).toBe('https://example.com/touch.png'); + expect(touchIcon.rel).toBe('apple-touch-icon'); + expect(document.querySelectorAll('link[rel~="icon"]')).toHaveLength(1); + } + ); });