mirror of
https://github.com/tompro/sattle.git
synced 2026-10-26 01:49:58 +00:00
158 lines
5.0 KiB
TypeScript
158 lines
5.0 KiB
TypeScript
// Cross-tab events are hints, never authority: the current localStorage
|
|
// value is the only state a listener may project into a tab.
|
|
|
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
|
|
import {linkingPubKeyHex} from './keys'
|
|
import {onTrustedMintsChange, readTrustedMints, type TrustedMint} from './trustedMints'
|
|
import {stubLocalStorage} from './test-utils'
|
|
|
|
const STORAGE_KEY = 'sattle_trusted_mints'
|
|
const OWNER_ID = linkingPubKeyHex(new Uint8Array(32).fill(7))
|
|
const MINT_KEY = '02' + 'aa'.repeat(32)
|
|
|
|
const mint = (server: string): TrustedMint => ({
|
|
server,
|
|
mintPubkey: MINT_KEY,
|
|
addedAt: 1,
|
|
locked: false,
|
|
})
|
|
|
|
const store = (mints: TrustedMint[]): void => {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify({version: 1, ownerId: OWNER_ID, mints}))
|
|
}
|
|
|
|
const storageEvent = (key: string | null, newValue: string | null): Event => {
|
|
const event = new Event('storage')
|
|
Object.defineProperties(event, {
|
|
key: {value: key},
|
|
newValue: {value: newValue},
|
|
})
|
|
return event
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.unstubAllGlobals()
|
|
stubLocalStorage()
|
|
})
|
|
|
|
describe('trusted-mint storage-event convergence', () => {
|
|
it('projects a replacement from current storage instead of event newValue', () => {
|
|
// Given a tab observing an existing trusted mint
|
|
const events = new EventTarget()
|
|
vi.stubGlobal('window', events)
|
|
store([mint('before.example')])
|
|
const observed: string[][] = []
|
|
const unsubscribe = onTrustedMintsChange(() => {
|
|
observed.push(readTrustedMints(OWNER_ID).map((entry) => entry.server))
|
|
})
|
|
|
|
try {
|
|
// When another tab replaces storage but a delayed event carries obsolete bytes
|
|
store([mint('current.example')])
|
|
events.dispatchEvent(
|
|
storageEvent(STORAGE_KEY, JSON.stringify({mints: [mint('before.example')]})),
|
|
)
|
|
|
|
// Then the projection follows the live registry
|
|
expect(observed).toEqual([['current.example']])
|
|
} finally {
|
|
unsubscribe()
|
|
}
|
|
})
|
|
|
|
it('projects removal from current storage instead of a stale replacement event', () => {
|
|
// Given a tab observing a trusted mint
|
|
const events = new EventTarget()
|
|
vi.stubGlobal('window', events)
|
|
store([mint('removed.example')])
|
|
const observed: TrustedMint[][] = []
|
|
const unsubscribe = onTrustedMintsChange(() => {
|
|
observed.push(readTrustedMints(OWNER_ID))
|
|
})
|
|
|
|
try {
|
|
// When the registry is removed while the event claims it still exists
|
|
localStorage.removeItem(STORAGE_KEY)
|
|
events.dispatchEvent(
|
|
storageEvent(STORAGE_KEY, JSON.stringify({mints: [mint('removed.example')]})),
|
|
)
|
|
|
|
// Then the live removal wins
|
|
expect(observed).toEqual([[]])
|
|
} finally {
|
|
unsubscribe()
|
|
}
|
|
})
|
|
|
|
it('projects a clear event by rereading current localStorage', () => {
|
|
// Given a tab observing a trusted mint
|
|
const events = new EventTarget()
|
|
vi.stubGlobal('window', events)
|
|
store([mint('cleared.example')])
|
|
const observed: TrustedMint[][] = []
|
|
const unsubscribe = onTrustedMintsChange(() => {
|
|
observed.push(readTrustedMints(OWNER_ID))
|
|
})
|
|
|
|
try {
|
|
// When another tab clears storage, which emits key null
|
|
localStorage.clear()
|
|
events.dispatchEvent(storageEvent(null, JSON.stringify({mints: [mint('cleared.example')]})))
|
|
|
|
// Then the live empty registry wins
|
|
expect(observed).toEqual([[]])
|
|
} finally {
|
|
unsubscribe()
|
|
}
|
|
})
|
|
|
|
it('ignores a delayed event after a newer live registry has replaced it', () => {
|
|
// Given an observer and an obsolete first registry
|
|
const events = new EventTarget()
|
|
vi.stubGlobal('window', events)
|
|
store([mint('obsolete.example')])
|
|
const observed: string[][] = []
|
|
const unsubscribe = onTrustedMintsChange(() => {
|
|
observed.push(readTrustedMints(OWNER_ID).map((entry) => entry.server))
|
|
})
|
|
|
|
try {
|
|
// When a newer registry is already live before the obsolete event arrives
|
|
store([mint('live.example')])
|
|
events.dispatchEvent(
|
|
storageEvent(STORAGE_KEY, JSON.stringify({mints: [mint('obsolete.example')]})),
|
|
)
|
|
|
|
// Then the obsolete event cannot resurrect its value
|
|
expect(observed).toEqual([['live.example']])
|
|
} finally {
|
|
unsubscribe()
|
|
}
|
|
})
|
|
|
|
it('clears the projection for malformed current storage without throwing', () => {
|
|
// Given an observer with a previously valid registry
|
|
const events = new EventTarget()
|
|
vi.stubGlobal('window', events)
|
|
store([mint('valid.example')])
|
|
const observed: TrustedMint[][] = []
|
|
const unsubscribe = onTrustedMintsChange(() => {
|
|
observed.push(readTrustedMints(OWNER_ID))
|
|
})
|
|
|
|
try {
|
|
// When the current value is malformed and its event claims a valid old value
|
|
localStorage.setItem(STORAGE_KEY, '{')
|
|
events.dispatchEvent(
|
|
storageEvent(STORAGE_KEY, JSON.stringify({mints: [mint('valid.example')]})),
|
|
)
|
|
|
|
// Then parsing the live value safely removes it from the projection
|
|
expect(observed).toEqual([[]])
|
|
} finally {
|
|
unsubscribe()
|
|
}
|
|
})
|
|
})
|