Compare commits

...

2 Commits

Author SHA1 Message Date
pythongosssss
dd0171e9b3 Merge branch 'main' into pysssss/template-truncated-tooltips 2026-06-09 19:37:35 +01:00
pythongosssss
10c17ae622 fix: add text ticker and tooltip to truncated template text 2026-06-09 05:58:36 -07:00
4 changed files with 140 additions and 28 deletions

View File

@@ -452,6 +452,76 @@ test.describe('Templates', { tag: ['@slow', '@workflow'] }, () => {
}
)
test('long title scrolls and description shows a hover tooltip', async ({
comfyPage
}) => {
const longTitle =
'An Extremely Long Template Title That Overflows The Card And Must Scroll On Hover'
const longDescription =
'A deliberately long template description that is clamped to two lines on the card so the full text is only visible through the hover tooltip.'
await comfyPage.page.route('**/templates/index.json', async (route) => {
const response: WorkflowTemplates[] = [
{
moduleName: 'default',
title: 'Test Templates',
type: 'image',
templates: [
{
name: 'long-content',
title: longTitle,
mediaType: 'image',
mediaSubtype: 'webp',
description: longDescription
}
]
}
]
await route.fulfill({
status: 200,
body: JSON.stringify(response),
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store'
}
})
})
await comfyPage.page.route('**/templates/**.webp', async (route) => {
await route.fulfill({
status: 200,
path: 'browser_tests/assets/example.webp',
headers: {
'Content-Type': 'image/webp',
'Cache-Control': 'no-store'
}
})
})
await comfyPage.command.executeCommand('Comfy.BrowseTemplates')
const card = comfyPage.page.getByTestId('template-workflow-long-content')
await expect(card).toBeVisible()
// Title is rendered inside a TextTicker that scrolls the overflowing text
// on hover instead of clamping it.
const titleTicker = card
.getByRole('heading', { name: longTitle })
.locator('div')
await titleTicker.hover()
await expect
.poll(() => titleTicker.evaluate((el) => el.scrollLeft))
.toBeGreaterThan(0)
// Description is clamped on the card; hovering reveals the full text in a
// tooltip rather than a native title attribute.
const description = card.locator('p.text-muted')
await expect(description).not.toHaveAttribute('title')
await description.hover()
await expect(comfyPage.page.getByRole('tooltip')).toContainText(
longDescription
)
})
test('Can open associated tutorial', async ({ comfyPage }) => {
const tutorialUrl = 'https://comfyanonymous.github.io/ComfyUI_examples/'
await comfyPage.page.route('**/templates/index.json', async (route) => {

View File

@@ -286,27 +286,26 @@
<template #bottom>
<CardBottom>
<div class="flex flex-col gap-2 pt-3">
<h3
class="m-0 line-clamp-1 text-sm"
:title="
getTemplateTitle(
template,
getEffectiveSourceModule(template)
)
"
>
{{
getTemplateTitle(
template,
getEffectiveSourceModule(template)
)
}}
<h3 class="m-0 text-sm">
<TextTicker>
{{
getTemplateTitle(
template,
getEffectiveSourceModule(template)
)
}}
</TextTicker>
</h3>
<div class="flex justify-between gap-2">
<div class="flex-1">
<p
v-tooltip.bottom="
buildTooltipConfig(
getTemplateDescription(template),
true
)
"
class="m-0 line-clamp-2 text-sm text-muted"
:title="getTemplateDescription(template)"
>
{{ getTemplateDescription(template) }}
</p>
@@ -412,6 +411,7 @@ import CardBottom from '@/components/card/CardBottom.vue'
import CardContainer from '@/components/card/CardContainer.vue'
import CardTop from '@/components/card/CardTop.vue'
import Tag from '@/components/chip/Tag.vue'
import TextTicker from '@/components/common/TextTicker.vue'
import AsyncSearchInput from '@/components/ui/search-input/AsyncSearchInput.vue'
import MultiSelect from '@/components/ui/multi-select/MultiSelect.vue'
import SingleSelect from '@/components/ui/single-select/SingleSelect.vue'
@@ -427,6 +427,7 @@ import { useIntersectionObserver } from '@/composables/useIntersectionObserver'
import { useLazyPagination } from '@/composables/useLazyPagination'
import { usePrimeVueOverlayChildStyle } from '@/composables/usePopoverSizing'
import { useTemplateFiltering } from '@/composables/useTemplateFiltering'
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useTemplateWorkflows } from '@/platform/workflow/templates/composables/useTemplateWorkflows'

View File

@@ -0,0 +1,32 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { buildTooltipConfig } from './useTooltipConfig'
const { getSetting } = vi.hoisted(() => ({ getSetting: vi.fn() }))
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: () => ({ get: getSetting })
}))
describe('buildTooltipConfig', () => {
beforeEach(() => {
getSetting.mockReset()
})
it('keeps a fixed delay and avoids the store by default', () => {
const config = buildTooltipConfig('Cancel job')
expect(config.value).toBe('Cancel job')
expect(config.showDelay).toBe(300)
expect(getSetting).not.toHaveBeenCalled()
})
it('uses the configured tooltip delay when global delay is enabled', () => {
getSetting.mockReturnValue(150)
const config = buildTooltipConfig('Cancel job', true)
expect(getSetting).toHaveBeenCalledWith('LiteGraph.Node.TooltipDelay')
expect(config.showDelay).toBe(150)
})
})

View File

@@ -1,18 +1,27 @@
import { useSettingStore } from '@/platform/settings/settingStore'
const DEFAULT_SHOW_DELAY = 300
const TOOLTIP_PT = {
text: {
class:
'border-node-component-tooltip-border bg-node-component-tooltip-surface text-node-component-tooltip border rounded-md px-2 py-1 text-xs leading-none shadow-none'
},
arrow: {
class: 'border-t-node-component-tooltip-border'
}
}
/**
* Build a tooltip configuration object compatible with v-tooltip.
* Consumers pass the translated text value.
* Consumers pass the translated text value. Pass `useGlobalDelay` to honor the
* `LiteGraph.Node.TooltipDelay` setting.
*/
export const buildTooltipConfig = (value: string) => ({
export const buildTooltipConfig = (value: string, useGlobalDelay = false) => ({
value,
showDelay: 300,
showDelay: useGlobalDelay
? useSettingStore().get('LiteGraph.Node.TooltipDelay')
: DEFAULT_SHOW_DELAY,
hideDelay: 0,
pt: {
text: {
class:
'border-node-component-tooltip-border bg-node-component-tooltip-surface text-node-component-tooltip border rounded-md px-2 py-1 text-xs leading-none shadow-none'
},
arrow: {
class: 'border-t-node-component-tooltip-border'
}
}
pt: TOOLTIP_PT
})