diff --git a/app/gui/integration-test/dashboard/actions/api.ts b/app/gui/integration-test/dashboard/actions/api.ts index 9944c8bbc14f..09f7c15cbb5f 100644 --- a/app/gui/integration-test/dashboard/actions/api.ts +++ b/app/gui/integration-test/dashboard/actions/api.ts @@ -15,6 +15,7 @@ import * as actions from '.' import { readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import invariant from 'tiny-invariant' const __dirname = dirname(fileURLToPath(import.meta.url)) @@ -1129,17 +1130,23 @@ async function mockApiInternal({ page, setupAPI }: MockParams) { }) }) - await get(remoteBackendPaths.getProjectAssetPath(GLOB_PROJECT_ID, '*'), (route, request) => { - const maybeId = request.url().match(/[/]projects[/]([^?/]+)/)?.[1] - if (!maybeId) return - const projectId = backend.ProjectId(maybeId) - called('getProjectAsset', { projectId }) - return route.fulfill({ - // This is a mock SVG image. Just a square with a black background. - body: '/mock/svg.svg', - contentType: 'text/plain', - }) - }) + await get( + remoteBackendPaths.getProjectAssetPath(GLOB_PROJECT_ID, '*'), + async (route, request) => { + const maybeId = request.url().match(/[/]projects[/]([^?/]+)/)?.[1] + + invariant(maybeId, 'Unable to parse the ID provided') + + const projectId = backend.ProjectId(maybeId) + + called('getProjectAsset', { projectId }) + + return route.fulfill({ + // This is a mock SVG image. Just a square with a black background. + path: join(__dirname, '../mock/example.png'), + }) + }, + ) await page.route('mock/svg.svg', (route) => { return route.fulfill({ body: MOCK_SVG, contentType: 'image/svg+xml' }) diff --git a/app/gui/integration-test/dashboard/assetPanel.spec.ts b/app/gui/integration-test/dashboard/assetPanel.spec.ts index 9282cf573724..37e8ffd05102 100644 --- a/app/gui/integration-test/dashboard/assetPanel.spec.ts +++ b/app/gui/integration-test/dashboard/assetPanel.spec.ts @@ -5,7 +5,7 @@ import { EmailAddress, UserId } from '#/services/Backend' import { PermissionAction } from '#/utilities/permissions' -import { mockAllAndLogin } from './actions' +import { mockAllAndLogin, TEXT } from './actions' /** Find an asset panel. */ function locateAssetPanel(page: Page) { @@ -87,4 +87,25 @@ test('Asset Panel documentation view', ({ page }) => await expect(assetPanel.getByTestId('asset-panel-tab-panel-docs')).toBeVisible() await expect(assetPanel.getByTestId('asset-docs-content')).toBeVisible() await expect(assetPanel.getByTestId('asset-docs-content')).toHaveText(/Project Goal/) + await expect(assetPanel.getByText(TEXT.arbitraryFetchImageError)).not.toBeVisible() })) + +test('Assets Panel docs images', ({ page }) => { + return mockAllAndLogin({ + page, + setupAPI: (api) => { + api.addProject({}) + }, + }) + .do(() => {}) + .driveTable.clickRow(0) + .toggleDocsAssetPanel() + .withAssetPanel(async (assetPanel) => { + await expect(assetPanel.getByTestId('asset-docs-content')).toBeVisible() + + for (const image of await assetPanel.getByRole('img').all()) { + await expect(image).toBeVisible() + await expect(image).toHaveJSProperty('complete', true) + } + }) +}) diff --git a/app/gui/integration-test/dashboard/mock/example.png b/app/gui/integration-test/dashboard/mock/example.png new file mode 100644 index 000000000000..b4d6d8b3cb9c Binary files /dev/null and b/app/gui/integration-test/dashboard/mock/example.png differ diff --git a/app/gui/src/dashboard/components/MarkdownViewer/MarkdownViewer.tsx b/app/gui/src/dashboard/components/MarkdownViewer/MarkdownViewer.tsx index 709d617623b8..ebd199958086 100644 --- a/app/gui/src/dashboard/components/MarkdownViewer/MarkdownViewer.tsx +++ b/app/gui/src/dashboard/components/MarkdownViewer/MarkdownViewer.tsx @@ -1,5 +1,6 @@ /** @file A Markdown viewer component. */ +import { useLogger } from '#/providers/LoggerProvider' import { useText } from '#/providers/TextProvider' import { useSuspenseQuery } from '@tanstack/react-query' import type { RendererObject } from 'marked' @@ -23,6 +24,7 @@ export function MarkdownViewer(props: MarkdownViewerProps) { const { text, imgUrlResolver, renderer = defaultRenderer, testId } = props const { getText } = useText() + const logger = useLogger() const markedInstance = marked.use({ renderer: Object.assign({}, defaultRenderer, renderer) }) @@ -36,7 +38,15 @@ export function MarkdownViewer(props: MarkdownViewerProps) { const href = token.href token.raw = href - token.href = await args.imgUrlResolver(href).catch(() => null) + token.href = await args + .imgUrlResolver(href) + .then((url) => { + return url + }) + .catch((error) => { + logger.error(error) + return null + }) token.text = getText('arbitraryFetchImageError') } }, diff --git a/app/gui/src/dashboard/components/MarkdownViewer/defaultRenderer.ts b/app/gui/src/dashboard/components/MarkdownViewer/defaultRenderer.ts index 032da7bf4f5d..ac2a909e2987 100644 --- a/app/gui/src/dashboard/components/MarkdownViewer/defaultRenderer.ts +++ b/app/gui/src/dashboard/components/MarkdownViewer/defaultRenderer.ts @@ -26,13 +26,11 @@ export const defaultRenderer: RendererObject = { return `${this.parser.parseInline(tokens)}` }, /** The renderer for images. */ - image({ href, title, raw, text }) { + image({ href, title, raw }) { const alt = title ?? '' return ` - - ${text} - + ${alt} ` }, /** The renderer for code. */