Skip to content

Commit

Permalink
feat: support passing elements as icon config
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaskuske committed Jun 2, 2019
1 parent 650bad3 commit ce403cc
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 35 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2019-06-02

### Added

- IconConfig can now take an HTMLLinkElement directly, rather than passing a selector string. Passing selectors is still supported.

### Changed

- BREAKING: IconConfig['selector'] is now called IconConfig['element']

### Added

- Tests added 🏗

## [0.5.2] - 2019-06-02

### Added
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ It takes either an configuration object for a single icon to be updated, or an A
```ts
type IconConfig = {
selector: string
element: string | HTMLLinkElement
href?: {
dark?: string
light?: string
}
}
```
The `selector` is a CSS selector. It will be passed to `document.querySelector()` and must return a `<link>` element.
The `element` is either a CSS selector or a `<link>` element. If a selector is passed, it will be passed to `document.querySelector()` and must return a `<link>` element.
The `href` property is _optional_:
Expand Down
10 changes: 5 additions & 5 deletions favicon-mode-switcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
*/

/** @typedef { import('index').ColorScheme } ColorScheme */
/** @typedef { import('index').Icon } Icon */
/** @typedef { import('index').IconConfig } IconConfig */
/** @typedef { import('index').default } FaviconModeSwitcher */
/** @typedef { IconConfig & {element: HTMLLinkElement} } Icon */

function warn(/** @type { string } */ message) {
typeof console !== 'undefined' && console.warn(message)
Expand Down Expand Up @@ -51,10 +51,10 @@ export default function faviconModeSwitcher(options) {
options = Array.isArray(options) ? options : [options]

const icons = options.reduce(
(arr, { selector, href }) => {
const element = document.querySelector(selector)
if (element && element instanceof HTMLLinkElement) arr.push({ element, href, selector })
else warn(`[favicon-mode-switcher] Icon not found or not an HTMLLinkElement: ${selector}`)
(arr, { element, href }) => {
const link = typeof element === 'string' ? document.querySelector(element) : element
if (link && link instanceof HTMLLinkElement) arr.push({ element: link, href })
else warn(`[favicon-mode-switcher] Icon not found or not an HTMLLinkElement: ${element}`)
return arr
},
/** @type { Icon[] } */ ([]),
Expand Down
4 changes: 3 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type IconConfig = {
*
* Passed to `document.querySelector()`.
*/
selector: string
element: string | HTMLLinkElement
/**
* Specifies the resource URIs to use. If you omit this property, the substring `"light"` or
* `"dark"` in the original URI found on the HTMLLinkElement will be *updated / replaced*
Expand All @@ -29,6 +29,8 @@ export type IconConfig = {
href?: { [Key in ColorScheme]?: string }
}

export type Icon = Pick<IconConfig, 'href'> & { element: HTMLLinkElement }

/** Remove the color scheme listeners and reset all icons to their original href. */
declare function DestroyFunction(): void

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "favicon-mode-switcher",
"version": "0.5.2",
"version": "1.0.0",
"description": "🕯 Make your favicon adapt to dark and light mode",
"main": "dist/index.js",
"unpkg": "dist/index.umd.min.js",
Expand Down
76 changes: 50 additions & 26 deletions test/browser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,65 +53,89 @@ afterEach(() => {
colorScheme._subscriptions = []
})

it('Replaces "dark"/"light" in original href if no href config is specified', () => {
it('Allows passing a CSS selector instead of an element', () => {
colorScheme.set('dark')

const link = insertLink({ href: 'light.test.ico', id: 'replace' })
const destroy = faviconModeSwitcher({ selector: '#replace' })
const element = insertLink({ href: 'light.test.ico', id: 'select' })
const destroy = faviconModeSwitcher({ element: '#select' })

expect(link.href).toEqual(expect.stringContaining('dark.test.ico'))
expect(element.href).toEqual(expect.stringContaining('dark.test.ico'))
colorScheme.set('light')
expect(link.href).toEqual(expect.stringContaining('light.test.ico'))
expect(element.href).toEqual(expect.stringContaining('light.test.ico'))

destroy()
})

it('Warns when an invalid `element` prop is passed', () => {
const originalConsoleWarn = console.warn
console.warn = jest.fn()
const warnSpy = jest.spyOn(console, 'warn')

const destroyFirst = faviconModeSwitcher({ element: 'doesntExist' })
expect(warnSpy).toBeCalledTimes(1)
destroyFirst()

// @ts-ignore
const destroySecond = faviconModeSwitcher({ element: document.createElement('div') })
expect(warnSpy).toBeCalledTimes(2)
destroySecond()

console.warn = originalConsoleWarn
})

it('Replaces "dark" / "light" in original href if no href config is specified', () => {
colorScheme.set('dark')

const element = insertLink({ href: 'light.test.ico' })
const destroy = faviconModeSwitcher({ element })

expect(element.href).toEqual(expect.stringContaining('dark.test.ico'))
colorScheme.set('light')
expect(element.href).toEqual(expect.stringContaining('light.test.ico'))

destroy()
})

it('Sets link.href to matching value from href config', () => {
const link = insertLink({ href: 'base.ico', id: 'config' })
const destroy = faviconModeSwitcher({
selector: '#config',
href: { dark: 'dark.ico', light: 'light.ico' },
})
const element = insertLink({ href: 'base.ico' })
const destroy = faviconModeSwitcher({ element, href: { dark: 'dark.ico', light: 'light.ico' } })

expect(link.href).toEqual(expect.stringContaining('base.ico'))
expect(element.href).toEqual(expect.stringContaining('base.ico'))

colorScheme.set('dark')
expect(link.href).toEqual(expect.stringContaining('dark.ico'))
expect(element.href).toEqual(expect.stringContaining('dark.ico'))
colorScheme.set('light')
expect(link.href).toEqual(expect.stringContaining('light.ico'))
expect(element.href).toEqual(expect.stringContaining('light.ico'))

destroy()
})

it('Falls back to original href if config has no value for current scheme', () => {
const link = insertLink({ href: 'original.ico', id: 'fallback' })
const destroy = faviconModeSwitcher({ selector: '#fallback', href: { dark: 'dark.ico' } })
const element = insertLink({ href: 'original.ico' })
const destroy = faviconModeSwitcher({ element, href: { dark: 'dark.ico' } })

colorScheme.set('dark')
expect(link.href).toEqual(expect.stringContaining('dark.ico'))
expect(element.href).toEqual(expect.stringContaining('dark.ico'))
colorScheme.set('light')
expect(link.href).toEqual(expect.stringContaining('original.ico'))
expect(element.href).toEqual(expect.stringContaining('original.ico'))

destroy()
})

it('Removes listeners and resets icons when destroy() is called', () => {
const link = insertLink({ href: 'original.ico', id: 'destroy' })
const destroy = faviconModeSwitcher({
selector: '#destroy',
href: { dark: 'dark.ico', light: 'light.ico' },
})
const element = insertLink({ href: 'original.ico' })
const destroy = faviconModeSwitcher({ element, href: { dark: 'dark.ico', light: 'light.ico' } })

expect(link.href).toEqual(expect.stringContaining('original.ico'))
expect(element.href).toEqual(expect.stringContaining('original.ico'))

colorScheme.set('dark')
expect(link.href).toEqual(expect.stringContaining('dark.ico'))
expect(element.href).toEqual(expect.stringContaining('dark.ico'))

// href is set back to original
destroy()
expect(link.href).toEqual(expect.stringContaining('original.ico'))
expect(element.href).toEqual(expect.stringContaining('original.ico'))

// and listeners are removed, further changes don't update the href
colorScheme.set('dark')
expect(link.href).toEqual(expect.stringContaining('original.ico'))
expect(element.href).toEqual(expect.stringContaining('original.ico'))
})

0 comments on commit ce403cc

Please sign in to comment.