0.10.6: setButtonEnabled action — disable buttons from the timeline
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d3c5b8b640
commit
0bbef66192
8 files changed
+221
-2
No files matched your search
@@ -4233,6 +4233,103 @@ export function registerAllTests(): void {
|
||||
})
|
||||
})
|
||||
|
||||
suite('ui — ActionEditor (setButtonEnabled)', () => {
|
||||
test('setButtonEnabled appears in the type dropdown', async () => {
|
||||
const s = makeFreshStore()
|
||||
void s
|
||||
const handle = mountComponent(ActionEditor, {
|
||||
actions: [{ type: 'pause' } as const],
|
||||
})
|
||||
await nextTick()
|
||||
const select = handle.host.querySelector('.type-select') as HTMLSelectElement
|
||||
const values = Array.from(select.options).map((o) => o.value)
|
||||
expect(values.includes('setButtonEnabled')).toBe(true)
|
||||
handle.unmount()
|
||||
})
|
||||
|
||||
test('switching a row to setButtonEnabled seeds it with the first button layer + enabled=false', async () => {
|
||||
const s = makeFreshStore()
|
||||
const btn = s.addButtonAsset('Play', 160, 60)
|
||||
const layer = s.addLayer(btn.id)!
|
||||
let latest: unknown = null
|
||||
const handle = mountComponent(ActionEditor, {
|
||||
actions: [{ type: 'pause' } as const],
|
||||
'onUpdate:actions': (v: unknown) => {
|
||||
latest = v
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
const select = handle.host.querySelector('.type-select') as HTMLSelectElement
|
||||
select.value = 'setButtonEnabled'
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
await nextTick()
|
||||
const next = (latest as unknown[])[0] as {
|
||||
type: string
|
||||
layerId?: string
|
||||
enabled?: boolean
|
||||
}
|
||||
expect(next.type).toBe('setButtonEnabled')
|
||||
expect(next.layerId).toBe(layer.id)
|
||||
// Defaults to "disable" — the common use case.
|
||||
expect(next.enabled).toBe(false)
|
||||
handle.unmount()
|
||||
})
|
||||
|
||||
test('button dropdown lists one entry per button layer across scenes', async () => {
|
||||
const s = makeFreshStore()
|
||||
const btn1 = s.addButtonAsset('Play', 160, 60)
|
||||
const layer1 = s.addLayer(btn1.id)!
|
||||
s.addScene('Other')
|
||||
const btn2 = s.addButtonAsset('Stop', 160, 60)
|
||||
const layer2 = s.addLayer(btn2.id)!
|
||||
let latest: unknown = null
|
||||
const handle = mountComponent(ActionEditor, {
|
||||
actions: [{ type: 'setButtonEnabled', layerId: layer1.id, enabled: false } as const],
|
||||
'onUpdate:actions': (v: unknown) => {
|
||||
latest = v
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
const selects = handle.host.querySelectorAll('select')
|
||||
// action-type dropdown + button-layer dropdown
|
||||
expect(selects.length).toBe(2)
|
||||
const btnSelect = selects[1] as HTMLSelectElement
|
||||
const ids = Array.from(btnSelect.options).map((o) => o.value)
|
||||
expect(ids.sort()).toEqual([layer1.id, layer2.id].sort())
|
||||
btnSelect.value = layer2.id
|
||||
btnSelect.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
await nextTick()
|
||||
const next = (latest as unknown[])[0] as { layerId: string; enabled: boolean }
|
||||
expect(next.layerId).toBe(layer2.id)
|
||||
expect(next.enabled).toBe(false)
|
||||
handle.unmount()
|
||||
})
|
||||
|
||||
test('enabled checkbox round-trips true/false into the emitted action', async () => {
|
||||
const s = makeFreshStore()
|
||||
const btn = s.addButtonAsset('Play', 160, 60)
|
||||
const layer = s.addLayer(btn.id)!
|
||||
let latest: unknown = null
|
||||
const handle = mountComponent(ActionEditor, {
|
||||
actions: [{ type: 'setButtonEnabled', layerId: layer.id, enabled: false } as const],
|
||||
'onUpdate:actions': (v: unknown) => {
|
||||
latest = v
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
const cb = handle.host.querySelector('input[type="checkbox"]') as HTMLInputElement
|
||||
expect(cb).toBeTruthy()
|
||||
expect(cb.checked).toBe(false)
|
||||
cb.checked = true
|
||||
cb.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
await nextTick()
|
||||
const next = (latest as unknown[])[0] as { layerId: string; enabled: boolean }
|
||||
expect(next.layerId).toBe(layer.id)
|
||||
expect(next.enabled).toBe(true)
|
||||
handle.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
suite('ui — Timeline audio track', () => {
|
||||
test('audio track renders as an .audio-track lane with one block per clip', async () => {
|
||||
const s = makeFreshStore()
|
||||
@@ -6348,6 +6445,29 @@ export function registerAllTests(): void {
|
||||
expect(rt.warnings[0].toLowerCase()).toContain('does not support')
|
||||
})
|
||||
|
||||
test('setButtonEnabled dispatches setButtonEnabled on the runtime', () => {
|
||||
const flips: { layerId: string; enabled: boolean }[] = []
|
||||
const rt = makeMockRuntime({
|
||||
setButtonEnabled(layerId: string, enabled: boolean) {
|
||||
flips.push({ layerId, enabled })
|
||||
},
|
||||
})
|
||||
evalAction({ type: 'setButtonEnabled', layerId: 'L-1', enabled: false }, rt)
|
||||
evalAction({ type: 'setButtonEnabled', layerId: 'L-1', enabled: true }, rt)
|
||||
expect(flips).toEqual([
|
||||
{ layerId: 'L-1', enabled: false },
|
||||
{ layerId: 'L-1', enabled: true },
|
||||
])
|
||||
expect(rt.warnings.length).toBe(0)
|
||||
})
|
||||
|
||||
test('setButtonEnabled warns when the runtime omits the handler', () => {
|
||||
const rt = makeMockRuntime() // no setButtonEnabled
|
||||
evalAction({ type: 'setButtonEnabled', layerId: 'L-1', enabled: false }, rt)
|
||||
expect(rt.warnings.length).toBe(1)
|
||||
expect(rt.warnings[0].toLowerCase()).toContain('does not support')
|
||||
})
|
||||
|
||||
test('script action parses and dispatches sub-actions (no warn on valid DSL)', () => {
|
||||
const rt = makeMockRuntime()
|
||||
evalAction({ type: 'script', source: 'play\npause' }, rt)
|
||||
@@ -6783,6 +6903,15 @@ export function registerAllTests(): void {
|
||||
expect(r).toContain('buttonLayerActiveAt')
|
||||
})
|
||||
|
||||
test('runtime engine evalAction handles setButtonEnabled via runtime.buttonDisabled', () => {
|
||||
// Pin the engine source so the setButtonEnabled case both exists
|
||||
// and writes into the runtime.buttonDisabled map — the same field
|
||||
// the click-dispatcher reads to early-return on disabled buttons.
|
||||
const r = generateRuntimeScript()
|
||||
expect(r).toContain("case 'setButtonEnabled'")
|
||||
expect(r).toContain('runtime.buttonDisabled[act.layerId]')
|
||||
})
|
||||
|
||||
test('timeline segments skip the connector line before a still-frame kf in button mode', async () => {
|
||||
// In button-edit the state frames are discrete: the connector
|
||||
// line that spans between kfs on a regular timeline must NOT
|
||||
@@ -7135,6 +7264,22 @@ export function registerAllTests(): void {
|
||||
expect(rt.getEffectiveTime('btn-d', false)).toBe(BUTTON_STATE_TIMES.normal)
|
||||
rt.clear('btn-d')
|
||||
})
|
||||
|
||||
test('store actionRuntime.setButtonEnabled toggles useButtonRuntime disabled state', async () => {
|
||||
const mod = await import('@/composables/useButtonRuntime')
|
||||
const btnRt = mod.useButtonRuntime()
|
||||
const s = makeFreshStore()
|
||||
const btn = s.addButtonAsset('B', 100, 40)
|
||||
const layer = s.addLayer(btn.id)!
|
||||
btnRt.clear(layer.id)
|
||||
const actionRt = s.actionRuntime()
|
||||
expect(typeof actionRt.setButtonEnabled).toBe('function')
|
||||
actionRt.setButtonEnabled!(layer.id, false)
|
||||
expect(btnRt.getEffective(layer.id, false)).toBe('disabled')
|
||||
actionRt.setButtonEnabled!(layer.id, true)
|
||||
expect(btnRt.getEffective(layer.id, false)).toBe('normal')
|
||||
btnRt.clear(layer.id)
|
||||
})
|
||||
})
|
||||
|
||||
suite('button asset — thumbnail rendering', () => {
|
||||
|
||||
Reference in new issue
Block a user