feat: sync active design to URL for refresh/share
Switching designs now reflects the active design id in the URL (?design=<id>). A page refresh or shared link reopens that design; an absent or unknown id falls back to the default design. ha-relevant: no
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { getDesignIdFromUrl, setDesignIdInUrl } from '@/utils/designUrl'
|
||||
|
||||
describe('designUrl', () => {
|
||||
beforeEach(() => {
|
||||
window.history.replaceState(null, '', '/')
|
||||
})
|
||||
|
||||
it('returns null when no design param is present', () => {
|
||||
expect(getDesignIdFromUrl()).toBeNull()
|
||||
})
|
||||
|
||||
it('reads the design id from the URL', () => {
|
||||
window.history.replaceState(null, '', '/?design=abc-123')
|
||||
expect(getDesignIdFromUrl()).toBe('abc-123')
|
||||
})
|
||||
|
||||
it('writes the design id into the URL without adding history', () => {
|
||||
const before = window.history.length
|
||||
setDesignIdInUrl('design-42')
|
||||
expect(getDesignIdFromUrl()).toBe('design-42')
|
||||
expect(window.history.length).toBe(before)
|
||||
})
|
||||
|
||||
it('drops the param when passed null', () => {
|
||||
setDesignIdInUrl('design-42')
|
||||
setDesignIdInUrl(null)
|
||||
expect(getDesignIdFromUrl()).toBeNull()
|
||||
expect(window.location.search).toBe('')
|
||||
})
|
||||
|
||||
it('preserves other query params when updating design', () => {
|
||||
window.history.replaceState(null, '', '/?key=secret')
|
||||
setDesignIdInUrl('design-42')
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
expect(params.get('key')).toBe('secret')
|
||||
expect(params.get('design')).toBe('design-42')
|
||||
})
|
||||
|
||||
it('round-trips: written id is read back', () => {
|
||||
setDesignIdInUrl('xyz')
|
||||
expect(getDesignIdFromUrl()).toBe('xyz')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
// Sync the active design id with the browser URL (`?design=<id>`), so the
|
||||
// current design survives a page refresh and can be shared/opened via URL.
|
||||
// When no `design` param is present, callers fall back to the default design.
|
||||
const DESIGN_PARAM = 'design'
|
||||
|
||||
/** Read the design id from the current URL, or null when absent. */
|
||||
export function getDesignIdFromUrl(): string | null {
|
||||
if (typeof window === 'undefined') return null
|
||||
return new URLSearchParams(window.location.search).get(DESIGN_PARAM)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect the active design id into the URL without adding a history entry
|
||||
* (replaceState). Pass null to drop the param (back to the default design).
|
||||
*/
|
||||
export function setDesignIdInUrl(id: string | null): void {
|
||||
if (typeof window === 'undefined') return
|
||||
const url = new URL(window.location.href)
|
||||
if (id) url.searchParams.set(DESIGN_PARAM, id)
|
||||
else url.searchParams.delete(DESIGN_PARAM)
|
||||
window.history.replaceState(window.history.state, '', url)
|
||||
}
|
||||
Reference in New Issue
Block a user