Compare commits

...

6 Commits

Author SHA1 Message Date
bymyself
fefb09cd9c test: trim RoleBadge tests to user-visible label behavior
Removes assertions on the root tag name, Vue rerender mechanics, and
class-fallthrough utility forwarding — these locked down implementation
details rather than user-visible behavior. The meaningful regression
coverage for role labels lives in MemberListItem.test.ts.

Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/11383#discussion_r3176431334
2026-05-04 13:34:15 -07:00
bymyself
b29ad1f9fe fix: rely on Vue class fallthrough in RoleBadge
Removes `inheritAttrs: false` and the manual `cn()` class merge so all
caller-provided attributes (aria-*, data-*, title, style, id, listeners)
fall through to the root span. Tailwind class merging is handled by
Vue's default class fallthrough.

Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/11383#discussion_r3176431333
2026-05-04 13:33:27 -07:00
bymyself
2d036e1dc1 fix: update cn import after tailwindUtil shim removal
Rebased onto main where PR #11453 removed the @/utils/tailwindUtil
re-export shim. Switch RoleBadge to import cn directly from
@comfyorg/tailwind-utils.
2026-05-01 21:12:47 -07:00
bymyself
50d8b7a98a test: add MemberListItem unit tests for role badge label coverage
Covers getRoleBadgeLabel function to fix codecov/patch failure.
2026-05-01 21:10:04 -07:00
bymyself
2cbeaae36b fix: rename attr passthrough test to clarify behavioral intent
Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/11383#discussion_r3106189426
2026-05-01 21:10:03 -07:00
bymyself
18d448c740 refactor: extract RoleBadge component and reuse across workspace UI
Replace 4 inline badge spans with the new RoleBadge component in
WorkspaceSwitcherPopover, MembersPanelContent, and
TeamWorkspacesDialogContent. The component accepts a label prop and
supports class merging via inheritAttrs: false + cn().

Fixes #10971
2026-05-01 21:10:03 -07:00
6 changed files with 95 additions and 55 deletions

View File

@@ -1,37 +1,11 @@
import { render, screen } from '@testing-library/vue'
import { createI18n } from 'vue-i18n'
import { describe, expect, it } from 'vitest'
import RoleBadge from './RoleBadge.vue'
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: {
workspaceSwitcher: {
roleOwner: 'Owner',
roleMember: 'Member'
}
}
}
})
function renderRoleBadge(role: 'owner' | 'member') {
return render(RoleBadge, {
props: { role },
global: { plugins: [i18n] }
})
}
describe('RoleBadge', () => {
it('renders the owner label', () => {
renderRoleBadge('owner')
expect(screen.getByText('Owner')).toBeInTheDocument()
})
it('renders the member label', () => {
renderRoleBadge('member')
expect(screen.getByText('Member')).toBeInTheDocument()
it('renders the label text', () => {
render(RoleBadge, { props: { label: 'PRO' } })
expect(screen.getByText('PRO')).toBeInTheDocument()
})
})

View File

@@ -2,23 +2,12 @@
<span
class="rounded-full bg-base-foreground px-1 py-0.5 text-2xs font-bold text-base-background uppercase"
>
{{ roleBadgeLabel }}
{{ label }}
</span>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
const { role } = defineProps<{
role: 'owner' | 'member'
defineProps<{
label: string
}>()
const { t } = useI18n()
const roleBadgeLabel = computed(() =>
role === 'owner'
? t('workspaceSwitcher.roleOwner')
: t('workspaceSwitcher.roleMember')
)
</script>

View File

@@ -46,12 +46,10 @@
: workspace.name
}}
</span>
<span
<RoleBadge
v-if="resolveTierLabel(workspace)"
class="rounded-full bg-base-foreground px-1 py-0.5 text-2xs font-bold text-base-background uppercase"
>
{{ resolveTierLabel(workspace) }}
</span>
:label="resolveTierLabel(workspace)!"
/>
</div>
<span class="text-xs text-muted-foreground">
{{ getRoleLabel(workspace.role) }}
@@ -112,6 +110,7 @@ import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import RoleBadge from '@/platform/workspace/components/RoleBadge.vue'
import WorkspaceProfilePic from '@/platform/workspace/components/WorkspaceProfilePic.vue'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { useWorkspaceSwitch } from '@/platform/workspace/composables/useWorkspaceSwitch'

View File

@@ -60,12 +60,11 @@
>
{{ workspace.name }}
</span>
<span
<RoleBadge
v-if="tierLabels.get(workspace.id)"
class="shrink-0 rounded-full bg-base-foreground px-1 py-0.5 text-2xs font-bold text-base-background uppercase"
>
{{ tierLabels.get(workspace.id) }}
</span>
class="shrink-0"
:label="tierLabels.get(workspace.id)!"
/>
</div>
</div>
<span class="text-primary-foreground shrink-0 text-sm font-medium">
@@ -141,6 +140,7 @@ import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import RoleBadge from '@/platform/workspace/components/RoleBadge.vue'
import WorkspaceProfilePic from '@/platform/workspace/components/WorkspaceProfilePic.vue'
import { useWorkspaceSwitch } from '@/platform/workspace/composables/useWorkspaceSwitch'
import { useWorkspaceTierLabel } from '@/platform/workspace/composables/useWorkspaceTierLabel'

View File

@@ -0,0 +1,69 @@
import type { ComponentProps } from 'vue-component-type-helpers'
import { render, screen } from '@testing-library/vue'
import { describe, expect, it } from 'vitest'
import { createI18n } from 'vue-i18n'
import type { WorkspaceMember } from '@/platform/workspace/stores/teamWorkspaceStore'
import MemberListItem from './MemberListItem.vue'
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: {
g: { you: 'you', moreOptions: 'More options' },
workspaceSwitcher: { roleOwner: 'Owner', roleMember: 'Member' }
}
},
missingWarn: false,
fallbackWarn: false
})
const baseMember: WorkspaceMember = {
id: 'u1',
name: 'Alice',
email: 'alice@example.com',
joinDate: new Date('2025-01-01'),
role: 'owner'
}
const baseProps: ComponentProps<typeof MemberListItem> = {
member: baseMember,
isCurrentUser: false,
gridCols: 'grid-cols-3',
showRoleBadge: true
}
function renderItem(
propOverrides?: Partial<ComponentProps<typeof MemberListItem>>
) {
return render(MemberListItem, {
props: { ...baseProps, ...propOverrides },
global: {
plugins: [i18n],
stubs: {
UserAvatar: { template: '<div />' },
Button: { template: '<button />', props: ['variant', 'size'] }
}
}
})
}
describe('MemberListItem', () => {
it('shows translated owner badge for owner role', () => {
renderItem({ member: { ...baseMember, role: 'owner' } })
expect(screen.getByText('Owner')).toBeInTheDocument()
})
it('shows translated member badge for member role', () => {
renderItem({ member: { ...baseMember, role: 'member' } })
expect(screen.getByText('Member')).toBeInTheDocument()
})
it('hides role badge when showRoleBadge is false', () => {
renderItem({ showRoleBadge: false })
expect(screen.queryByText('Owner')).not.toBeInTheDocument()
})
})

View File

@@ -22,7 +22,10 @@
({{ $t('g.you') }})
</span>
</span>
<RoleBadge v-if="showRoleBadge" :role="member.role" />
<RoleBadge
v-if="showRoleBadge"
:label="getRoleBadgeLabel(member.role)"
/>
</div>
<span class="text-sm text-muted-foreground">
{{ member.email }}
@@ -84,7 +87,13 @@ defineEmits<{
showMenu: [event: Event]
}>()
const { d } = useI18n()
const { d, t } = useI18n()
function getRoleBadgeLabel(role: string): string {
return role === 'owner'
? t('workspaceSwitcher.roleOwner')
: t('workspaceSwitcher.roleMember')
}
function formatDate(date: Date): string {
return d(date, { dateStyle: 'medium' })