diff --git a/app/(main)/admin/__tests__/branding-placeholders.test.tsx b/app/(main)/admin/__tests__/branding-placeholders.test.tsx new file mode 100644 index 000000000..9918c3030 --- /dev/null +++ b/app/(main)/admin/__tests__/branding-placeholders.test.tsx @@ -0,0 +1,37 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { useAdminUnsavedStore } from '@/stores/admin-unsaved-store'; + +// Branding per domain: the image fields said "Enter URL (uploads only for +// default scope)", cut off in the field and wrong for a single domain, which +// can upload. Only a wildcard domain takes a URL alone. + +vi.mock('@/lib/browser-navigation', () => ({ + apiFetch: vi.fn(async () => new Response(JSON.stringify({ + domainBranding: { source: 'admin', value: [{ host: 'mail.example.com' }, { host: '*.example.com' }] }, + }), { status: 200, headers: { 'content-type': 'application/json' } })), + withBasePath: (p: string) => p, +})); +vi.mock('next/link', () => ({ default: ({ children }: { children: React.ReactNode }) => children })); + +import { BrandingPage } from '../_pages/branding'; + +afterEach(() => { + useAdminUnsavedStore.setState({ dirty: {}, pendingLeave: null, pendingScope: null }); +}); + +describe('admin branding image fields', () => { + it('offers upload for a single domain and a URL only for a wildcard, with the full hint as a title', async () => { + render(); + + fireEvent.click(await screen.findByRole('button', { name: 'mail.example.com' })); + const favicon = screen.getByRole('textbox', { name: 'Favicon' }); + expect(favicon.getAttribute('placeholder')).toBe('Enter URL or upload a file'); + expect(favicon.getAttribute('title')).toBe('Enter URL or upload a file'); + + fireEvent.click(screen.getByRole('button', { name: '*.example.com' })); + const wildcardFavicon = screen.getByRole('textbox', { name: 'Favicon' }); + expect(wildcardFavicon.getAttribute('placeholder')).toBe('Enter URL'); + expect(wildcardFavicon.getAttribute('title')).toBe('Enter URL'); + }); +}); diff --git a/app/(main)/admin/_pages/branding.tsx b/app/(main)/admin/_pages/branding.tsx index 70446999e..f398455e2 100644 --- a/app/(main)/admin/_pages/branding.tsx +++ b/app/(main)/admin/_pages/branding.tsx @@ -396,7 +396,9 @@ export function BrandingPage() { accept={field.accept} value={currentValue(field.key)} badge={overrideBadge(field.key)} - placeholder={selectedHost ? 'Enter URL (uploads only for default scope)' : 'Enter URL or upload a file'} + // Files upload to the default scope and to exact hosts; a wildcard has + // no single host to serve them from (the scope line says so). + placeholder={wildcardScope ? 'Enter URL' : 'Enter URL or upload a file'} uploading={uploading === field.key} uploadDisabled={wildcardScope} uploaded={isUploadedFile(field.key)} @@ -417,6 +419,7 @@ export function BrandingPage() { value={currentValue(field.key)} onChange={(e) => handleChange(field.key, e.target.value)} placeholder={placeholder} + title={placeholder} aria-label={field.label} autoComplete="off" spellCheck={false} @@ -592,6 +595,7 @@ function ImageItem({ label, accept, value, badge, placeholder, uploading, upload value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} + title={placeholder} aria-label={label} autoComplete="off" spellCheck={false}