mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo.git
synced 2026-09-21 16:52:01 -07:00
Merged the refactorer handoff (e77e174). The architecture holds: the image
helpers isImageUrl/imageAltText are pure post-links functions, failed-images
is a pure state transition, and PostImage/PostContent stay presentational
with no IO. Added a unit test for isImageUrl non-string input to kill the
last surviving language mutant; mutation and acceptance manifests are
tool-written.
By architect.
180 lines
6.8 KiB
JavaScript
180 lines
6.8 KiB
JavaScript
/*
|
|
Unit tests for the post link parser.
|
|
|
|
The parser turns a post's text into text and link segments. A link segment
|
|
is either an http(s) URL (scheme preserved) or a bare domain (linked with an
|
|
https scheme while its visible text is left as written). Trailing sentence
|
|
punctuation never becomes part of the link. Plain text and email addresses
|
|
stay plain text.
|
|
*/
|
|
|
|
'use strict'
|
|
|
|
const test = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
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'
|
|
assert.deepEqual(parsePostLinks(text), [{ type: 'text', text }])
|
|
})
|
|
|
|
test('parsePostLinks returns an empty text segment for an empty string', () => {
|
|
assert.deepEqual(parsePostLinks(''), [{ type: 'text', text: '' }])
|
|
})
|
|
|
|
test('parsePostLinks links an https URL and preserves its scheme', () => {
|
|
assert.deepEqual(parsePostLinks('visit https://memo.fullstackcash.net for details'), [
|
|
{ type: 'text', text: 'visit ' },
|
|
{ type: 'link', href: 'https://memo.fullstackcash.net', text: 'https://memo.fullstackcash.net' },
|
|
{ type: 'text', text: ' for details' }
|
|
])
|
|
})
|
|
|
|
test('parsePostLinks links an http URL and preserves its scheme', () => {
|
|
assert.deepEqual(parsePostLinks('link http://example.com/path here'), [
|
|
{ type: 'text', text: 'link ' },
|
|
{ type: 'link', href: 'http://example.com/path', text: 'http://example.com/path' },
|
|
{ type: 'text', text: ' here' }
|
|
])
|
|
})
|
|
|
|
test('parsePostLinks keeps trailing punctuation out of the link', () => {
|
|
assert.deepEqual(parsePostLinks('read https://memo.fullstackcash.net, then reply'), [
|
|
{ type: 'text', text: 'read ' },
|
|
{ type: 'link', href: 'https://memo.fullstackcash.net', text: 'https://memo.fullstackcash.net' },
|
|
{ type: 'text', text: ', then reply' }
|
|
])
|
|
})
|
|
|
|
test('parsePostLinks links a bare domain with an https href and unchanged visible text', () => {
|
|
assert.deepEqual(parsePostLinks('visit memo.fullstackcash.net for details'), [
|
|
{ type: 'text', text: 'visit ' },
|
|
{ type: 'link', href: 'https://memo.fullstackcash.net', text: 'memo.fullstackcash.net' },
|
|
{ type: 'text', text: ' for details' }
|
|
])
|
|
})
|
|
|
|
test('parsePostLinks links a bare domain with a path', () => {
|
|
assert.deepEqual(parsePostLinks('see memo.fullstackcash.net/feed now'), [
|
|
{ type: 'text', text: 'see ' },
|
|
{ type: 'link', href: 'https://memo.fullstackcash.net/feed', text: 'memo.fullstackcash.net/feed' },
|
|
{ type: 'text', text: ' now' }
|
|
])
|
|
})
|
|
|
|
test('parsePostLinks links a bare www domain', () => {
|
|
assert.deepEqual(parsePostLinks('go to www.example.com now'), [
|
|
{ type: 'text', text: 'go to ' },
|
|
{ type: 'link', href: 'https://www.example.com', text: 'www.example.com' },
|
|
{ type: 'text', text: ' now' }
|
|
])
|
|
})
|
|
|
|
test('parsePostLinks keeps trailing punctuation out of a bare-domain link', () => {
|
|
assert.deepEqual(parsePostLinks('visit memo.fullstackcash.net, it is live'), [
|
|
{ type: 'text', text: 'visit ' },
|
|
{ type: 'link', href: 'https://memo.fullstackcash.net', text: 'memo.fullstackcash.net' },
|
|
{ type: 'text', text: ', it is live' }
|
|
])
|
|
})
|
|
|
|
test('parsePostLinks handles multiple links in one post', () => {
|
|
assert.deepEqual(
|
|
parsePostLinks('watch https://youtu.be/dQw4w9WgXcQ then read https://memo.fullstackcash.net'),
|
|
[
|
|
{ type: 'text', text: 'watch ' },
|
|
{ type: 'link', href: 'https://youtu.be/dQw4w9WgXcQ', text: 'https://youtu.be/dQw4w9WgXcQ' },
|
|
{ type: 'text', text: ' then read ' },
|
|
{ type: 'link', href: 'https://memo.fullstackcash.net', text: 'https://memo.fullstackcash.net' }
|
|
]
|
|
)
|
|
})
|
|
|
|
test('parsePostLinks does not link an email address', () => {
|
|
const text = 'write to me at chris@example.com please'
|
|
assert.deepEqual(parsePostLinks(text), [{ type: 'text', text }])
|
|
})
|
|
|
|
test('parsePostLinks does not link a dotted user name before an @ sign', () => {
|
|
const text = 'write to first.last@example.com please'
|
|
assert.deepEqual(parsePostLinks(text), [{ type: 'text', text }])
|
|
})
|
|
|
|
test('parsePostLinks links a bare domain at the start of the text', () => {
|
|
assert.deepEqual(parsePostLinks('memo.fullstackcash.net is live'), [
|
|
{ type: 'link', href: 'https://memo.fullstackcash.net', text: 'memo.fullstackcash.net' },
|
|
{ type: 'text', text: ' is live' }
|
|
])
|
|
})
|
|
|
|
test('parsePostLinks does not link a bare domain glued to a leading token character', () => {
|
|
assert.deepEqual(parsePostLinks('-example.com'), [{ type: 'text', text: '-example.com' }])
|
|
})
|
|
|
|
test('parsePostLinks round-trips: segments reconstruct the original text', () => {
|
|
const samples = [
|
|
'just a normal memo',
|
|
'visit memo.fullstackcash.net for details',
|
|
'read https://memo.fullstackcash.net, then reply',
|
|
'go to www.example.com now',
|
|
'watch https://youtu.be/dQw4w9WgXcQ then read https://memo.fullstackcash.net',
|
|
'write to chris@example.com please',
|
|
''
|
|
]
|
|
for (const text of samples) {
|
|
const rebuilt = parsePostLinks(text)
|
|
.map((segment) => segment.text)
|
|
.join('')
|
|
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('isImageUrl rejects non-string input', () => {
|
|
assert.equal(isImageUrl(null), false)
|
|
assert.equal(isImageUrl(undefined), false)
|
|
assert.equal(isImageUrl(12345), false)
|
|
assert.equal(isImageUrl({}), 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')
|
|
})
|