Files
ComfyUI_frontend/src/services/dialogService.renderer.test.ts
Dante 90c523b4a3 feat(dialog): flip remaining callers + default renderer to Reka (Phase 6a cutover, stacked on #12848. 6a-2) (#12593)
## Summary

The **renderer cutover** for Phase 6: every remaining dialog caller is
flipped to Reka, and `createDialog` now defaults `renderer: 'reka'` so
the PrimeVue `Dialog` branch is no longer reached by default (it
survives only as an explicit `renderer: 'primevue'` escape hatch,
deleted in Phase 6b).

> **Stacked on #12848** (mask editor + 3D viewer dialogs + dialog
infra). Per @jtydhr88's review, the heavy, screenshot-bearing surface
(3D + mask editor) was split into #12848 so it reviews and tests on its
own. **Merge #12848 first**, then this PR's base auto-retargets to
`main`.

Parent:
[FE-571](https://linear.app/comfyorg/issue/FE-571/dialog-system-migration-primevue-reka-ui-parent)
This phase:
[FE-578](https://linear.app/comfyorg/issue/FE-578/phase-6-remove-primevue-dialogconfirmdialog-imports-clean-up-css)

## Changes

- **drop dead `ConfirmationService` registration** — zero
`useConfirm`/`<ConfirmDialog>` consumers remain in `src/`; desktop-ui
keeps its own.
- **flip `showConfirmDialog`** — all six confirm callers render Reka
chrome; width goes from PrimeVue auto-hug to fixed `size:'md'`, matching
`dialogService.confirm()`.
- **flip remaining `dialogService` + composable callers** — signin,
update-password, top-up, workspace family, cancel-subscription, publish,
cloud-notification, edit-keybinding / node-conflict / import-failed,
upload-model, queue-clear-history, delete-assets, share /
open-shared-workflow, subscription pricing. Self-styled panels get a
shared transparent `w-fit` chrome replicating PrimeVue's auto-sized
root.
- **default `createDialog` to `renderer:'reka'`** — cuts over
`showExtensionDialog` (third-party dialogs) and anything unflagged. The
single-commit revert point.
- **retarget class-based e2e selectors** — `BaseDialog` `.p-dialog` →
`getByRole('dialog')`, `BuilderSaveAsHelper` close-X → `getByLabel`,
`shareWorkflowDialog` role-based, dead `confirm-dialog` testid removed.
- honor `[autofocus]` inside Reka dialogs; size the template browser
dialog so the filter bar fits; drop redundant Tailwind width constraints
on the remaining callers.

## Review focus

1. **`modal:false` on the pricing dialogs** — same trade-off as
Settings/Manager (visual overlay without focus trap) because
`PricingTable(.Workspace)` hosts a body-teleported PrimeVue `Popover`.
2. **`w-fit` shrink-wrapped chrome** for self-styled panels — replicates
PrimeVue's shrink-to-fit root.
3. **Confirm width change** (auto-hug → fixed 576px `md`) — intentional
consistency with `dialogService.confirm()`.

## Public API impact

`createDialog` now defaults to Reka. Third-party extension dialogs
render through Reka by default — a fixed `size:'md'` frame with a modal
focus trap instead of PrimeVue auto-width; `renderer:'primevue'` remains
an explicit escape hatch until Phase 6b. Worth a release note for
extension authors.

## Out of scope (Phase 6b)

PrimeVue branch deletion (`GlobalDialog.vue` legacy branch,
`PrimeDialog` import, `.p-dialog` CSS/bridge tokens, `dialogStore`
`pt`/`position`/`unstyled` typing) — lands after this soaks one cloud
deploy cycle.

## 📸 Screenshots — manual verification

Captured via Chrome DevTools (CDP) from this branch running locally in
**cloud mode** (proxied to the `cloud.comfy.org` backend, free Personal
Workspace). Every dialog below now renders through the **Reka** path —
the PrimeVue `Dialog` branch is no longer reached. (Mask editor + 3D
viewers live in the stacked base #12848.)

**Confirm dialog** (`showConfirmDialog`) — Reka chrome at a fixed
`size:'md'`, replacing PrimeVue's auto-hug width — *review focus #3*
<img width="880" alt="confirm-dialog"
src="https://github.com/user-attachments/assets/5d9953c1-4d0c-4ff9-adc7-88dd370c6a24"
/>

**Settings** — renders through Reka
<img width="880" alt="settings"
src="https://github.com/user-attachments/assets/44e3fd3f-8d9b-4322-8fbe-8ce8d94ed15d"
/>

**Edit Keybinding**, stacked on Settings — small-layout `w-fit` chrome;
closing it leaves Settings open (stacked-dismiss holds)
<img width="880" alt="edit-keybinding-nested"
src="https://github.com/user-attachments/assets/d0875c00-7b9c-439d-b24d-ba6770009d08"
/>

**Subscription pricing** (`PricingTable`) — opened with `modal:false`
because it hosts a body-teleported PrimeVue `Popover` — *review focus
#1*
<img width="880" alt="subscription-pricing"
src="https://github.com/user-attachments/assets/3be20397-8a69-4b00-b803-73eff4e0e313"
/>

**Share** and **Publish** (open-shared-workflow + publish) — shared
transparent shrink-wrapped (`w-fit`) chrome — *review focus #2*
<img width="880" alt="share-dialog"
src="https://github.com/user-attachments/assets/16f1c1b5-e35e-4664-a957-2f7f61ad96bd"
/>
<img width="880" alt="publish-dialog"
src="https://github.com/user-attachments/assets/935ff453-5247-430f-9c21-2f500d4bc6e2"
/>

**Workspace** (workspace-family callers)
<img width="880" alt="workspace-settings"
src="https://github.com/user-attachments/assets/8031a352-f6fc-41e4-9567-e26e0c35ecd9"
/>

**Template selector** (`showExtensionDialog` /
`useWorkflowTemplateSelectorDialog`)
<img width="880" alt="templates-dialog"
src="https://github.com/user-attachments/assets/9975ebbe-75ae-4ad9-a90a-248db4850e1a"
/>

**Account / workspace menu** (cloud)
<img width="880" alt="account-menu"
src="https://github.com/user-attachments/assets/5bc0cade-9bd9-49de-8bb4-779d65e211b0"
/>
2026-06-20 00:12:01 +00:00

135 lines
4.9 KiB
TypeScript

/**
* Dialog migration regression net: when callers in `dialogService` open a
* Reka-migrated dialog, the dialog stack item must carry `renderer: 'reka'`.
* Catches accidental reverts of the Reka renderer flip.
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
const showDialog = vi.hoisted(() => vi.fn())
vi.mock('@/stores/dialogStore', () => ({
useDialogStore: () => ({ showDialog })
}))
vi.mock('@/i18n', () => ({
t: (key: string) => key
}))
vi.mock('@/platform/telemetry', () => ({
useTelemetry: () => ({ trackEvent: vi.fn() })
}))
vi.mock('@/platform/distribution/types', () => ({
isCloud: false
}))
vi.mock('@/composables/billing/useBillingContext', () => ({
useBillingContext: () => ({
isActiveSubscription: { value: true },
isFreeTier: { value: false },
type: { value: 'legacy' }
})
}))
import { useDialogService } from '@/services/dialogService'
describe('dialogService Reka renderer opt-in', () => {
beforeEach(() => {
showDialog.mockReset()
})
it("prompt() sets renderer 'reka' and size 'md'", () => {
void useDialogService().prompt({ title: 'T', message: 'M' })
const [args] = showDialog.mock.calls[0]
expect(args.dialogComponentProps.renderer).toBe('reka')
expect(args.dialogComponentProps.size).toBe('md')
})
it("confirm() sets renderer 'reka' and size 'md'", () => {
void useDialogService().confirm({ title: 'T', message: 'M' })
const [args] = showDialog.mock.calls[0]
expect(args.dialogComponentProps.renderer).toBe('reka')
expect(args.dialogComponentProps.size).toBe('md')
})
it("showBillingComingSoonDialog() sets renderer 'reka', size 'sm', and 360px contentClass", () => {
useDialogService().showBillingComingSoonDialog()
const [args] = showDialog.mock.calls[0]
expect(args.dialogComponentProps.renderer).toBe('reka')
expect(args.dialogComponentProps.size).toBe('sm')
expect(args.dialogComponentProps.contentClass).toBe('max-w-[360px]')
})
it("showExecutionErrorDialog() sets renderer 'reka' and size 'lg'", () => {
useDialogService().showExecutionErrorDialog({
exception_type: 'RuntimeError',
exception_message: 'boom',
node_id: 1,
node_type: 'KSampler',
traceback: ['line 1', 'line 2']
})
const [args] = showDialog.mock.calls[0]
expect(args.dialogComponentProps.renderer).toBe('reka')
expect(args.dialogComponentProps.size).toBe('lg')
})
it("showErrorDialog() sets renderer 'reka' and size 'lg'", () => {
useDialogService().showErrorDialog(new Error('boom'))
const [args] = showDialog.mock.calls[0]
expect(args.dialogComponentProps.renderer).toBe('reka')
expect(args.dialogComponentProps.size).toBe('lg')
})
it("showTopUpCreditsDialog() sets renderer 'reka' with a transparent shrink-wrapped chrome", async () => {
await useDialogService().showTopUpCreditsDialog()
const [args] = showDialog.mock.calls[0]
expect(args.dialogComponentProps.renderer).toBe('reka')
expect(args.dialogComponentProps.headless).toBe(true)
expect(args.dialogComponentProps.pt).toBeUndefined()
expect(args.dialogComponentProps.contentClass).toContain('w-fit')
expect(args.dialogComponentProps.contentClass).toContain('bg-transparent')
})
it("showLayoutDialog() defaults to renderer 'reka' headless without pt", () => {
const Component = { template: '<div />' }
useDialogService().showLayoutDialog({
key: 'layout-test',
component: Component,
props: {}
})
const [args] = showDialog.mock.calls[0]
expect(args.dialogComponentProps.renderer).toBe('reka')
expect(args.dialogComponentProps.headless).toBe(true)
expect(args.dialogComponentProps.pt).toBeUndefined()
})
it('showLayoutDialog() lets callers override the defaults', () => {
const Component = { template: '<div />' }
useDialogService().showLayoutDialog({
key: 'layout-override-test',
component: Component,
props: {},
dialogComponentProps: { closable: false, contentClass: 'w-170' }
})
const [args] = showDialog.mock.calls[0]
expect(args.dialogComponentProps.renderer).toBe('reka')
expect(args.dialogComponentProps.closable).toBe(false)
expect(args.dialogComponentProps.contentClass).toBe('w-170')
})
it("showSmallLayoutDialog() sets renderer 'reka' with zeroed section padding", () => {
const Component = { template: '<div />' }
useDialogService().showSmallLayoutDialog({
key: 'small-layout-test',
component: Component
})
const [args] = showDialog.mock.calls[0]
expect(args.dialogComponentProps.renderer).toBe('reka')
expect(args.dialogComponentProps.pt).toBeUndefined()
expect(args.dialogComponentProps.contentClass).toContain('w-fit')
expect(args.dialogComponentProps.headerClass).toBe('p-0')
expect(args.dialogComponentProps.bodyClass).toBe('p-0 overflow-y-hidden')
expect(args.dialogComponentProps.footerClass).toBe('p-0')
})
})