Add tests for internal linking (#808)

* Add tests for internal linking

* Add workflow for unit tests

* Use jest
This commit is contained in:
ekzyis 2024-02-17 22:53:36 +01:00 committed by GitHub
parent 81ab960d92
commit 6e6c355a3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 4033 additions and 28 deletions

20
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Tests
on: [pull_request]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18.17.0"
- name: Install
run: npm install
- name: Test
run: npm test

View File

@ -13,7 +13,7 @@ import Thumb from '../svgs/thumb-up-fill.svg'
import { toString } from 'mdast-util-to-string'
import copy from 'clipboard-copy'
import ZoomableImage, { decodeOriginalUrl } from './image'
import { IMGPROXY_URL_REGEXP } from '../lib/url'
import { IMGPROXY_URL_REGEXP, parseInternalLinks } from '../lib/url'
import reactStringReplace from 'react-string-replace'
import { rehypeInlineCodeProperty } from '../lib/md'
import { Button } from 'react-bootstrap'
@ -186,31 +186,12 @@ export default memo(function Text ({ nofollow, imgproxyUrls, children, tab, item
}
try {
// parse internal links and show as #<itemId>
const url = new URL(href)
const { pathname, searchParams } = url
// ignore empty parts which exist due to pathname starting with '/'
const emptyPart = part => !!part
const parts = pathname.split('/').filter(emptyPart)
if (parts[0] === 'items' && /^[0-9]+$/.test(parts[1])) {
const itemId = parts[1]
// check for valid item page due to referral links like /items/123456/r/ekzyis
const itemPages = ['edit', 'ots', 'related']
const itemPage = itemPages.includes(parts[2]) ? parts[2] : null
if (itemPage) {
// parse https://stacker.news/items/1/related?commentId=2
// as #1/related
// and not #2
// since commentId will be ignored anyway
const linkText = `#${itemId}/${itemPage}`
return <a target='_blank' href={href} rel='noreferrer'>{linkText}</a>
}
const commentId = searchParams.get('commentId')
const linkText = `#${commentId || itemId}`
const linkText = parseInternalLinks(href)
if (linkText) {
return <a target='_blank' href={href} rel='noreferrer'>{linkText}</a>
}
} catch {
// ignore invalid URLs
// ignore errors like invalid URLs
}
// if the link is to a youtube video, render the video

View File

@ -23,6 +23,34 @@ export function removeTracking (value) {
return value
}
/**
* parse links like https://stacker.news/items/123456 as #123456
*/
export function parseInternalLinks (href) {
const url = new URL(href)
const { pathname, searchParams } = url
// ignore empty parts which exist due to pathname starting with '/'
const emptyPart = part => !!part
const parts = pathname.split('/').filter(emptyPart)
if (parts[0] === 'items' && /^[0-9]+$/.test(parts[1])) {
const itemId = parts[1]
// check for valid item page due to referral links like /items/123456/r/ekzyis
const itemPages = ['edit', 'ots', 'related']
const itemPage = itemPages.includes(parts[2]) ? parts[2] : null
if (itemPage) {
// parse https://stacker.news/items/1/related?commentId=2
// as #1/related
// and not #2
// since commentId will be ignored anyway
const linkText = `#${itemId}/${itemPage}`
return linkText
}
const commentId = searchParams.get('commentId')
const linkText = `#${commentId || itemId}`
return linkText
}
}
export function stripTrailingSlash (uri) {
return uri.endsWith('/') ? uri.slice(0, -1) : uri
}

28
lib/url.spec.js Normal file
View File

@ -0,0 +1,28 @@
/* eslint-env jest */
import { parseInternalLinks } from './url.js'
const cases = [
['https://stacker.news/items/123', '#123'],
['https://stacker.news/items/123/related', '#123/related'],
// invalid links should not be parsed so user can spot error
['https://stacker.news/items/123foobar', undefined],
// parse referral links
['https://stacker.news/items/123/r/ekzyis', '#123'],
// use comment id if available
['https://stacker.news/items/123?commentId=456', '#456'],
// comment id + referral link
['https://stacker.news/items/123/r/ekzyis?commentId=456', '#456'],
// multiple params
['https://stacker.news/items/123?commentId=456&parentId=789', '#456']
]
describe('internal links', () => {
test.each(cases)(
'parses %p as %p',
(href, expected) => {
const actual = parseInternalLinks(href)
expect(actual).toBe(expected)
}
)
})

3956
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
"migrate": "prisma migrate deploy",
"start": "NODE_OPTIONS='--trace-warnings' next start -p $PORT --keepAliveTimeout 120000",
"lint": "standard",
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
"worker": "tsx --trace-warnings worker/index.js"
},
"dependencies": {
@ -115,6 +116,7 @@
"devDependencies": {
"@next/eslint-plugin-next": "^13.5.4",
"eslint": "^8.51.0",
"jest": "^29.7.0",
"standard": "^17.1.0"
}
}