278 lines
10 KiB
TypeScript
278 lines
10 KiB
TypeScript
import { renderToString } from 'hono/jsx/dom/server';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { Atom as RenderAtom, json as renderJson, RSS as RenderRSS, rss3 as renderRss3 } from '@/utils/render';
|
|
import Atom from '@/views/atom';
|
|
import jsonView from '@/views/json';
|
|
import RSS from '@/views/rss';
|
|
import rss3View from '@/views/rss3';
|
|
|
|
afterEach(() => {
|
|
vi.resetModules();
|
|
vi.unmock('@/config');
|
|
vi.unmock('@/utils/debug-info');
|
|
vi.unmock('@/utils/git-hash');
|
|
});
|
|
|
|
describe('view exports', () => {
|
|
it('re-exports view helpers from render', () => {
|
|
expect(RenderAtom).toBe(Atom);
|
|
expect(RenderRSS).toBe(RSS);
|
|
expect(renderJson).toBe(jsonView);
|
|
expect(renderRss3).toBe(rss3View);
|
|
});
|
|
});
|
|
|
|
describe('Atom view', () => {
|
|
it('renders optional fields and media extensions', () => {
|
|
const html = renderToString(
|
|
<Atom
|
|
data={{
|
|
title: 'Atom Feed',
|
|
link: 'https://example.com',
|
|
description: 'Atom Description',
|
|
language: 'zh',
|
|
lastBuildDate: '2024-01-01T00:00:00Z',
|
|
icon: 'https://example.com/icon.png',
|
|
logo: 'https://example.com/logo.png',
|
|
item: [
|
|
{
|
|
title: 'Item One',
|
|
link: 'https://example.com/one',
|
|
description: 'Entry One',
|
|
pubDate: '2024-01-01T00:00:00Z',
|
|
updated: '2024-01-02T00:00:00Z',
|
|
author: 'Author One',
|
|
category: 'News',
|
|
media: {
|
|
content: {
|
|
url: 'https://example.com/video.mp4',
|
|
},
|
|
},
|
|
upvotes: 12,
|
|
downvotes: 1,
|
|
comments: 3,
|
|
},
|
|
{
|
|
title: 'Item Two',
|
|
link: 'https://example.com/two',
|
|
description: 'Entry Two',
|
|
category: ['Tech', 'AI'],
|
|
guid: 'guid-2',
|
|
},
|
|
],
|
|
}}
|
|
/>
|
|
);
|
|
|
|
expect(html).toContain('<icon>https://example.com/icon.png</icon>');
|
|
expect(html).toContain('<logo>https://example.com/logo.png</logo>');
|
|
expect(html).toContain('media:content');
|
|
expect(html).toContain('term="News"');
|
|
expect(html).toContain('term="Tech"');
|
|
expect(html).toContain('rsshub:upvotes');
|
|
expect(html).toContain('rsshub:downvotes');
|
|
expect(html).toContain('rsshub:comments');
|
|
});
|
|
});
|
|
|
|
describe('RSS view', () => {
|
|
it('renders itunes, media, and telegram image variants', () => {
|
|
const html = renderToString(
|
|
<RSS
|
|
data={{
|
|
title: 'RSS Feed',
|
|
link: 'https://t.me/s/rsshub',
|
|
atomlink: 'https://example.com/feed.xml',
|
|
description: 'RSS Description',
|
|
language: 'en',
|
|
lastBuildDate: '2024-01-01T00:00:00Z',
|
|
ttl: 60,
|
|
itunes_author: 'Podcast Author',
|
|
itunes_category: 'Tech',
|
|
itunes_explicit: 'true',
|
|
image: 'https://example.com/image.jpg',
|
|
item: [
|
|
{
|
|
title: 'Episode One',
|
|
link: 'https://example.com/one',
|
|
description: 'Episode One',
|
|
guid: 'episode-1',
|
|
pubDate: '2024-01-01T00:00:00Z',
|
|
author: 'Host',
|
|
image: 'https://example.com/episode.jpg',
|
|
itunes_item_image: 'https://example.com/itunes.jpg',
|
|
itunes_duration: '01:02:03',
|
|
enclosure_url: 'https://example.com/audio.mp3',
|
|
enclosure_length: '123',
|
|
enclosure_type: 'audio/mpeg',
|
|
category: 'Podcast',
|
|
media: {
|
|
content: {
|
|
url: 'https://example.com/media.mp4',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
title: 'Episode Two',
|
|
link: 'https://example.com/two',
|
|
description: 'Episode Two',
|
|
category: ['News', 'Updates'],
|
|
},
|
|
],
|
|
}}
|
|
/>
|
|
);
|
|
|
|
expect(html).toContain('xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"');
|
|
expect(html).toContain('xmlns:media="http://search.yahoo.com/mrss/"');
|
|
expect(html).toContain('<itunes:author>Podcast Author</itunes:author>');
|
|
expect(html).toContain('itunes:category text="Tech"');
|
|
expect(html).toContain('<itunes:explicit>true</itunes:explicit>');
|
|
expect(html).toContain('<height>31</height>');
|
|
expect(html).toContain('<width>88</width>');
|
|
expect(html).toContain('media:content');
|
|
expect(html).toContain('<itunes:image href="https://example.com/itunes.jpg"');
|
|
expect(html).toContain('<enclosure url="https://example.com/audio.mp3"');
|
|
expect(html).toContain('<category>Podcast</category>');
|
|
expect(html).toContain('<category>News</category>');
|
|
});
|
|
});
|
|
|
|
describe('JSON view', () => {
|
|
it('renders summary, authors, tags, attachments, and extras', () => {
|
|
const jsonOutput = jsonView({
|
|
title: 'JSON Feed',
|
|
link: 'https://example.com',
|
|
feedLink: 'https://example.com/feed.json',
|
|
description: 'JSON Description',
|
|
language: 'en',
|
|
author: 'Feed Author',
|
|
image: 'https://example.com/icon.png',
|
|
item: [
|
|
{
|
|
title: 'Item One',
|
|
link: 'https://example.com/one',
|
|
description: 'Entry One',
|
|
guid: 'guid-1',
|
|
content: {
|
|
html: '<p>hello</p>',
|
|
text: 'hello',
|
|
},
|
|
image: 'https://example.com/image.jpg',
|
|
banner: 'https://example.com/banner.jpg',
|
|
pubDate: '2024-01-01T00:00:00Z',
|
|
updated: '2024-01-02T00:00:00Z',
|
|
author: 'Item Author',
|
|
category: ['Tech', 'AI'],
|
|
enclosure_url: 'https://example.com/audio.mp3',
|
|
enclosure_type: 'audio/mpeg',
|
|
enclosure_title: 'Audio Title',
|
|
enclosure_length: 123,
|
|
itunes_duration: 321,
|
|
_extra: { foo: 'bar' },
|
|
},
|
|
],
|
|
});
|
|
|
|
const feed = JSON.parse(jsonOutput);
|
|
|
|
expect(feed.version).toBe('https://jsonfeed.org/version/1.1');
|
|
expect(feed.title).toBe('JSON Feed');
|
|
expect(feed.home_page_url).toBe('https://example.com');
|
|
expect(feed.feed_url).toBe('https://example.com/feed.json');
|
|
expect(feed.description).toBe('JSON Description - Powered by RSSHub');
|
|
expect(feed.authors).toEqual([{ name: 'Feed Author' }]);
|
|
expect(feed.items).toHaveLength(1);
|
|
expect(feed.items[0]).toMatchObject({
|
|
id: 'guid-1',
|
|
url: 'https://example.com/one',
|
|
title: 'Item One',
|
|
content_html: '<p>hello</p>',
|
|
content_text: 'hello',
|
|
summary: 'Entry One',
|
|
image: 'https://example.com/image.jpg',
|
|
banner_image: 'https://example.com/banner.jpg',
|
|
date_published: '2024-01-01T00:00:00Z',
|
|
date_modified: '2024-01-02T00:00:00Z',
|
|
authors: [{ name: 'Item Author' }],
|
|
tags: ['Tech', 'AI'],
|
|
attachments: [
|
|
{
|
|
url: 'https://example.com/audio.mp3',
|
|
mime_type: 'audio/mpeg',
|
|
title: 'Audio Title',
|
|
size_in_bytes: 123,
|
|
duration_in_seconds: 321,
|
|
},
|
|
],
|
|
});
|
|
expect(feed.items[0]._extra).toEqual({ foo: 'bar' });
|
|
});
|
|
});
|
|
|
|
describe('Index view', () => {
|
|
const renderIndex = async (debugInfo: string | undefined, debugQuery: string | undefined) => {
|
|
const debugData = {
|
|
hitCache: 2,
|
|
request: 10,
|
|
etag: 3,
|
|
error: 1,
|
|
routes: {
|
|
'/foo': 5,
|
|
'/bar': 2,
|
|
},
|
|
paths: {
|
|
'/foo?x=1': 4,
|
|
'/bar?x=2': 1,
|
|
},
|
|
errorRoutes: {
|
|
'/error': 2,
|
|
'/fail': 1,
|
|
},
|
|
errorPaths: {
|
|
'/error?x=1': 1,
|
|
'/fail?x=2': 1,
|
|
},
|
|
};
|
|
|
|
vi.doMock('@/config', () => ({
|
|
config: {
|
|
debugInfo,
|
|
disallowRobot: true,
|
|
nodeName: 'TestNode',
|
|
cache: {
|
|
routeExpire: 120,
|
|
},
|
|
},
|
|
}));
|
|
vi.doMock('@/utils/debug-info', () => ({
|
|
getDebugInfo: () => debugData,
|
|
}));
|
|
vi.doMock('@/utils/git-hash', () => ({
|
|
gitHash: 'abc123',
|
|
gitDate: new Date('2020-01-01T00:00:00Z'),
|
|
}));
|
|
|
|
const { default: Index } = await import('@/views/index');
|
|
|
|
return renderToString(<Index debugQuery={debugQuery} />);
|
|
};
|
|
|
|
it('shows debug info when enabled', async () => {
|
|
const html = await renderIndex('secret', 'secret');
|
|
|
|
expect(html).toContain('Debug Info');
|
|
expect(html).toContain('TestNode');
|
|
expect(html).toContain('abc123');
|
|
expect(html).toContain('5 /foo');
|
|
expect(html).toContain('2 /error');
|
|
});
|
|
|
|
it('hides debug info when disabled', async () => {
|
|
const html = await renderIndex('false', 'secret');
|
|
|
|
expect(html).not.toContain('Debug Info');
|
|
});
|
|
});
|