mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 22:58:08 +00:00
## Summary Phase 2 of the dialog migration kicked off in #11719 and continued in #12041. Migrates four medium-complexity dialogs to the Reka-UI primitives. Public API of `useDialogService` / `dialogStore` is unchanged. Parent: [FE-571](https://linear.app/comfyorg/issue/FE-571/dialog-system-migration-primevue-reka-ui-parent) This phase: [FE-574](https://linear.app/comfyorg/issue/FE-574/phase-2-migrate-error-nodesearchbox-secretform-videohelp-customization) Predecessors: #11719 (Phase 0, merged), #12041 (Phase 1, merged) > **NodeSearchBoxPopover deferred** — host of an inner PrimeVue Dialog (filter panel) that teleports to body and conflicts with Reka's DismissableLayer outside-pointer detection (CI dismissed the outer dialog mid-interaction). Tracking as a follow-up PR; FE-574 stays open for it. ## Changes ### `src/services/dialogService.ts` | Call site | Renderer | Size | | --- | --- | --- | | `showExecutionErrorDialog()` | `'reka'` | `lg` | | `showErrorDialog()` | `'reka'` | `lg` | ### `src/components/dialog/content/ErrorDialogContent.vue` - Drops `import Divider from 'primevue/divider'` and `import ScrollPanel from 'primevue/scrollpanel'` - Replaces with `<hr class="border-t border-border-subtle">` + `<div class="h-[400px] w-full max-w-[80vw] overflow-auto">` ### Direct PrimeVue → Reka swaps (no `dialogStore` involvement) | File | Notes | | --- | --- | | `src/components/common/CustomizationDialog.vue` | Reka primitives + DialogTitle/Header/Footer; drops PrimeVue Divider; `:modal="false"` and `pointer-down-outside` overlay guard so the PrimeVue ColorPicker overlay (teleported to body) does not auto-dismiss the dialog | | `src/platform/assets/components/VideoHelpDialog.vue` | Headless Reka content; preserves capture-phase ESC by stopping propagation on `escape-key-down`; `VisuallyHidden` title for a11y | | `src/platform/secrets/components/SecretFormDialog.vue` | Reka primitives, retains `v-model:visible`, autofocus on the provider trigger, form submit/validation | ### Tests - `src/services/dialogService.renderer.test.ts`: extends the regression net to cover both error-dialog call sites (renderer `'reka'`, size `'lg'`) - `src/components/common/CustomizationDialog.test.ts`: swaps PrimeVue Dialog stub for Reka primitive stubs ### screenshot <img width="1236" height="761" alt="Screenshot 2026-05-11 at 10 26 51 PM" src="https://github.com/user-attachments/assets/086cb73f-a98d-41f8-96ee-21922da8dd73" /> <img width="1161" height="786" alt="Screenshot 2026-05-12 at 1 26 39 PM" src="https://github.com/user-attachments/assets/db7383d8-f737-4472-91c0-dab5aa41547b" /> ## Quality gates - [x] `pnpm typecheck` — clean - [x] `pnpm lint` — 0 errors (3 pre-existing warnings unrelated to this PR) - [x] `pnpm format` — applied - [x] `pnpm test:unit` (touched + adjacent areas): - `dialogService.renderer.test.ts` — 5/5 - `CustomizationDialog.test.ts` — 4/4 - All `src/components/dialog` tests — 73/73 - `src/platform/secrets` tests — 39/39 - `NodeBookmarkTreeExplorer.test.ts` — 7/7 - [ ] CI Playwright matrix ## Public API impact None. `useDialogService` / `dialogStore` signatures unchanged. Custom-node extensions calling `app.extensionManager.dialog.*` continue to work. ## Out of scope (later phases) - NodeSearchBoxPopover — follow-up PR under FE-574 - Settings dialog — Phase 3 (FE-575) - Manager dialog — Phase 4 (FE-576) - `ConfirmDialog` callers (`SecretsPanel`, `BaseWorkflowsSidebarTab`) — Phase 5 (FE-577) - Removing PrimeVue `Dialog`/`<style>` overrides in `GlobalDialog.vue` — Phase 6 (FE-578) ## Test plan - [x] Unit: 73/73 dialog-area, 39/39 secrets - [ ] CI: full Vitest + Playwright matrix - [ ] Manual on a backend: - Trigger an execution error → error dialog opens through Reka, scroll body, copy-to-clipboard, close - Add/edit a secret → form submits, validation errors render, ESC and cancel close - Open VideoHelpDialog from `UploadModelFooter` while inside the asset modal → ESC closes only the help dialog - Customize a node bookmark color/icon → apply/reset, color picker overlay works
118 lines
3.7 KiB
TypeScript
118 lines
3.7 KiB
TypeScript
import { render, screen } from '@testing-library/vue'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import { createI18n } from 'vue-i18n'
|
|
|
|
import CustomizationDialog from './CustomizationDialog.vue'
|
|
|
|
const DEFAULT_ICON = 'pi-bookmark-fill'
|
|
const DEFAULT_COLOR = '#a1a1aa'
|
|
|
|
vi.mock('@/stores/nodeBookmarkStore', () => ({
|
|
useNodeBookmarkStore: () => ({
|
|
defaultBookmarkIcon: DEFAULT_ICON,
|
|
defaultBookmarkColor: DEFAULT_COLOR,
|
|
bookmarksCustomization: {}
|
|
})
|
|
}))
|
|
|
|
vi.mock('primevue/selectbutton', () => ({
|
|
default: {
|
|
name: 'SelectButton',
|
|
template: '<div />',
|
|
props: ['modelValue', 'options']
|
|
}
|
|
}))
|
|
|
|
vi.mock('@/components/ui/dialog/Dialog.vue', () => ({
|
|
default: { name: 'Dialog', template: '<div><slot /></div>' }
|
|
}))
|
|
vi.mock('@/components/ui/dialog/DialogPortal.vue', () => ({
|
|
default: { name: 'DialogPortal', template: '<div><slot /></div>' }
|
|
}))
|
|
vi.mock('@/components/ui/dialog/DialogOverlay.vue', () => ({
|
|
default: { name: 'DialogOverlay', template: '<div />' }
|
|
}))
|
|
vi.mock('@/components/ui/dialog/DialogContent.vue', () => ({
|
|
default: { name: 'DialogContent', template: '<div><slot /></div>' }
|
|
}))
|
|
vi.mock('@/components/ui/dialog/DialogHeader.vue', () => ({
|
|
default: { name: 'DialogHeader', template: '<div><slot /></div>' }
|
|
}))
|
|
vi.mock('@/components/ui/dialog/DialogFooter.vue', () => ({
|
|
default: { name: 'DialogFooter', template: '<div><slot /></div>' }
|
|
}))
|
|
vi.mock('@/components/ui/dialog/DialogTitle.vue', () => ({
|
|
default: { name: 'DialogTitle', template: '<div><slot /></div>' }
|
|
}))
|
|
vi.mock('@/components/ui/dialog/DialogClose.vue', () => ({
|
|
default: { name: 'DialogClose', template: '<button />' }
|
|
}))
|
|
|
|
vi.mock('@/components/common/ColorCustomizationSelector.vue', () => ({
|
|
default: {
|
|
name: 'ColorCustomizationSelector',
|
|
template: '<div />',
|
|
props: ['modelValue', 'colorOptions']
|
|
}
|
|
}))
|
|
|
|
vi.mock('@/components/ui/button/Button.vue', () => ({
|
|
default: {
|
|
name: 'Button',
|
|
template: `<button @click="$emit('click')"><slot /></button>`,
|
|
emits: ['click']
|
|
}
|
|
}))
|
|
|
|
const i18n = createI18n({ legacy: false, locale: 'en', messages: { en: {} } })
|
|
|
|
function renderDialog(extraProps: Record<string, unknown> = {}) {
|
|
const onConfirm = vi.fn()
|
|
render(CustomizationDialog, {
|
|
global: { plugins: [i18n] },
|
|
props: { modelValue: true, onConfirm, ...extraProps }
|
|
})
|
|
return { onConfirm }
|
|
}
|
|
|
|
describe('CustomizationDialog', () => {
|
|
describe('confirmCustomization', () => {
|
|
it('emits confirm with default icon and color when no initial values provided', async () => {
|
|
const user = userEvent.setup()
|
|
const { onConfirm } = renderDialog()
|
|
|
|
await user.click(screen.getByText('g.confirm'))
|
|
|
|
expect(onConfirm).toHaveBeenCalledWith(DEFAULT_ICON, DEFAULT_COLOR)
|
|
})
|
|
|
|
it('emits confirm with matching initialIcon when provided', async () => {
|
|
const user = userEvent.setup()
|
|
const { onConfirm } = renderDialog({ initialIcon: 'pi-star' })
|
|
|
|
await user.click(screen.getByText('g.confirm'))
|
|
|
|
expect(onConfirm).toHaveBeenCalledWith('pi-star', DEFAULT_COLOR)
|
|
})
|
|
|
|
it('falls back to default icon when initialIcon does not match any option', async () => {
|
|
const user = userEvent.setup()
|
|
const { onConfirm } = renderDialog({ initialIcon: 'pi-nonexistent' })
|
|
|
|
await user.click(screen.getByText('g.confirm'))
|
|
|
|
expect(onConfirm).toHaveBeenCalledWith(DEFAULT_ICON, DEFAULT_COLOR)
|
|
})
|
|
|
|
it('emits confirm with initialColor when provided', async () => {
|
|
const user = userEvent.setup()
|
|
const { onConfirm } = renderDialog({ initialColor: '#007bff' })
|
|
|
|
await user.click(screen.getByText('g.confirm'))
|
|
|
|
expect(onConfirm).toHaveBeenCalledWith(DEFAULT_ICON, '#007bff')
|
|
})
|
|
})
|
|
})
|