mirror of
https://github.com/mudler/LocalAI.git
synced 2026-08-02 11:30:44 -04:00
Commit b35d630cf fixed this for gallery models but left the Backends admin
page with the same asymmetry: its detail panel renders the description
through renderMarkdown, while the collapsed table row dumped the raw gallery
string into both the cell body and the title tooltip.
That is user-visible. 40 of the 949 entries in backend/index.yaml carry
Markdown - insightface uses inline code backticks, others use lists and
links - and backend descriptions also contain embedded newlines, so the
one-line cell showed literal syntax.
The cell now runs stripMarkdown over the description once and uses the
result for the text and the title, matching Models.jsx and the
ResourceRowDesc component in Manage.jsx. The '-' placeholder is preserved,
and now also fires when a description reduces to nothing after stripping.
The detail panel is untouched and no new dangerouslySetInnerHTML is
introduced: stripMarkdown output lands in a JSX text node, so React escapes
it.
Three Playwright specs cover it: a description with a heading, inline code
and a link renders as clean text with no literal syntax and no block
element in the cell, the title tooltip carries the same stripped text, and
a backend without a description still shows the placeholder.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
80 lines
3.4 KiB
JavaScript
80 lines
3.4 KiB
JavaScript
import { test, expect } from './coverage-fixtures.js'
|
|
|
|
// Backends admin page (src/pages/Backends.jsx).
|
|
test.describe('Backends management page', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/app/backends')
|
|
})
|
|
|
|
test('renders the management header and gallery tabs', async ({ page }) => {
|
|
await expect(page).toHaveURL(/\/app\/backends$/)
|
|
await expect(page.getByRole('heading', { name: 'Backend Management' })).toBeVisible()
|
|
await expect(page.getByRole('button', { name: 'Manual Install' })).toBeVisible()
|
|
await expect(page.getByRole('button').filter({ hasText: /^All$/ })).toBeVisible()
|
|
await expect(page.getByRole('button').filter({ hasText: /^Image$/ })).toBeVisible()
|
|
})
|
|
|
|
test('search field accepts input', async ({ page }) => {
|
|
const search = page.getByPlaceholder(/search backends/i)
|
|
await expect(search).toBeVisible()
|
|
await search.fill('whisper')
|
|
await expect(search).toHaveValue('whisper')
|
|
})
|
|
|
|
test('Manual Install reveals the OCI install form', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Manual Install' }).click()
|
|
await expect(page.getByPlaceholder('oci://quay.io/example/backend:latest')).toBeVisible()
|
|
})
|
|
})
|
|
|
|
// Backend gallery descriptions are Markdown too: 40 of the entries in
|
|
// backend/index.yaml carry headings, inline code, lists or links, and they used
|
|
// to be dumped raw into the truncated table cell.
|
|
const MARKDOWN_DESCRIPTION =
|
|
'# InsightFace\n\nUse `insightface` for face analysis. See [the docs](https://example.com/docs) for **details**.'
|
|
const STRIPPED_DESCRIPTION =
|
|
'InsightFace Use insightface for face analysis. See the docs for details.'
|
|
|
|
test.describe('Backends management page - Markdown descriptions', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.route('**/api/backends*', (route) => {
|
|
route.fulfill({
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
backends: [
|
|
{ name: 'markdown-backend', description: MARKDOWN_DESCRIPTION, installed: false },
|
|
{ name: 'plain-backend', description: '', installed: false },
|
|
],
|
|
}),
|
|
})
|
|
})
|
|
await page.goto('/app/backends')
|
|
await expect(page.locator('th', { hasText: 'Description' })).toBeVisible({ timeout: 10_000 })
|
|
})
|
|
|
|
test('table cell shows the description as clean text, not raw Markdown', async ({ page }) => {
|
|
const cell = page.locator('tr', { hasText: 'markdown-backend' }).locator('span[title]', { hasText: 'InsightFace' })
|
|
|
|
await expect(cell).toHaveText(STRIPPED_DESCRIPTION)
|
|
// The syntax itself must be gone, not merely rendered somewhere.
|
|
await expect(cell).not.toContainText('#')
|
|
await expect(cell).not.toContainText('`')
|
|
await expect(cell).not.toContainText('**')
|
|
await expect(cell).not.toContainText('https://example.com/docs')
|
|
// A block element here would blow up the row height.
|
|
await expect(cell.locator('h1')).toHaveCount(0)
|
|
})
|
|
|
|
test('title tooltip carries the stripped text, not raw Markdown', async ({ page }) => {
|
|
const cell = page.locator('tr', { hasText: 'markdown-backend' }).locator('span[title]', { hasText: 'InsightFace' })
|
|
|
|
await expect(cell).toHaveAttribute('title', STRIPPED_DESCRIPTION)
|
|
})
|
|
|
|
test('a backend with no description still shows the placeholder', async ({ page }) => {
|
|
const row = page.locator('tr', { hasText: 'plain-backend' })
|
|
|
|
await expect(row.locator('span[title=""]')).toHaveText('-')
|
|
})
|
|
})
|