Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/platform/missingMedia/components/MissingMediaCard.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { render, screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'

import { testI18n } from '@/components/searchbox/v2/__test__/testUtils'
Expand Down Expand Up @@ -58,4 +59,42 @@ describe('MissingMediaCard', () => {
).toBeInTheDocument()
expect(screen.queryAllByLabelText(/&(?:amp|lt|gt);/)).toHaveLength(0)
})

it('emits locateNode with the node id when the locate button is clicked', async () => {
const user = userEvent.setup()
vi.mocked(getNodeByExecutionId).mockReturnValue(
createMockLGraphNode({ title: 'Load Image' })
)
const missingMediaGroups: MissingMediaGroup[] = [
{
mediaType: 'image',
items: [
{
name: 'image.png',
mediaType: 'image',
representative: {
nodeId: '5',
nodeType: 'LoadImage',
widgetName: 'image',
mediaType: 'image',
name: 'image.png',
isMissing: true
},
referencingNodes: [
{ nodeId: '5', nodeType: 'LoadImage', widgetName: 'image' }
]
}
]
}
]

const { emitted } = render(MissingMediaCard, {
props: { missingMediaGroups },
global: { plugins: [testI18n] }
})

await user.click(screen.getByTestId('missing-media-locate-button'))

expect(emitted().locateNode).toEqual([['5']])
})
})
15 changes: 5 additions & 10 deletions src/platform/missingMedia/components/MissingMediaCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,9 @@
{{ item.displayItemLabel }}
</button>
</span>
<Button
<LocateNodeButton
data-testid="missing-media-locate-button"
variant="textonly"
size="icon-sm"
class="size-8 shrink-0 text-muted-foreground hover:text-base-foreground focus-visible:ring-inset"
:aria-label="
:label="
t(
'rightSidePanel.locateNodeFor',
{
Expand All @@ -48,10 +45,8 @@
{ escapeParameter: false }
)
"
@click.stop="emit('locateNode', item.nodeId)"
>
<i aria-hidden="true" class="icon-[lucide--locate] size-4" />
</Button>
@locate="emit('locateNode', item.nodeId)"
/>
</div>
</li>
</TransitionGroup>
Expand All @@ -63,7 +58,7 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { cn } from '@comfyorg/tailwind-utils'

import Button from '@/components/ui/button/Button.vue'
import LocateNodeButton from '@/components/rightSidePanel/errors/LocateNodeButton.vue'
import { selectionEmphasisClass } from '@/components/rightSidePanel/errors/selectionEmphasis'
import { resolveMissingMediaItemLabel } from '@/platform/errorCatalog/errorMessageResolver'
import { getMissingMediaReferences } from '@/platform/missingMedia/missingMediaGrouping'
Expand Down
15 changes: 15 additions & 0 deletions src/platform/missingModel/components/MissingModelRow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,21 @@ describe('MissingModelRow', () => {
expect(onLocateModel).toHaveBeenCalledWith('1')
})

it('gives the header locate button a node-specific accessible name', async () => {
const user = userEvent.setup()
const { onLocateModel } = renderRow(
makeModel([{ nodeId: '1', widgetName: 'ckpt_name' }])
)

const locateButton = screen.getByRole('button', {
name: 'Locate CheckpointLoaderSimple'
})
expect(locateButton).toBeInTheDocument()

await user.click(locateButton)
expect(onLocateModel).toHaveBeenCalledWith('1')
})

it('moves locate actions to expanded child rows when a cloud model has multiple references', async () => {
const user = userEvent.setup()
const { onLocateModel } = renderRow(
Expand Down
36 changes: 18 additions & 18 deletions src/platform/missingModel/components/MissingModelRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,12 @@
</Button>
</template>

<Button
<LocateNodeButton
v-if="!hasMultipleReferences && !isUnknownCategory && primaryReference"
data-testid="missing-model-locate"
variant="textonly"
size="icon-sm"
:aria-label="t('rightSidePanel.missingModels.locateNode')"
class="size-8 shrink-0 text-muted-foreground hover:text-base-foreground focus-visible:ring-inset"
@click="handleLocatePrimary"
>
<i aria-hidden="true" class="icon-[lucide--locate] size-4" />
</Button>
:label="locateNodeLabelFor(primaryReference.nodeId)"
@locate="handleLocatePrimary"
/>
</div>

<TransitionCollapse>
Expand Down Expand Up @@ -177,16 +172,12 @@
getNodeDisplayLabel(ref.nodeId, model.representative.nodeType)
}}
</button>
<Button
<LocateNodeButton
data-testid="missing-model-locate"
variant="textonly"
size="icon-sm"
:aria-label="t('rightSidePanel.missingModels.locateNode')"
class="ml-auto size-8 shrink-0 text-muted-foreground hover:text-base-foreground focus-visible:ring-inset"
@click="emit('locateModel', String(ref.nodeId))"
>
<i aria-hidden="true" class="icon-[lucide--locate] size-4" />
</Button>
class="ml-auto"
:label="locateNodeLabelFor(ref.nodeId)"
@locate="emit('locateModel', String(ref.nodeId))"
/>
</div>
</li>
</ul>
Expand All @@ -201,6 +192,7 @@ import { useI18n } from 'vue-i18n'
import { cn } from '@comfyorg/tailwind-utils'

import { selectionEmphasisClass } from '@/components/rightSidePanel/errors/selectionEmphasis'
import LocateNodeButton from '@/components/rightSidePanel/errors/LocateNodeButton.vue'
import Button from '@/components/ui/button/Button.vue'
import TransitionCollapse from '@/components/rightSidePanel/layout/TransitionCollapse.vue'
import type { MissingModelViewModel } from '@/platform/missingModel/types'
Expand Down Expand Up @@ -395,6 +387,14 @@ function handleLocatePrimary() {
if (ref) emit('locateModel', String(ref.nodeId))
}

function locateNodeLabelFor(nodeId: string | number): string {
return t(
'rightSidePanel.locateNodeFor',
{ item: getNodeDisplayLabel(nodeId, model.representative.nodeType) },
{ escapeParameter: false }
)
}

function copyModelLink() {
const url = model.representative.url
copyToClipboard(url ? toBrowsableUrl(url) : model.name)
Expand Down
47 changes: 34 additions & 13 deletions src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const i18n = createI18n({
nodesCount: '{count} node | {count} nodes'
},
rightSidePanel: {
locateNode: 'Locate node on canvas',
locateNodeFor: 'Locate {item}',
missingNodePacks: {
collapse: 'Collapse',
expand: 'Expand'
Expand Down Expand Up @@ -122,7 +122,7 @@ describe('SwapNodeGroupRow', () => {
it('starts collapsed — node list not visible', () => {
renderRow()
expect(
screen.queryByRole('button', { name: 'Locate node on canvas' })
screen.queryByRole('button', { name: /^Locate / })
).not.toBeInTheDocument()
})

Expand All @@ -133,7 +133,7 @@ describe('SwapNodeGroupRow', () => {
screen.getByRole('button', { name: 'Expand OldNodeType' })
)
expect(
screen.getAllByRole('button', { name: 'Locate node on canvas' })
screen.getAllByRole('button', { name: 'Locate OldNodeType' })
).toHaveLength(2)
})

Expand All @@ -144,13 +144,13 @@ describe('SwapNodeGroupRow', () => {
screen.getByRole('button', { name: 'Expand OldNodeType' })
)
expect(
screen.getAllByRole('button', { name: 'Locate node on canvas' })
screen.getAllByRole('button', { name: 'Locate OldNodeType' })
).toHaveLength(2)
await user.click(
screen.getByRole('button', { name: 'Collapse OldNodeType' })
)
expect(
screen.queryByRole('button', { name: 'Locate node on canvas' })
screen.queryByRole('button', { name: /^Locate / })
).not.toBeInTheDocument()
})

Expand Down Expand Up @@ -204,7 +204,7 @@ describe('SwapNodeGroupRow', () => {
renderRow()
await expand()
expect(
screen.getAllByRole('button', { name: 'Locate node on canvas' })
screen.getAllByRole('button', { name: 'Locate OldNodeType' })
).toHaveLength(2)
})

Expand All @@ -219,7 +219,7 @@ describe('SwapNodeGroupRow', () => {
})
await expand()
expect(
screen.queryByRole('button', { name: 'Locate node on canvas' })
screen.queryByRole('button', { name: /^Locate / })
).not.toBeInTheDocument()
})

Expand All @@ -245,9 +245,30 @@ describe('SwapNodeGroupRow', () => {
})
).toHaveLength(1)
expect(
screen.getAllByRole('button', { name: 'Locate node on canvas' })
screen.getAllByRole('button', { name: 'Locate MixedNodeType' })
).toHaveLength(1)
})

it('gives each locate control a node-specific accessible name', async () => {
renderRow({
group: makeGroup({
type: 'AlphaNode',
nodeTypes: [
{ type: 'AlphaNode', nodeId: '1', isReplaceable: true },
{ type: 'BetaNode', nodeId: '2', isReplaceable: true }
]
})
})

await expand()

expect(
screen.getByRole('button', { name: 'Locate AlphaNode' })
).toBeInTheDocument()
expect(
screen.getByRole('button', { name: 'Locate BetaNode' })
).toBeInTheDocument()
})
})

describe('Events', () => {
Expand All @@ -259,7 +280,7 @@ describe('SwapNodeGroupRow', () => {
screen.getByRole('button', { name: 'Expand OldNodeType' })
)
const locateBtns = screen.getAllByRole('button', {
name: 'Locate node on canvas'
name: 'Locate OldNodeType'
})
await user.click(locateBtns[0])
expect(onLocateNode).toHaveBeenCalledWith('1')
Expand Down Expand Up @@ -302,7 +323,7 @@ describe('SwapNodeGroupRow', () => {
expect(onLocateNode).toHaveBeenCalledWith('42')

await user.click(
screen.getByRole('button', { name: 'Locate node on canvas' })
screen.getByRole('button', { name: 'Locate SingleNodeType' })
)
expect(onLocateNode).toHaveBeenCalledTimes(2)
expect(onLocateNode).toHaveBeenLastCalledWith('42')
Expand All @@ -323,7 +344,7 @@ describe('SwapNodeGroupRow', () => {
screen.queryByRole('button', { name: 'NoIdNode' })
).not.toBeInTheDocument()
expect(
screen.queryByRole('button', { name: 'Locate node on canvas' })
screen.queryByRole('button', { name: /^Locate / })
).not.toBeInTheDocument()
})
})
Expand All @@ -344,7 +365,7 @@ describe('SwapNodeGroupRow', () => {
screen.queryByRole('button', { name: /^Expand / })
).not.toBeInTheDocument()
expect(
screen.queryByRole('button', { name: 'Locate node on canvas' })
screen.queryByRole('button', { name: /^Locate / })
).not.toBeInTheDocument()
})

Expand All @@ -371,7 +392,7 @@ describe('SwapNodeGroupRow', () => {
screen.queryByRole('button', { name: 'OtherStringType' })
).not.toBeInTheDocument()
expect(
screen.queryByRole('button', { name: 'Locate node on canvas' })
screen.queryByRole('button', { name: /^Locate / })
).not.toBeInTheDocument()
})
})
Expand Down
42 changes: 21 additions & 21 deletions src/platform/nodeReplacement/components/SwapNodeGroupRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,17 @@
</span>
</Button>

<Button
<LocateNodeButton
v-if="primaryLocatableNodeType"
variant="textonly"
size="icon-sm"
class="size-8 shrink-0 text-muted-foreground hover:text-base-foreground focus-visible:ring-inset"
:aria-label="locateNodeLabel"
@click="handleLocateNode(primaryLocatableNodeType)"
>
<i aria-hidden="true" class="icon-[lucide--locate] size-4" />
</Button>
:label="
t(
'rightSidePanel.locateNodeFor',
{ item: getLabel(primaryLocatableNodeType) },
{ escapeParameter: false }
)
"
@locate="handleLocateNode(primaryLocatableNodeType)"
/>
</div>

<TransitionCollapse>
Expand All @@ -141,16 +142,17 @@
{{ getLabel(nodeType) }}
</span>
</span>
<Button
<LocateNodeButton
v-if="isLocatableNodeType(nodeType)"
variant="textonly"
size="icon-sm"
class="size-8 shrink-0 text-muted-foreground hover:text-base-foreground focus-visible:ring-inset"
:aria-label="locateNodeLabel"
@click="handleLocateNode(nodeType)"
>
<i aria-hidden="true" class="icon-[lucide--locate] size-4" />
</Button>
:label="
t(
'rightSidePanel.locateNodeFor',
{ item: getLabel(nodeType) },
{ escapeParameter: false }
)
"
@locate="handleLocateNode(nodeType)"
/>
</div>
</li>
</ul>
Expand All @@ -165,6 +167,7 @@ import { cn } from '@comfyorg/tailwind-utils'
import { selectionEmphasisClass } from '@/components/rightSidePanel/errors/selectionEmphasis'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import LocateNodeButton from '@/components/rightSidePanel/errors/LocateNodeButton.vue'
import TransitionCollapse from '@/components/rightSidePanel/layout/TransitionCollapse.vue'
import type { MissingNodeType } from '@/types/comfy'
import type { SwapNodeGroup } from '@/components/rightSidePanel/errors/useErrorGroups'
Expand All @@ -187,9 +190,6 @@ const hasMultipleNodeTypes = computed(() => group.nodeTypes.length > 1)
const replacementLabel = computed(
() => group.newNodeId ?? t('nodeReplacement.unknownNode', 'Unknown')
)
const locateNodeLabel = computed(() =>
t('rightSidePanel.locateNode', 'Locate node on canvas')
)
const titleToggleAriaLabel = computed(
() =>
`${
Expand Down
Loading