Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,16 @@ afterEach(async () => {
vi.unstubAllGlobals()
})

async function render(canEditLogo = true) {
async function render(canEditLogo = true, isCollapsed = false, onExpandSidebar = vi.fn()) {
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<ToastProvider>
<OrganizationHeader
organization={organization}
canEditLogo={canEditLogo}
isCollapsed={false}
onExpandSidebar={vi.fn()}
isCollapsed={isCollapsed}
onExpandSidebar={onExpandSidebar}
/>
</ToastProvider>
</QueryClientProvider>
Expand All @@ -74,9 +74,9 @@ async function openMenu() {
})
}

function menuItem(name: string) {
return Array.from(document.querySelectorAll<HTMLElement>('[role="menuitem"]')).find(
(item) => item.textContent === name
function logoControl() {
return document.querySelector<HTMLElement>(
'[role="menuitem"][aria-label="Change organization logo"]'
)
}

Expand All @@ -87,21 +87,70 @@ async function pickFile(file: File) {
}

describe('OrganizationHeader logo upload', () => {
it('opens the same native file picker from the admin menu', async () => {
it('opens the native file picker by clicking the logo and keeps the menu open', async () => {
await render()
await openMenu()
const input = container.querySelector<HTMLInputElement>('input[type="file"]')!
const click = vi.spyOn(input, 'click').mockImplementation(() => {})
expect(input.accept).toContain('image/png')
await act(async () => menuItem('Upload logo')!.click())
await act(async () => logoControl()!.click())
expect(click).toHaveBeenCalledOnce()
expect(logoControl()).not.toBeNull()
expect(document.body.textContent).not.toContain('Upload logo')
expect(document.querySelector('[role="menu"]')?.textContent).toContain('Settings')
})

it.each(['Enter', ' '])('opens the file picker using the %j key', async (key) => {
await render()
await openMenu()
const input = container.querySelector<HTMLInputElement>('input[type="file"]')!
const click = vi.spyOn(input, 'click').mockImplementation(() => {})
await act(async () => {
logoControl()!.focus()
logoControl()!.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }))
})
expect(click).toHaveBeenCalledOnce()
})

it('does not offer logo changes to members', async () => {
await render(false)
await openMenu()
expect(menuItem('Upload logo')).toBeUndefined()
expect(logoControl()).toBeNull()
expect(container.querySelector('input[type="file"]')).toBeNull()
expect(document.querySelector('[role="menu"]')?.textContent).toContain('Design')
})

it('preserves the collapsed logo as the sidebar expand control', async () => {
const expand = vi.fn()
await render(true, true, expand)
await act(async () => {
container.querySelector<HTMLButtonElement>('[aria-label="Expand sidebar"]')!.click()
})
expect(expand).toHaveBeenCalledOnce()
expect(container.querySelector('input[type="file"]')).toBeNull()
expect(mocks.upload).not.toHaveBeenCalled()
})

it('disables logo changes while the upload is pending', async () => {
let completeUpload!: () => void
mocks.upload.mockImplementation(
() =>
new Promise<void>((resolve) => {
completeUpload = resolve
})
)
await render()
await openMenu()
await pickFile(new File(['image'], 'logo.png', { type: 'image/png' }))
await act(async () => {
await vi.waitFor(() => expect(logoControl()?.getAttribute('aria-disabled')).toBe('true'))
})
expect(logoControl()?.getAttribute('aria-busy')).toBe('true')
expect(container.querySelector<HTMLInputElement>('input[type="file"]')!.disabled).toBe(true)
await act(async () => logoControl()!.click())
expect(mocks.upload).toHaveBeenCalledOnce()
await act(async () => completeUpload())
expect(mocks.refresh).toHaveBeenCalledOnce()
})

it('uploads under the organization scope and refreshes its identity after success', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
OverflowText,
Tooltip,
toast,
} from '@sim/emcn'
import { PanelLeft, Settings, Upload } from '@sim/emcn/icons'
import { PanelLeft, Settings } from '@sim/emcn/icons'
import { useRouter } from 'next/navigation'
import { IdentityTile } from '@/components/identity-tile/identity-tile'
import { getOrganizationSettingsHref } from '@/components/settings/navigation'
Expand Down Expand Up @@ -80,6 +81,9 @@ export function OrganizationHeader({
}

const { memberCount } = organization
const logo = (
<IdentityTile size='lg' initial={initial} logoUrl={organization.logo} alt={organization.name} />
)

return (
<div className='min-w-0 flex-1'>
Expand Down Expand Up @@ -120,28 +124,37 @@ export function OrganizationHeader({
className='w-64 max-w-[calc(100vw-24px)]'
>
<div className='flex items-center gap-2 px-2 py-1.5'>
<IdentityTile
size='lg'
initial={initial}
logoUrl={organization.logo}
alt={organization.name}
/>
{canEditLogo ? (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<DropdownMenuItem
aria-label='Change organization logo'
aria-busy={isUploadingLogo}
textValue='Change organization logo'
className='h-auto shrink-0 p-1'
disabled={isUploadingLogo}
onSelect={(event) => {
event.preventDefault()
fileInputRef.current?.click()
}}
>
{logo}
</DropdownMenuItem>
</Tooltip.Trigger>
<Tooltip.Content>
{isUploadingLogo ? 'Uploading...' : 'Change logo'}
</Tooltip.Content>
</Tooltip.Root>
) : (
logo
)}
<div className='flex min-w-0 flex-col'>
<OverflowText label={organization.name} />
<span className='text-[var(--text-muted)] text-caption'>
{memberCount} {memberCount === 1 ? 'member' : 'members'}
</span>
</div>
</div>
{canEditLogo && (
<DropdownMenuItem
disabled={isUploadingLogo}
onSelect={() => fileInputRef.current?.click()}
>
<Upload className='size-[14px]' />
{isUploadingLogo ? 'Uploading...' : 'Upload logo'}
</DropdownMenuItem>
)}
<DropdownMenuItem asChild>
<SettingsGuardedLink href={getOrganizationSettingsHref(organization.id, 'members')}>
<Settings className='size-[14px]' />
Expand Down
Loading