Skip to content
Draft
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
325 changes: 325 additions & 0 deletions apps/web/e2e/tests/admin/designer-builder.spec.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const AREA_QUERY_KEYS: Record<SettingsArea, readonly (readonly string[])[]> = {
['settings', 'branding'],
['settings', 'logo'],
['settings', 'headerLogo'],
['settings', 'portal-design'],
],
portal: [['settings', 'publicPortalConfig']],
messenger: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,23 @@ describe('Copilot pause control', () => {
expect(screen.queryByRole('button', { name: 'Pause Copilot' })).not.toBeInTheDocument()
})

it('says AI is off when both provider keys are set', async () => {
vi.mocked(getAssistantSettingsFn).mockResolvedValue({
config,
revision: 4,
managedFieldPaths: [],
aiAvailable: false,
aiConfigIssue: 'multiple-providers',
} as never)
renderWithProviders(<CopilotHeader />)
await waitFor(() =>
expect(screen.getByTestId('line')).toHaveTextContent(
'AI is off because both OPENAI_API_KEY and ANTHROPIC_API_KEY are set. Keep one.'
)
)
expect(screen.queryByRole('button', { name: 'Pause Copilot' })).not.toBeInTheDocument()
})

it('offers the control when the settings report a configured model', async () => {
vi.mocked(getAssistantSettingsFn).mockResolvedValue({
config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ export function useCopilotStatusLine(availableOverride?: boolean) {
const intl = useIntl()
const settingsQuery = useQuery(assistantQueries.settings())
const available = availableOverride ?? settingsQuery.data?.aiAvailable !== false
if (settingsQuery.data?.aiConfigIssue === 'multiple-providers') {
return intl.formatMessage({
id: 'automation.copilot.deployment.multipleProviders',
defaultMessage:
'AI is off because both OPENAI_API_KEY and ANTHROPIC_API_KEY are set. Keep one.',
})
}
if (!available) {
return intl.formatMessage({
id: 'automation.copilot.deployment.unavailable',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,12 @@ describe('SettingRow', () => {
expect(wrap?.children.length).toBe(2)
})
})

describe('a setting’s description', () => {
it('is read with its control', () => {
render(<Harness onChange={() => {}} />)
const control = screen.getByRole('switch')
const id = control.getAttribute('aria-describedby')
expect(id && document.getElementById(id)?.textContent).toBe('Visitors can comment on posts.')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// @vitest-environment happy-dom
/**
* Appearance is a summary of the portal's design: what visitors see now, what
* waits in the draft, when it was last published, and the way into the
* builder. Nothing on it edits the design.
*/
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cleanup, render, screen } from '@testing-library/react'
import type { ReactNode } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { defaultDesign } from '@/lib/shared/design/schema'

const flags = vi.hoisted(() => ({ extra: {} as Record<string, boolean> }))

vi.mock('@tanstack/react-router', () => ({
useRouter: () => ({ invalidate: async () => {} }),
ClientOnly: ({ children }: { children: ReactNode }) => <>{children}</>,
useRouteContext: ({ select }: { select: (context: unknown) => unknown }) =>
select({ settings: { featureFlags: { feedback: true, ...flags.extra } } }),
}))
vi.mock('@/lib/server/functions/portal-design', () => ({
getPortalDesignFn: async () => editorState(),
listPortalDesignVersionsFn: async () => versions,
getPortalDesignSwitchFn: async () => ({ canSwitchBack: false, canPublish: true }),
restorePortalDesignVersionFn: async () => ({ ok: true }),
switchBackPortalDesignFn: async () => ({ ok: true }),
}))

type Version = {
id: `portal_design_revision_${string}`
number: number
source: 'appearance'
message: string | null
principalId: null
createdAt: Date
}
let versions: Version[] = []

const { AppearanceView } = await import('../appearance-view')
const { portalDesignQueries } = await import('@/lib/client/queries/portal-design')

function editorState(changed = false) {
const design = defaultDesign()
if (changed) {
design.brand.light.accent = '#0f766e'
design.layout.density = 'compact'
}
return {
design,
live: defaultDesign(),
source: changed ? ('draft' as const) : ('converted' as const),
published: versions[0] ? { revisionId: versions[0].id, number: versions[0].number } : null,
report: { adjustments: [], errors: [], css: [] },
revision: 'rev1',
authors: {},
draftSavedAt: null,
canPublish: true,
}
}

function mount({ changed = false } = {}) {
const client = new QueryClient()
client.setQueryData(portalDesignQueries.editor().queryKey, editorState(changed))
client.setQueryData(portalDesignQueries.versions().queryKey, versions)
render(
<QueryClientProvider client={client}>
<AppearanceView />
</QueryClientProvider>
)
}

afterEach(() => {
cleanup()
flags.extra = {}
versions = []
})

describe('AppearanceView', () => {
it('says nothing is published or waiting on a portal still on its first design', () => {
mount()
expect(screen.getByText('Nothing published yet')).toBeTruthy()
expect(screen.getByText('No changes waiting')).toBeTruthy()
})

it('shows the live version, when it was published, and the changes waiting', () => {
versions = [
{
id: 'portal_design_revision_2',
number: 2,
source: 'appearance',
message: null,
principalId: null,
createdAt: new Date(Date.now() - 3 * 86_400_000),
},
]
mount({ changed: true })
expect(screen.getByText(/^Version 2/).textContent).toMatch(/published 3 days ago/)
expect(screen.getByText('2 changes waiting to publish')).toBeTruthy()
})

it('edits nothing itself: the builder is where the design changes', () => {
mount({ changed: true })
expect(screen.queryByRole('tab')).toBeNull()
expect(screen.queryByRole('textbox')).toBeNull()
expect(screen.queryByLabelText(/logo/i)).toBeNull()
expect(screen.getByRole('link', { name: 'Open the builder' }).getAttribute('href')).toBe(
'/design?path=%2F&from=appearance'
)
})
})
Loading
Loading