Help Modal

This commit is contained in:
th committed 2026-04-23 19:33:22 +02:00
1 parent 8e8aa5f27c
commit 8d987d1526
11 files changed
+2405 -58

No files matched your search

+116 -1
View File
@@ -1,4 +1,4 @@
import { createApp, h, nextTick, type Component, type Slot } from 'vue'
import { createApp, defineComponent, h, nextTick, ref, type Component, type Slot } from 'vue'
import {
createBlankProject,
createBlankScene,
@@ -46,6 +46,7 @@ import ActionEditor from '@/components/ActionEditor.vue'
import Timeline from '@/components/Timeline.vue'
import AssetSidebar from '@/components/AssetSidebar.vue'
import FullscreenPreview from '@/components/FullscreenPreview.vue'
import HelpDialog from '@/components/HelpDialog.vue'
import { createAudioRuntime } from '@/composables/useAudioRuntime'
import {
clearWaveformCache,
@@ -1813,6 +1814,120 @@ export function registerAllTests(): void {
})
})
suite('ui — HelpDialog', () => {
const snapshotDialogs = () =>
new Set(Array.from(document.querySelectorAll('[role="dialog"]')))
const dialogsAddedSince = (baseline: Set<Element>) =>
Array.from(document.querySelectorAll('[role="dialog"]')).filter(
(el) => !baseline.has(el),
) as HTMLElement[]
test('opens on modelValue=true and renders a TOC with all 13 chapters', async () => {
const baseline = snapshotDialogs()
const handle = mountComponent(HelpDialog, { modelValue: true })
await nextTick()
const added = dialogsAddedSince(baseline)
expect(added.length).toBe(1)
const tocItems = added[0].querySelectorAll('.toc-item')
expect(tocItems.length).toBe(13)
handle.unmount()
})
test('defaults to the Getting started chapter', async () => {
const baseline = snapshotDialogs()
const handle = mountComponent(HelpDialog, { modelValue: true })
await nextTick()
const dialog = dialogsAddedSince(baseline)[0]
const activeToc = dialog.querySelector('.toc-item.active') as HTMLElement
expect(activeToc).toBeTruthy()
expect(activeToc.textContent?.trim()).toBe('Getting started')
// Content heading matches.
const heading = dialog.querySelector('.chapter h3')
expect(heading?.textContent?.trim()).toBe('Getting started')
handle.unmount()
})
test('clicking a TOC item swaps the right-pane content', async () => {
const baseline = snapshotDialogs()
const handle = mountComponent(HelpDialog, { modelValue: true })
await nextTick()
const dialog = dialogsAddedSince(baseline)[0]
const items = Array.from(dialog.querySelectorAll('.toc-item')) as HTMLButtonElement[]
const scripting = items.find((el) => el.textContent?.includes('Script language'))
expect(scripting).toBeTruthy()
scripting!.click()
await nextTick()
const heading = dialog.querySelector('.chapter h3')
expect(heading?.textContent?.trim()).toBe('Script language')
// Active highlight moved.
const active = dialog.querySelector('.toc-item.active')
expect(active?.textContent?.trim()).toBe('Script language')
handle.unmount()
})
test('Script language chapter documents every DSL command', async () => {
// Pin the parser/help alignment — if a new command ships in
// scriptDsl.ts, this test reminds us to document it here too.
const baseline = snapshotDialogs()
const handle = mountComponent(HelpDialog, { modelValue: true })
await nextTick()
const dialog = dialogsAddedSince(baseline)[0]
const items = Array.from(dialog.querySelectorAll('.toc-item')) as HTMLButtonElement[]
items.find((el) => el.textContent?.includes('Script language'))!.click()
await nextTick()
const body = dialog.querySelector('.chapter')!.textContent ?? ''
expect(body).toContain('play')
expect(body).toContain('pause')
expect(body).toContain('rewind')
expect(body).toContain('jump')
expect(body).toContain('#')
expect(body).toContain('@')
expect(body).toContain('//')
handle.unmount()
})
test('Shortcuts chapter renders one row per shortcut with a kbd key', async () => {
const baseline = snapshotDialogs()
const handle = mountComponent(HelpDialog, { modelValue: true })
await nextTick()
const dialog = dialogsAddedSince(baseline)[0]
const items = Array.from(dialog.querySelectorAll('.toc-item')) as HTMLButtonElement[]
items.find((el) => el.textContent?.includes('Keyboard shortcuts'))!.click()
await nextTick()
const rows = dialog.querySelectorAll('.shortcuts li')
expect(rows.length).toBeGreaterThan(9)
// Every row ends with a kbd element.
for (const li of Array.from(rows)) {
expect(li.querySelector('kbd')).toBeTruthy()
}
handle.unmount()
})
test('reopening resets to the first chapter (no stale selection)', async () => {
// Regression: a user who leaves the dialog on "Script language"
// and reopens it expects to land on the intro, not mid-content.
const open = ref(true)
const Wrapper = defineComponent({
render: () => h(HelpDialog, { modelValue: open.value }),
})
const handle = mountComponent(Wrapper, {})
await nextTick()
const items = Array.from(
document.querySelectorAll('.toc-item'),
) as HTMLButtonElement[]
items.find((el) => el.textContent?.includes('Script language'))!.click()
await nextTick()
open.value = false
await nextTick()
open.value = true
await nextTick()
await nextTick()
const heading = document.querySelector('.chapter h3')
expect(heading?.textContent?.trim()).toBe('Getting started')
handle.unmount()
})
})
suite('ui — IconButton', () => {
test('renders a button element', async () => {
// IconButton now uses the custom v-tooltip directive instead of