chore(lint): apply eslint auto-fixes across the .tsx backlog
Mechanical --fix output over the newly linted .tsx files (indent, arrow-parens, comma-dangle, member-delimiter-style, unnecessary type assertions), plus the three generic-arrow test hooks converted to function declarations up front: the comma-dangle fixer strips the <T,> disambiguation comma and turns them into parse errors otherwise.
This commit is contained in:
@@ -232,13 +232,13 @@ function standardKit(
|
||||
kit['renderSlot'] = boundRenderSlot(host, entry)
|
||||
// renderSlotChain rides the same declaration source: only entries whose
|
||||
// children include a chain-kind slot receive the chain dispatch seat.
|
||||
if (Object.values(entry.children).some((spec) => spec.kind === 'chain')) {
|
||||
if (Object.values(entry.children).some(spec => spec.kind === 'chain')) {
|
||||
kit['renderSlotChain'] = boundRenderSlotChain(host, entry)
|
||||
}
|
||||
// SessionProvider standard seat: entries declaring a session-scope child
|
||||
// render the session area, so the framework hands them the self-wired
|
||||
// provider (module-level component = stable reference; no value import).
|
||||
if (Object.values(entry.children).some((spec) => spec.scope === 'session')) {
|
||||
if (Object.values(entry.children).some(spec => spec.scope === 'session')) {
|
||||
kit['SessionProvider'] = SessionProvider
|
||||
}
|
||||
}
|
||||
@@ -297,7 +297,7 @@ function SlotOutlet({ slotKey, ownerProps, opts }: {
|
||||
const host = useHost()
|
||||
// Version tick drives entries() re-read; the host batches per microtask.
|
||||
useSyncExternalStore(
|
||||
(fn) => host.subscribe(slotKey, fn),
|
||||
fn => host.subscribe(slotKey, fn),
|
||||
() => host.getVersion(slotKey),
|
||||
)
|
||||
const sessionInfo = useSessionMaybeProvideInfo()
|
||||
@@ -321,12 +321,12 @@ function SlotOutlet({ slotKey, ownerProps, opts }: {
|
||||
spec.scope === 'session'
|
||||
? <StrictSessionEntry slotKey={slotKey} entry={entry} ownerProps={owner} key={key} />
|
||||
: (
|
||||
<SlotErrorBoundary slotKey={slotKey} key={key}>
|
||||
{spec.scope === 'session-maybe'
|
||||
? <SessionMaybeEntry entry={entry} ownerProps={owner} />
|
||||
: <RootEntry entry={entry} ownerProps={owner} />}
|
||||
</SlotErrorBoundary>
|
||||
)
|
||||
<SlotErrorBoundary slotKey={slotKey} key={key}>
|
||||
{spec.scope === 'session-maybe'
|
||||
? <SessionMaybeEntry entry={entry} ownerProps={owner} />
|
||||
: <RootEntry entry={entry} ownerProps={owner} />}
|
||||
</SlotErrorBoundary>
|
||||
)
|
||||
)
|
||||
|
||||
if (spec.kind === 'single') {
|
||||
@@ -335,7 +335,7 @@ function SlotOutlet({ slotKey, ownerProps, opts }: {
|
||||
return guarded(entry)
|
||||
}
|
||||
if (spec.kind === 'keyed') {
|
||||
const entry = entries.find((e) => e.options?.key === opts?.entryKey)
|
||||
const entry = entries.find(e => e.options?.key === opts?.entryKey)
|
||||
if (!entry) return <>{opts?.fallback ?? null}</>
|
||||
return guarded(entry)
|
||||
}
|
||||
@@ -388,13 +388,13 @@ function SlotOutlet({ slotKey, ownerProps, opts }: {
|
||||
return elected ?? <>{opts?.fallback ?? null}</>
|
||||
}
|
||||
// list: registration order refined by explicit order, optional id filter.
|
||||
const withListOptions = entries.map((entry) => ({
|
||||
const withListOptions = entries.map(entry => ({
|
||||
entry,
|
||||
id: entry.options?.id,
|
||||
order: entry.options?.order ?? 0,
|
||||
}))
|
||||
let list = [...withListOptions].sort((a, b) => a.order - b.order)
|
||||
if (opts?.only !== undefined) list = list.filter((item) => item.id === opts.only)
|
||||
if (opts?.only !== undefined) list = list.filter(item => item.id === opts.only)
|
||||
if (list.length === 0) return <>{opts?.fallback ?? null}</>
|
||||
return <>{list.map((item, i) => guarded(item.entry, item.id ?? i))}</>
|
||||
}
|
||||
@@ -403,7 +403,7 @@ function SlotOutlet({ slotKey, ownerProps, opts }: {
|
||||
function RootOutlet({ ownerProps }: { ownerProps: object }) {
|
||||
const host = useHost()
|
||||
useSyncExternalStore(
|
||||
(fn) => host.subscribe('root', fn),
|
||||
fn => host.subscribe('root', fn),
|
||||
() => host.getVersion('root'),
|
||||
)
|
||||
const entry = host.entriesOf('root')[0]
|
||||
|
||||
@@ -73,11 +73,11 @@ const absentSource: HostObservable<undefined> = {
|
||||
/** Bind a source that disappears with the current session to an optional selector hook. */
|
||||
export function maybeObservableHook<T>(source: HostObservable<T> | undefined): MaybeSnapshotSelectorHook<T> {
|
||||
if (source !== undefined) return observableHook(source)
|
||||
return useAbsentSnapshot as MaybeSnapshotSelectorHook<T>
|
||||
return useAbsentSnapshot
|
||||
}
|
||||
|
||||
function useAbsentSnapshot<S>(_selector: (snapshot: never) => S, _equal?: (a: S, b: S) => boolean): S | undefined {
|
||||
return observableHook(absentSource)(() => undefined)
|
||||
observableHook(absentSource)(() => undefined)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,7 @@ function useAbsentSnapshot<S>(_selector: (snapshot: never) => S, _equal?: (a: S,
|
||||
*/
|
||||
export function SessionMaybeProvider({ children }: { children: ReactNode }) {
|
||||
const host = useHost()
|
||||
const id = observableHook(host.sessions.current)((s) => s)
|
||||
const id = observableHook(host.sessions.current)(s => s)
|
||||
return (
|
||||
<BindingContext.Provider value={host.sessions.maybeProvideInfo(id)}>
|
||||
{children}
|
||||
@@ -112,7 +112,7 @@ export interface SessionProviderProps {
|
||||
*/
|
||||
export function SessionProvider({ empty, children }: SessionProviderProps) {
|
||||
const host = useHost()
|
||||
const id = observableHook(host.sessions.current)((s) => s)
|
||||
const id = observableHook(host.sessions.current)(s => s)
|
||||
const info = id === undefined ? undefined : host.sessions.provideInfo(id)
|
||||
if (id === undefined || info === undefined) return <>{empty?.() ?? null}</>
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { HostObservable as ObservableSnapshot, SnapshotSelectorHook } from
|
||||
// Keep equality local: this suite asserts the eq parameter contract without
|
||||
// adding a reverse dependency from web-react to runtime.
|
||||
const shallowEqual = (a: Record<string, unknown>, b: Record<string, unknown>): boolean =>
|
||||
Object.keys(a).length === Object.keys(b).length && Object.keys(a).every((k) => Object.is(a[k], b[k]))
|
||||
Object.keys(a).length === Object.keys(b).length && Object.keys(a).every(k => Object.is(a[k], b[k]))
|
||||
|
||||
interface Snap { a: number; b: number }
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('bindSnapshotSelector', () => {
|
||||
const { source, set } = makeSource({ a: 1, b: 10 })
|
||||
const useSelector = bindSnapshotSelector(source)
|
||||
const probe = { renders: 0, value: undefined as number | undefined }
|
||||
render(<Harness useSelector={useSelector} sel={(s) => s.a} probe={probe} />)
|
||||
render(<Harness useSelector={useSelector} sel={s => s.a} probe={probe} />)
|
||||
expect(probe.value).toBe(1)
|
||||
const before = probe.renders
|
||||
act(() => { set({ a: 1, b: 11 }) }) // unrelated field: Object.is bail
|
||||
@@ -65,7 +65,7 @@ describe('bindSnapshotSelector', () => {
|
||||
const { source, set } = makeSource({ a: 1, b: 10 })
|
||||
const useSelector = bindSnapshotSelector(source)
|
||||
const probe = { renders: 0, value: undefined as { a: number } | undefined }
|
||||
render(<Harness useSelector={useSelector} sel={(s) => ({ a: s.a })} eq={shallowEqual} probe={probe} />)
|
||||
render(<Harness useSelector={useSelector} sel={s => ({ a: s.a })} eq={shallowEqual} probe={probe} />)
|
||||
const before = probe.renders
|
||||
act(() => { set({ a: 1, b: 99 }) }) // fresh object, shallow-equal slice
|
||||
expect(probe.renders).toBe(before)
|
||||
@@ -78,11 +78,11 @@ describe('bindSnapshotSelector', () => {
|
||||
const { source, set, stats } = makeSource({ a: 1, b: 10 })
|
||||
const useSelector = bindSnapshotSelector(source)
|
||||
const probe = { renders: 0, value: undefined as number | undefined }
|
||||
const { rerender } = render(<Harness useSelector={useSelector} sel={(s) => s.a} probe={probe} />)
|
||||
const { rerender } = render(<Harness useSelector={useSelector} sel={s => s.a} probe={probe} />)
|
||||
const after = stats.subscribeCalls
|
||||
rerender(<Harness useSelector={useSelector} sel={(s) => s.a} probe={probe} />)
|
||||
rerender(<Harness useSelector={useSelector} sel={s => s.a} probe={probe} />)
|
||||
act(() => { set({ a: 2, b: 10 }) })
|
||||
rerender(<Harness useSelector={useSelector} sel={(s) => s.a} probe={probe} />)
|
||||
rerender(<Harness useSelector={useSelector} sel={s => s.a} probe={probe} />)
|
||||
expect(stats.subscribeCalls).toBe(after)
|
||||
})
|
||||
|
||||
@@ -92,7 +92,7 @@ describe('bindSnapshotSelector', () => {
|
||||
const probe = { renders: 0, value: undefined as number | undefined }
|
||||
const view = render(
|
||||
<StrictMode>
|
||||
<Harness useSelector={useSelector} sel={(s) => s.a} probe={probe} />
|
||||
<Harness useSelector={useSelector} sel={s => s.a} probe={probe} />
|
||||
</StrictMode>,
|
||||
)
|
||||
expect(probe.value).toBe(1)
|
||||
@@ -112,7 +112,7 @@ describe('bindSnapshotSelector', () => {
|
||||
}
|
||||
const useSelector = bindSnapshotSelector(new MethodSource())
|
||||
const probe = { renders: 0, value: undefined as number | undefined }
|
||||
render(<Harness useSelector={useSelector} sel={(s) => s.a} probe={probe} />)
|
||||
render(<Harness useSelector={useSelector} sel={s => s.a} probe={probe} />)
|
||||
expect(probe.value).toBe(7)
|
||||
})
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ type FrameSlots = PropsRenderSlots<'spec.single' | 'spec.list'>
|
||||
function hostOver(core: SlotCore): SlotRendererHost {
|
||||
return {
|
||||
subscribe: (key, fn) => core.subscribe(key, fn),
|
||||
getVersion: (key) => core.getVersion(key),
|
||||
entriesOf: (key) => core.entries(key),
|
||||
specOf: (key) => core.specDynamic(key),
|
||||
isLive: (entry) => core.isLive(entry),
|
||||
getVersion: key => core.getVersion(key),
|
||||
entriesOf: key => core.entries(key),
|
||||
specOf: key => core.specDynamic(key),
|
||||
isLive: entry => core.isLive(entry),
|
||||
storeOf: () => undefined,
|
||||
sessions: {
|
||||
list: { getSnapshot: () => ({}), subscribe: () => () => {} },
|
||||
@@ -61,7 +61,7 @@ function mountFrame(core: SlotCore, body: (renderSlot: FrameSlots['renderSlot'])
|
||||
describe('createSlotRenderer over the real SlotCore', () => {
|
||||
it('renders registrations live through real microtask batching: register, dispose back to fallback', async () => {
|
||||
const core = new SlotCore()
|
||||
const { view } = mountFrame(core, (renderSlot) =>
|
||||
const { view } = mountFrame(core, renderSlot =>
|
||||
renderSlot('spec.single', {}, { fallback: <i>none</i> }))
|
||||
expect(view.container.textContent).toBe('none')
|
||||
let dispose = () => {}
|
||||
@@ -78,7 +78,7 @@ describe('createSlotRenderer over the real SlotCore', () => {
|
||||
const core = new SlotCore()
|
||||
const notified = vi.fn()
|
||||
core.subscribe('spec.list', notified)
|
||||
const { view } = mountFrame(core, (renderSlot) => renderSlot('spec.list', {}))
|
||||
const { view } = mountFrame(core, renderSlot => renderSlot('spec.list', {}))
|
||||
await act(async () => {
|
||||
core.register({ name: 'spec.list', id: 'two', order: 2 }, () => <span>2</span>)
|
||||
core.register({ name: 'spec.list', id: 'one', order: 1 }, () => <span>1</span>)
|
||||
|
||||
@@ -96,10 +96,10 @@ function makeHost() {
|
||||
subs.set(key, set)
|
||||
return () => { set.delete(fn) }
|
||||
},
|
||||
getVersion: (key) => versions.get(key) ?? 0,
|
||||
entriesOf: (key) => entries.get(key) ?? [],
|
||||
specOf: (key) => specs.get(key),
|
||||
isLive: (entry) => live.has(entry),
|
||||
getVersion: key => versions.get(key) ?? 0,
|
||||
entriesOf: key => entries.get(key) ?? [],
|
||||
specOf: key => specs.get(key),
|
||||
isLive: entry => live.has(entry),
|
||||
storeOf: (entry, scopeKey) => {
|
||||
if (entry.store === undefined) return undefined
|
||||
let perScope = storeCache.get(entry)
|
||||
@@ -121,8 +121,8 @@ function makeHost() {
|
||||
sessions: {
|
||||
list,
|
||||
current,
|
||||
provideInfo: (id) => infos.get(id),
|
||||
maybeProvideInfo: (id) => (id === undefined ? undefined : infos.get(id))
|
||||
provideInfo: id => infos.get(id),
|
||||
maybeProvideInfo: id => (id === undefined ? undefined : infos.get(id))
|
||||
?? { sessionId: undefined, hooks: {}, props: {} },
|
||||
},
|
||||
workspaces: { list: workspaces },
|
||||
@@ -145,7 +145,7 @@ function makeHost() {
|
||||
live.add(entry)
|
||||
bump(key)
|
||||
return () => {
|
||||
entries.set(key, (entries.get(key) ?? []).filter((e) => e !== entry))
|
||||
entries.set(key, (entries.get(key) ?? []).filter(e => e !== entry))
|
||||
live.delete(entry)
|
||||
bump(key)
|
||||
}
|
||||
@@ -187,7 +187,7 @@ const chainEntryOf = (partial: {
|
||||
priority?: number
|
||||
}): Omit<StoredEntry, 'options'> & { options?: StoredEntry['options'] } => ({
|
||||
component: partial.component,
|
||||
select: partial.select as StoredEntry['select'],
|
||||
select: partial.select,
|
||||
...(partial.priority !== undefined ? { options: { priority: partial.priority } } : {}),
|
||||
})
|
||||
|
||||
@@ -230,7 +230,7 @@ describe('child outlets and the renderSlot binding', () => {
|
||||
const h = makeHost()
|
||||
h.declare('k.single', SINGLE_ROOT)
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT },
|
||||
(renderSlot) => renderSlot('k.single', {}, { fallback: <i>none</i> }))
|
||||
renderSlot => renderSlot('k.single', {}, { fallback: <i>none</i> }))
|
||||
expect(view.container.textContent).toBe('none')
|
||||
let dispose = () => {}
|
||||
act(() => { dispose = h.add('k.single', { component: () => <b>SB</b> }) })
|
||||
@@ -242,7 +242,7 @@ describe('child outlets and the renderSlot binding', () => {
|
||||
it('renders an undeclared key as empty (declaring entry unloaded = natural blank, not a crash)', () => {
|
||||
const h = makeHost()
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT },
|
||||
(renderSlot) => <main>{renderSlot('k.single', {}, { fallback: <i>fb</i> })}</main>)
|
||||
renderSlot => <main>{renderSlot('k.single', {}, { fallback: <i>fb</i> })}</main>)
|
||||
// Declared by children (authorization) but absent from the ledger (specOf
|
||||
// undefined): the outlet renders nothing, not even the fallback path's spec dispatch.
|
||||
expect(view.container.querySelector('main')!.textContent).toBe('')
|
||||
@@ -256,7 +256,7 @@ describe('child outlets and the renderSlot binding', () => {
|
||||
h.add('k.list', { component: () => <span>a</span>, options: { id: 'a', order: 1 } })
|
||||
h.add('k.keyed', { component: () => <span>goal</span>, options: { key: 'goal' } })
|
||||
const children = { 'k.list': { kind: 'list', scope: 'root' } as DeclaredSpec, 'k.keyed': { kind: 'keyed', scope: 'root' } as DeclaredSpec }
|
||||
const { view } = mountRoot(h, children, (renderSlot) => <>
|
||||
const { view } = mountRoot(h, children, renderSlot => <>
|
||||
<main>{renderSlot('k.list', {})}</main>
|
||||
<aside>{renderSlot('k.list', {}, { only: 'b' })}</aside>
|
||||
<nav>{renderSlot('k.keyed', {}, { entryKey: 'goal' })}</nav>
|
||||
@@ -291,7 +291,7 @@ describe('child outlets and the renderSlot binding', () => {
|
||||
h.add('k.list', { component: () => <span>alive</span>, options: { id: 'ok', order: 2 } })
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const { view } = mountRoot(h, { 'k.list': { kind: 'list', scope: 'root' } },
|
||||
(renderSlot) => renderSlot('k.list', {}))
|
||||
renderSlot => renderSlot('k.list', {}))
|
||||
spy.mockRestore()
|
||||
expect(view.container.textContent).toBe('alive')
|
||||
expect(view.container.querySelector('[data-slot-error]')).not.toBeNull()
|
||||
@@ -309,10 +309,10 @@ describe('chain outlets and the renderSlotChain binding', () => {
|
||||
}))
|
||||
h.add('k.chain', chainEntryOf({
|
||||
component: ({ matched }: { matched?: { label: string } }) => <b>{matched?.label}</b>,
|
||||
select: (owner) => ({ label: `hit:${(owner as { tag: string }).tag}` }),
|
||||
select: owner => ({ label: `hit:${(owner as { tag: string }).tag}` }),
|
||||
}))
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT },
|
||||
(renderSlotChain) => renderSlotChain('k.chain', { tag: 'T' }))
|
||||
renderSlotChain => renderSlotChain('k.chain', { tag: 'T' }))
|
||||
// The declining entry never mounts: the routing decision is select-layer only.
|
||||
expect(view.container.textContent).toBe('hit:T')
|
||||
expect(declinerBody).not.toHaveBeenCalled()
|
||||
@@ -327,10 +327,10 @@ describe('chain outlets and the renderSlotChain binding', () => {
|
||||
}))
|
||||
h.add('k.chain', chainEntryOf({
|
||||
component: ({ matched }: { matched?: string }) => <b>{matched}</b>,
|
||||
select: (owner) => (owner as { pick?: string }).pick ?? null,
|
||||
select: owner => (owner as { pick?: string }).pick ?? null,
|
||||
}))
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT }, (renderSlotChain) => <>
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT }, renderSlotChain => <>
|
||||
<main>{renderSlotChain('k.chain', { pick: 'OK' })}</main>
|
||||
<aside>{renderSlotChain('k.chain', {}, { fallback: <i>fb</i> })}</aside>
|
||||
</>)
|
||||
@@ -347,16 +347,16 @@ describe('chain outlets and the renderSlotChain binding', () => {
|
||||
h.declare('k.chain', CHAIN_ROOT)
|
||||
h.add('k.chain', chainEntryOf({
|
||||
component: () => { throw new Error('entry A boom') },
|
||||
select: (owner) => (owner as { pick?: string }).pick === 'A' ? {} : null,
|
||||
select: owner => (owner as { pick?: string }).pick === 'A' ? {} : null,
|
||||
}))
|
||||
h.add('k.chain', chainEntryOf({
|
||||
component: () => <b>B-ok</b>,
|
||||
select: (owner) => (owner as { pick?: string }).pick === 'B' ? {} : null,
|
||||
select: owner => (owner as { pick?: string }).pick === 'B' ? {} : null,
|
||||
}))
|
||||
let pick = 'A'
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT },
|
||||
(renderSlotChain) => renderSlotChain('k.chain', { pick }))
|
||||
renderSlotChain => renderSlotChain('k.chain', { pick }))
|
||||
spy.mockRestore()
|
||||
expect(view.container.querySelector('[data-slot-error]')).not.toBeNull()
|
||||
// Re-elect entry B: the entry-keyed boundary remounts fresh instead of
|
||||
@@ -372,9 +372,9 @@ describe('chain outlets and the renderSlotChain binding', () => {
|
||||
h.declare('k.chain', CHAIN_ROOT)
|
||||
h.add('k.chain', chainEntryOf({
|
||||
component: ({ matched }: { matched?: string }) => <b>{matched}</b>,
|
||||
select: (owner) => (owner as { pick?: string }).pick ?? null,
|
||||
select: owner => (owner as { pick?: string }).pick ?? null,
|
||||
}))
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT }, (renderSlotChain) => <>
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT }, renderSlotChain => <>
|
||||
<main>{renderSlotChain('k.chain', {}, { fallback: <i>bar</i> })}</main>
|
||||
<aside>{renderSlotChain('k.chain', { pick: 'P' }, { fallback: <i>bar</i> })}</aside>
|
||||
</>)
|
||||
@@ -387,7 +387,7 @@ describe('chain outlets and the renderSlotChain binding', () => {
|
||||
const h = makeHost()
|
||||
h.declare('k.chain', CHAIN_ROOT)
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT },
|
||||
(renderSlotChain) => renderSlotChain('k.chain', {}, { fallback: <i>none</i> }))
|
||||
renderSlotChain => renderSlotChain('k.chain', {}, { fallback: <i>none</i> }))
|
||||
expect(view.container.textContent).toBe('none')
|
||||
let dispose = () => {}
|
||||
act(() => {
|
||||
@@ -422,7 +422,7 @@ describe('chain outlets and the renderSlotChain binding', () => {
|
||||
priority: 1,
|
||||
}))
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT },
|
||||
(renderSlotChain) => renderSlotChain('k.chain', {}))
|
||||
renderSlotChain => renderSlotChain('k.chain', {}))
|
||||
expect(view.container.textContent).toBe('early')
|
||||
})
|
||||
|
||||
@@ -490,13 +490,13 @@ describe('overlay chains (ChainRenderOpts.overlay)', () => {
|
||||
h.declare('k.chain', CHAIN_ROOT)
|
||||
h.add('k.chain', chainEntryOf({
|
||||
component: () => <b>TAKEOVER</b>,
|
||||
select: (owner) => (owner as { take?: boolean }).take ? {} : null,
|
||||
select: owner => (owner as { take?: boolean }).take ? {} : null,
|
||||
}))
|
||||
const mounted = vi.fn()
|
||||
const Probe = fallbackProbe(mounted)
|
||||
let take = false
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT },
|
||||
(renderSlotChain) => renderSlotChain('k.chain', { take }, { fallback: <Probe />, overlay: true }))
|
||||
renderSlotChain => renderSlotChain('k.chain', { take }, { fallback: <Probe />, overlay: true }))
|
||||
const wrapper = () => view.container.querySelector<HTMLElement>('[data-chain-overlay-fallback="k.chain"]')!
|
||||
const input = () => view.container.querySelector<HTMLInputElement>('input[aria-label="probe"]')!
|
||||
|
||||
@@ -525,13 +525,13 @@ describe('overlay chains (ChainRenderOpts.overlay)', () => {
|
||||
h.declare('k.chain', CHAIN_ROOT)
|
||||
h.add('k.chain', chainEntryOf({
|
||||
component: () => <b>TAKEOVER</b>,
|
||||
select: (owner) => (owner as { take?: boolean }).take ? {} : null,
|
||||
select: owner => (owner as { take?: boolean }).take ? {} : null,
|
||||
}))
|
||||
const mounted = vi.fn()
|
||||
const Probe = fallbackProbe(mounted)
|
||||
let take = false
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT },
|
||||
(renderSlotChain) => renderSlotChain('k.chain', { take }, { fallback: <Probe /> }))
|
||||
renderSlotChain => renderSlotChain('k.chain', { take }, { fallback: <Probe /> }))
|
||||
fireEvent.change(view.container.querySelector('input[aria-label="probe"]')!, { target: { value: 'gone' } })
|
||||
expect(view.container.querySelector('[data-chain-overlay-fallback]')).toBeNull()
|
||||
|
||||
@@ -561,7 +561,7 @@ describe('overlay chains (ChainRenderOpts.overlay)', () => {
|
||||
priority: 2,
|
||||
}))
|
||||
const { view } = mountChainRoot(h, { 'k.chain': CHAIN_ROOT },
|
||||
(renderSlotChain) => renderSlotChain('k.chain', {}, { fallback: <i>resident</i>, overlay: true }))
|
||||
renderSlotChain => renderSlotChain('k.chain', {}, { fallback: <i>resident</i>, overlay: true }))
|
||||
expect(view.container.textContent).toContain('ELECTED')
|
||||
expect(spy.mock.calls.some(([msg]) => String(msg).includes('chain selector crashed'))).toBe(true)
|
||||
spy.mockRestore()
|
||||
@@ -578,9 +578,9 @@ describe('standard-kit synthesis', () => {
|
||||
h.declare('k.single', SINGLE_ROOT)
|
||||
h.add('k.single', {
|
||||
component: ({ useSessions }: { useSessions: <S>(sel: (s: { ids: string[] }) => S) => S }) =>
|
||||
<b>{useSessions((s) => s.ids.length)}</b>,
|
||||
<b>{useSessions(s => s.ids.length)}</b>,
|
||||
})
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT }, (renderSlot) => renderSlot('k.single', {}))
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT }, renderSlot => renderSlot('k.single', {}))
|
||||
expect(view.container.textContent).toBe('0')
|
||||
act(() => { h.list.set({ ids: ['a', 'b'] }) })
|
||||
expect(view.container.textContent).toBe('2')
|
||||
@@ -591,9 +591,9 @@ describe('standard-kit synthesis', () => {
|
||||
h.declare('k.single', SINGLE_ROOT)
|
||||
h.add('k.single', {
|
||||
component: ({ useWorkspaces }: { useWorkspaces: <S>(sel: (s: { ids: string[] }) => S) => S }) =>
|
||||
<b>{useWorkspaces((s) => s.ids.length)}</b>,
|
||||
<b>{useWorkspaces(s => s.ids.length)}</b>,
|
||||
})
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT }, (renderSlot) => renderSlot('k.single', {}))
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT }, renderSlot => renderSlot('k.single', {}))
|
||||
expect(view.container.textContent).toBe('0')
|
||||
act(() => { h.workspaces.set({ ids: ['w1'] }) })
|
||||
expect(view.container.textContent).toBe('1')
|
||||
@@ -606,11 +606,11 @@ describe('standard-kit synthesis', () => {
|
||||
const seen: AnyProps[] = []
|
||||
h.add('k.session', {
|
||||
component: (props: { useSession?: <S>(sel: (s: { sid: string }) => S) => S; sessionId?: string }) => {
|
||||
seen.push({ ...props, read: props.useSession!((s) => s.sid) })
|
||||
seen.push({ ...props, read: props.useSession!(s => s.sid) })
|
||||
return null
|
||||
},
|
||||
})
|
||||
mountRoot(h, { 'k.session': SINGLE_SESSION }, (renderSlot) => (
|
||||
mountRoot(h, { 'k.session': SINGLE_SESSION }, renderSlot => (
|
||||
<SessionProvider empty={() => <i>empty</i>}>
|
||||
{() => renderSlot('k.session', {})}
|
||||
</SessionProvider>
|
||||
@@ -669,14 +669,14 @@ describe('standard-kit synthesis', () => {
|
||||
h.declare('k.session', SINGLE_SESSION)
|
||||
h.add('k.session', { component: () => <b>x</b> })
|
||||
const { view } = mountRoot(h, { 'k.session': SINGLE_SESSION },
|
||||
(renderSlot) => renderSlot('k.session', {}))
|
||||
renderSlot => renderSlot('k.session', {}))
|
||||
expect(view.container.querySelector('b')).toBeNull()
|
||||
})
|
||||
|
||||
it('delivers the store pair for store-declaring entries and writes through baked actions', () => {
|
||||
const h = makeHost()
|
||||
h.declare('k.single', SINGLE_ROOT)
|
||||
const handle = miniStore(() => ({ n: 0 }), { inc: (s) => ({ n: s.n + 1 }) })
|
||||
const handle = miniStore(() => ({ n: 0 }), { inc: s => ({ n: s.n + 1 }) })
|
||||
let bump = () => {}
|
||||
h.add('k.single', {
|
||||
component: ({ useStore, actions }: {
|
||||
@@ -684,11 +684,11 @@ describe('standard-kit synthesis', () => {
|
||||
actions: { inc: () => void }
|
||||
}) => {
|
||||
bump = actions.inc
|
||||
return <b>{useStore((s) => s.n)}</b>
|
||||
return <b>{useStore(s => s.n)}</b>
|
||||
},
|
||||
store: handle,
|
||||
})
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT }, (renderSlot) => renderSlot('k.single', {}))
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT }, renderSlot => renderSlot('k.single', {}))
|
||||
expect(view.container.textContent).toBe('0')
|
||||
act(() => { bump() })
|
||||
expect(view.container.textContent).toBe('1')
|
||||
@@ -707,11 +707,11 @@ describe('standard-kit synthesis', () => {
|
||||
actions: { setDraft: (text: string) => void }
|
||||
}) => {
|
||||
setDraft = actions.setDraft
|
||||
return <b>{useStore((s) => s.draft) || '(blank)'}</b>
|
||||
return <b>{useStore(s => s.draft) || '(blank)'}</b>
|
||||
},
|
||||
store: handle,
|
||||
})
|
||||
const { view } = mountRoot(h, { 'k.session': SINGLE_SESSION }, (renderSlot) => (
|
||||
const { view } = mountRoot(h, { 'k.session': SINGLE_SESSION }, renderSlot => (
|
||||
<SessionProvider>{() => renderSlot('k.session', {})}</SessionProvider>
|
||||
))
|
||||
act(() => { h.current.set('s1') })
|
||||
@@ -730,7 +730,7 @@ describe('inject: execution point, parameter derivation, cache granularity', ()
|
||||
h.declare('k.single', SINGLE_ROOT)
|
||||
const inject = vi.fn(() => ({ tag: 'FROM-INJECT' }))
|
||||
h.add('k.single', { component: ({ tag }: { tag?: string }) => <b>{tag}</b>, inject })
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT }, (renderSlot) => renderSlot('k.single', {}))
|
||||
const { view } = mountRoot(h, { 'k.single': SINGLE_ROOT }, renderSlot => renderSlot('k.single', {}))
|
||||
expect(view.container.textContent).toBe('FROM-INJECT')
|
||||
act(() => { h.add('k.single', { component: () => null }) }) // sibling bump re-renders the outlet
|
||||
expect(inject).toHaveBeenCalledTimes(1)
|
||||
@@ -745,9 +745,9 @@ describe('inject: execution point, parameter derivation, cache granularity', ()
|
||||
const inject = vi.fn((sessionId: string) => ({ sid: sessionId }))
|
||||
h.add('k.session', {
|
||||
component: ({ sid }: { sid?: string }) => <b>{sid}</b>,
|
||||
inject: inject as unknown as StoredEntry['inject'],
|
||||
inject: inject,
|
||||
})
|
||||
const { view } = mountRoot(h, { 'k.session': SINGLE_SESSION }, (renderSlot) => (
|
||||
const { view } = mountRoot(h, { 'k.session': SINGLE_SESSION }, renderSlot => (
|
||||
<SessionProvider>{() => renderSlot('k.session', {})}</SessionProvider>
|
||||
))
|
||||
act(() => { h.current.set('s1') })
|
||||
@@ -767,22 +767,22 @@ describe('inject: execution point, parameter derivation, cache granularity', ()
|
||||
h.declare('k.single', SINGLE_ROOT)
|
||||
h.declare('k.session', SINGLE_SESSION)
|
||||
h.addSession('s1')
|
||||
const handle = miniStore(() => ({ n: 0 }), { inc: (s) => ({ n: s.n + 1 }) })
|
||||
const handle = miniStore(() => ({ n: 0 }), { inc: s => ({ n: s.n + 1 }) })
|
||||
const rootInject = vi.fn((actions: { inc: () => void }) => ({ viaRoot: actions }))
|
||||
const sessionInject = vi.fn((sessionId: string, actions: { inc: () => void }) => ({ sid: sessionId, viaSession: actions }))
|
||||
const seenRoot: AnyProps[] = []
|
||||
const seenSession: AnyProps[] = []
|
||||
h.add('k.single', {
|
||||
component: (props: object) => { seenRoot.push(props as AnyProps); return null },
|
||||
inject: rootInject as unknown as StoredEntry['inject'],
|
||||
inject: rootInject,
|
||||
store: handle,
|
||||
})
|
||||
h.add('k.session', {
|
||||
component: (props: object) => { seenSession.push(props as AnyProps); return null },
|
||||
inject: sessionInject as unknown as StoredEntry['inject'],
|
||||
inject: sessionInject,
|
||||
store: handle,
|
||||
})
|
||||
mountRoot(h, { 'k.single': SINGLE_ROOT, 'k.session': SINGLE_SESSION }, (renderSlot) => <>
|
||||
mountRoot(h, { 'k.single': SINGLE_ROOT, 'k.session': SINGLE_SESSION }, renderSlot => <>
|
||||
{renderSlot('k.single', {})}
|
||||
<SessionProvider>{() => renderSlot('k.session', {})}</SessionProvider>
|
||||
</>)
|
||||
@@ -807,7 +807,7 @@ describe('inject: execution point, parameter derivation, cache granularity', ()
|
||||
h.add('k.list', { component: () => <span>alive</span>, options: { id: 'ok', order: 2 } })
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const { view } = mountRoot(h, { 'k.list': { kind: 'list', scope: 'root' } },
|
||||
(renderSlot) => <main>{renderSlot('k.list', {})}</main>)
|
||||
renderSlot => <main>{renderSlot('k.list', {})}</main>)
|
||||
spy.mockRestore()
|
||||
// The failing entry blacks out alone; the sibling and the tree above survive.
|
||||
expect(view.container.querySelector('main')).not.toBeNull()
|
||||
@@ -824,7 +824,7 @@ describe('inject: execution point, parameter derivation, cache granularity', ()
|
||||
inject: () => ({ fromInject: 'inject', shared: 'inject' }),
|
||||
})
|
||||
mountRoot(h, { 'k.single': SINGLE_ROOT },
|
||||
(renderSlot) => renderSlot('k.single', { owner: 'owner', shared: 'owner' }))
|
||||
renderSlot => renderSlot('k.single', { owner: 'owner', shared: 'owner' }))
|
||||
const props = seen.at(-1)!
|
||||
expect(typeof props['useSessions']).toBe('function') // kit always present
|
||||
expect(typeof props['useWorkspaces']).toBe('function')
|
||||
|
||||
@@ -43,15 +43,15 @@ function makeHost(bodies: { root: (rp: (key: string, owner: object) => React.Rea
|
||||
const host: SlotRendererHost = {
|
||||
subscribe: () => () => {},
|
||||
getVersion: () => 0,
|
||||
entriesOf: (key) => key === 'root' ? [rootEntry] : sessionEntries,
|
||||
specOf: (key) => key === 'k.session' ? { kind: 'single', scope: 'session' } : undefined,
|
||||
entriesOf: key => key === 'root' ? [rootEntry] : sessionEntries,
|
||||
specOf: key => key === 'k.session' ? { kind: 'single', scope: 'session' } : undefined,
|
||||
isLive: () => true,
|
||||
storeOf: () => undefined,
|
||||
sessions: {
|
||||
list: observable<unknown>({ ids: [] }),
|
||||
current,
|
||||
provideInfo: (id) => infos.get(id),
|
||||
maybeProvideInfo: (id) => (id === undefined ? undefined : infos.get(id))
|
||||
provideInfo: id => infos.get(id),
|
||||
maybeProvideInfo: id => (id === undefined ? undefined : infos.get(id))
|
||||
?? { sessionId: undefined, hooks: { session: undefined }, props: {} },
|
||||
},
|
||||
workspaces: { list: observable<unknown>({ items: [] }) },
|
||||
@@ -78,7 +78,7 @@ describe('SessionProvider', () => {
|
||||
const h = makeHost({
|
||||
root: () => (
|
||||
<SessionProvider empty={() => <span>empty</span>}>
|
||||
{(id) => <div data-testid="body">{id}</div>}
|
||||
{id => <div data-testid="body">{id}</div>}
|
||||
</SessionProvider>
|
||||
),
|
||||
})
|
||||
@@ -93,7 +93,7 @@ describe('SessionProvider', () => {
|
||||
|
||||
it('renders null empty state when the empty prop is omitted', () => {
|
||||
const h = makeHost({
|
||||
root: () => <SessionProvider>{(id) => <b>{id}</b>}</SessionProvider>,
|
||||
root: () => <SessionProvider>{id => <b>{id}</b>}</SessionProvider>,
|
||||
})
|
||||
const view = render(<>{createSlotRenderer().renderRoot(h.host, {})}</>)
|
||||
expect(view.container.textContent).toBe('')
|
||||
@@ -110,7 +110,7 @@ describe('SessionProvider', () => {
|
||||
return <div>{id}</div>
|
||||
}
|
||||
const h = makeHost({
|
||||
root: () => <SessionProvider>{(id) => <Body id={id} />}</SessionProvider>,
|
||||
root: () => <SessionProvider>{id => <Body id={id} />}</SessionProvider>,
|
||||
})
|
||||
h.addSession('s1')
|
||||
h.addSession('s2')
|
||||
@@ -127,7 +127,7 @@ describe('SessionProvider', () => {
|
||||
it('delivers the resolved cell to session slots under it (observable behavior, not context internals)', () => {
|
||||
const seen: Record<string, unknown>[] = []
|
||||
const h = makeHost({
|
||||
root: (renderSlot) => <SessionProvider>{() => renderSlot('k.session', {})}</SessionProvider>,
|
||||
root: renderSlot => <SessionProvider>{() => renderSlot('k.session', {})}</SessionProvider>,
|
||||
})
|
||||
h.addSession('s1')
|
||||
h.addSession('s2')
|
||||
@@ -135,7 +135,7 @@ describe('SessionProvider', () => {
|
||||
component: (props: { useSession?: <S>(sel: (s: { sid: string }) => S) => S; sessionId?: string }) => {
|
||||
// The bound hook reads the cell's bare source — asserting through it
|
||||
// proves the machinery wired THIS session's source, not another's.
|
||||
seen.push({ sessionId: props.sessionId, read: props.useSession!((s) => s.sid) })
|
||||
seen.push({ sessionId: props.sessionId, read: props.useSession!(s => s.sid) })
|
||||
return null
|
||||
},
|
||||
options: {},
|
||||
@@ -152,7 +152,7 @@ describe('SessionProvider', () => {
|
||||
it('fails loud when mounted outside the renderer tree (no host channel)', () => {
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
expect(() => render(
|
||||
<SessionProvider>{(id) => <b>{id}</b>}</SessionProvider>,
|
||||
<SessionProvider>{id => <b>{id}</b>}</SessionProvider>,
|
||||
)).toThrow(/outside the installed renderer tree/)
|
||||
spy.mockRestore()
|
||||
})
|
||||
|
||||
@@ -32,10 +32,10 @@ function makeHost() {
|
||||
subs.set(key, set)
|
||||
return () => { set.delete(fn) }
|
||||
},
|
||||
getVersion: (key) => versions.get(key) ?? 0,
|
||||
entriesOf: (key) => entries.get(key) ?? [],
|
||||
getVersion: key => versions.get(key) ?? 0,
|
||||
entriesOf: key => entries.get(key) ?? [],
|
||||
specOf: () => ({ kind: 'single', scope: 'root' }),
|
||||
isLive: (entry) => live.has(entry),
|
||||
isLive: entry => live.has(entry),
|
||||
storeOf: () => undefined,
|
||||
sessions: {
|
||||
list: { getSnapshot: () => ({}), subscribe: () => () => {} },
|
||||
@@ -54,7 +54,7 @@ function makeHost() {
|
||||
live.add(entry)
|
||||
bump(key)
|
||||
return () => {
|
||||
entries.set(key, (entries.get(key) ?? []).filter((e) => e !== entry))
|
||||
entries.set(key, (entries.get(key) ?? []).filter(e => e !== entry))
|
||||
live.delete(entry)
|
||||
bump(key)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user