mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo.git
synced 2026-09-21 16:52:01 -07:00
Render image URLs inline in posts
Recognize common image file extensions in post URLs (query string and fragment ignored), render them as inline images inside new-tab anchors with the filename as alt text, and fall back to a plain link when the image fails to load. Non-image URLs keep the existing plain-link behavior. By coder.
This commit is contained in:
@@ -14,8 +14,8 @@ const React = require('react')
|
||||
const ReactDOMServer = require('react-dom/server')
|
||||
const PostContent = require('../../src/components/post-feed/post-content')
|
||||
|
||||
function render (text) {
|
||||
return ReactDOMServer.renderToStaticMarkup(React.createElement(PostContent, { text }))
|
||||
function render (text, props = {}) {
|
||||
return ReactDOMServer.renderToStaticMarkup(React.createElement(PostContent, { text, ...props }))
|
||||
}
|
||||
|
||||
test('renders an http URL as an anchor that opens in a new tab', () => {
|
||||
@@ -48,3 +48,44 @@ test('renders an embedded YouTube player and a separate link together', () => {
|
||||
assert.match(html, /<a[^>]+href="https:\/\/memo\.fullstackcash\.net"/)
|
||||
assert.doesNotMatch(html, /<a[^>]+href="https:\/\/youtu\.be\/dQw4w9WgXcQ"/)
|
||||
})
|
||||
|
||||
test('renders an image URL as an inline image inside a new-tab anchor', () => {
|
||||
const html = render('https://i.imgur.com/swCI56T.jpeg Anong breed ng basil ito?')
|
||||
assert.match(html, /<a[^>]+href="https:\/\/i\.imgur\.com\/swCI56T\.jpeg"[^>]*>[\s\S]*<img/)
|
||||
assert.match(html, /<a[^>]+target="_blank"/)
|
||||
assert.match(html, /<img[^>]+src="https:\/\/i\.imgur\.com\/swCI56T\.jpeg"/)
|
||||
assert.match(html, /<img[^>]+alt="swCI56T\.jpeg"/)
|
||||
})
|
||||
|
||||
test('renders image alt text from the filename while keeping the full src URL', () => {
|
||||
const html = render('https://example.com/img/photo.webp?w=500 a wide shot')
|
||||
assert.match(html, /<img[^>]+src="https:\/\/example\.com\/img\/photo\.webp\?w=500"/)
|
||||
assert.match(html, /<img[^>]+alt="photo\.webp"/)
|
||||
})
|
||||
|
||||
test('does not render an image URL as visible text', () => {
|
||||
const html = render('https://i.imgur.com/swCI56T.jpeg basil leaves')
|
||||
const textOnly = html.replace(/<[^>]+>/g, '')
|
||||
assert.doesNotMatch(textOnly, /i\.imgur\.com/)
|
||||
assert.match(textOnly, /basil leaves/)
|
||||
})
|
||||
|
||||
test('preserves surrounding text around an image', () => {
|
||||
const html = render('https://cdn.example.com/pics/Sunset.PNG over the bay')
|
||||
assert.match(html, /over the bay/)
|
||||
})
|
||||
|
||||
test('renders a non-image URL as a plain link with no image element', () => {
|
||||
const html = render('view https://example.com/photo?format=jpg here')
|
||||
assert.match(html, /<a[^>]+href="https:\/\/example\.com\/photo\?format=jpg"/)
|
||||
assert.doesNotMatch(html, /<img/)
|
||||
})
|
||||
|
||||
test('falls back to a plain link when the image fails to load', () => {
|
||||
const url = 'https://i.imgur.com/swCI56T.jpeg'
|
||||
const html = render(`${url} basil leaves`, { initialFailedImages: [url] })
|
||||
assert.doesNotMatch(html, /<img/)
|
||||
assert.match(html, /<a[^>]+href="https:\/\/i\.imgur\.com\/swCI56T\.jpeg"/)
|
||||
assert.match(html, />https:\/\/i\.imgur\.com\/swCI56T\.jpeg<\/a>/)
|
||||
assert.match(html, /basil leaves/)
|
||||
})
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const { parsePostLinks } = require('../../src/services/post-links')
|
||||
const {
|
||||
parsePostLinks,
|
||||
isImageUrl,
|
||||
imageAltText
|
||||
} = require('../../src/services/post-links')
|
||||
|
||||
test('parsePostLinks returns a single text segment for plain text', () => {
|
||||
const text = 'just a normal memo'
|
||||
@@ -129,3 +133,40 @@ test('parsePostLinks round-trips: segments reconstruct the original text', () =>
|
||||
assert.equal(rebuilt, text)
|
||||
}
|
||||
})
|
||||
|
||||
test('isImageUrl recognizes every supported image extension', () => {
|
||||
for (const ext of ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']) {
|
||||
assert.equal(isImageUrl(`https://example.com/photo.${ext}`), true, ext)
|
||||
}
|
||||
})
|
||||
|
||||
test('isImageUrl is case-insensitive', () => {
|
||||
assert.equal(isImageUrl('https://example.com/photo.PNG'), true)
|
||||
assert.equal(isImageUrl('https://example.com/photo.JPEG'), true)
|
||||
})
|
||||
|
||||
test('isImageUrl ignores query strings and fragments', () => {
|
||||
assert.equal(isImageUrl('https://example.com/photo.webp?w=500'), true)
|
||||
assert.equal(isImageUrl('https://example.com/photo.png#section'), true)
|
||||
})
|
||||
|
||||
test('isImageUrl rejects URLs that are not images', () => {
|
||||
assert.equal(isImageUrl('https://example.com/page'), false)
|
||||
assert.equal(isImageUrl('https://example.com/logo.svg'), false)
|
||||
assert.equal(isImageUrl('https://example.com/photo?format=jpg'), false)
|
||||
assert.equal(isImageUrl('not a url'), false)
|
||||
})
|
||||
|
||||
test('imageAltText returns the URL filename', () => {
|
||||
assert.equal(imageAltText('https://i.imgur.com/swCI56T.jpeg'), 'swCI56T.jpeg')
|
||||
assert.equal(imageAltText('https://cdn.example.com/pics/Sunset.PNG'), 'Sunset.PNG')
|
||||
})
|
||||
|
||||
test('imageAltText ignores query strings when deriving the filename', () => {
|
||||
assert.equal(imageAltText('https://example.com/img/photo.webp?w=500'), 'photo.webp')
|
||||
})
|
||||
|
||||
test('imageAltText falls back to "post image" when there is no filename', () => {
|
||||
assert.equal(imageAltText('https://example.com/'), 'post image')
|
||||
assert.equal(imageAltText('not a url'), 'post image')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user