Help Modal
This commit is contained in:
1 parent
8e8aa5f27c
commit
8d987d1526
11 files changed
+2405
-58
No files matched your search
@@ -2,7 +2,7 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { PhArrowClockwise, PhCheck, PhClipboardText, PhWarning, PhX } from '@phosphor-icons/vue'
|
||||
import { useSceneStore } from '@/stores/scene'
|
||||
import { t } from '@/i18n'
|
||||
import { t, useI18n } from '@/i18n'
|
||||
import { runAll, type TestResult } from './testRunner'
|
||||
import { registerAllTests } from './tests'
|
||||
|
||||
@@ -140,6 +140,13 @@ async function run() {
|
||||
clipboardStatus.value = 'idle'
|
||||
startedAt.value = performance.now()
|
||||
const snapshot = JSON.parse(JSON.stringify(store.$state))
|
||||
// The test harness asserts against English UI strings (aria-labels,
|
||||
// section titles, help dialog headings, "yet"/"unsaved" substrings).
|
||||
// Pin the locale to 'en' for the duration of the run so a German
|
||||
// browser default doesn't flip them all red. Restore on finally.
|
||||
const { locale } = useI18n()
|
||||
const prevLocale = locale.value
|
||||
locale.value = 'en'
|
||||
try {
|
||||
await runAll(registerAllTests, {
|
||||
onProgress: (d, t) => {
|
||||
@@ -160,6 +167,7 @@ async function run() {
|
||||
})
|
||||
} finally {
|
||||
store.$state = snapshot
|
||||
locale.value = prevLocale
|
||||
finishedAt.value = performance.now()
|
||||
running.value = false
|
||||
}
|
||||
|
||||
+116
-1
@@ -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
|
||||
|
||||
Reference in new issue
Block a user