Files
YouTube.js/test/main.test.ts
Daniel Wykerd 2ccbe2ce62 refactor!: cleanup platform support (#306)
* refactor!: cleanup platform support

* chore: lint

* fix: web platform

* feat: provide UniversalCache

Provide UniversalCache as a wrapper around Platform.shim.Cache.

* fix: invalid import

* refactor: remove isolated-vm support

* fix: type info

* refactor: cleanup exports

* fix: mark jintr as external dependency

In the bundled CJS node build, mark jintr as external.

* chore: add additional exports

web exports provide a way to select web implementation manually without
relying on the bundler to select it correctly from the "exports" field

web points to src/platform/web.js
web.bundle points to bundle/browser.js
web.bundle.browser points to bundle/browser.min.js

agnostic exports provide users of the library to provide their own
platform implementation without first importing the default one.

agnostic points to src/platform/lib.ts

* fix: toDash on web

* revert: eval is synchronous

* fix: use serializeDOM in FormatUtils

* ci: automate releases with `release-please`

* chore: clean up workflow files

* ci: fix NPM publish action

---------

Co-authored-by: LuanRT <luan.lrt4@gmail.com>
2023-02-12 04:21:44 -03:00

285 lines
10 KiB
TypeScript

import fs from 'fs';
import { Innertube, Utils } from '../bundle/node.cjs';
import { CHANNELS, VIDEOS } from './constants';
import type TextRun from '../src/parser/classes/misc/TextRun';
describe('YouTube.js Tests', () => {
let yt: Innertube;
beforeAll(async () => {
yt = await Innertube.create();
});
describe('Info', () => {
let info: any;
it('should retrieve full video info', async () => {
info = await yt.getInfo(VIDEOS[0].ID);
expect(info.basic_info.id).toBe(VIDEOS[0].ID);
});
it('should have captions', () => {
expect(info.captions?.caption_tracks.length).toBeGreaterThan(0);
});
it('should have chapters', () => {
const markers_map = info.player_overlays?.decorated_player_bar?.player_bar?.markers_map;
const chapters = (
markers_map?.get({ marker_key: 'AUTO_CHAPTERS' }) ||
markers_map?.get({ marker_key: 'DESCRIPTION_CHAPTERS' })
)?.value?.chapters;
expect(chapters).toBeDefined();
});
it('should have heatmap', () => {
const markers_map = info.player_overlays?.decorated_player_bar?.player_bar?.markers_map;
const heatmap = markers_map?.get({ marker_key: 'HEATSEEKER' })?.value?.heatmap;
expect(heatmap).toBeDefined();
});
it('should have watch next feed', () => {
expect(info.watch_next_feed).toBeDefined();
});
it('should retrieve basic video info', async () => {
const b_info = await yt.getBasicInfo(VIDEOS[0].ID);
expect(b_info.basic_info.id).toBe(VIDEOS[0].ID);
});
it('should be upcoming', async () => {
const b_info = await yt.getBasicInfo(VIDEOS[4].ID);
expect(b_info.basic_info.is_upcoming).toBe(true);
});
it('should be live', async () => {
const b_info = await yt.getBasicInfo(VIDEOS[5].ID);
expect(b_info.basic_info.is_live).toBe(true);
});
it('should extract live stream start timestamp', async () => {
const b_info = await yt.getBasicInfo(VIDEOS[4].ID);
expect(b_info.basic_info.start_timestamp).not.toBeNull()
expect(b_info.basic_info.start_timestamp!.toISOString()).toBe('2024-03-30T23:00:00.000Z');
})
});
describe('Search', () => {
let search: any;
it('should search', async () => {
search = await yt.search(VIDEOS[0].QUERY);
expect(search.results.length).toBeGreaterThanOrEqual(5);
expect(search.playlists).toBeDefined();
expect(search.channels).toBeDefined();
expect(search.has_continuation).toBe(true);
});
it('should retrieve search continuation', async () => {
const next = await search.getContinuation();
expect(next.results.length).toBeGreaterThanOrEqual(5);
expect(search.playlists).toBeDefined();
expect(search.channels).toBeDefined();
expect(search.has_continuation).toBe(true);
});
it('should retrieve search suggestions', async () => {
const suggestions = await yt.getSearchSuggestions(VIDEOS[0].QUERY);
expect(suggestions.length).toBeGreaterThanOrEqual(1);
});
});
describe('Comments', () => {
let comment_section: Awaited<ReturnType<(typeof yt)['getComments']>>;
it('should retrieve comments', async () => {
comment_section = await yt.getComments(VIDEOS[1].ID);
expect(comment_section.contents.length).toBeGreaterThan(0);
});
it('should parse formatted comments', async () => {
const comment_section = await yt.getComments(VIDEOS[3].ID);
const channel_owner_thread = comment_section.contents.find(t => t.comment?.author_is_channel_owner);
expect(channel_owner_thread).not.toBeUndefined();
expect(channel_owner_thread!.comment?.content.runs?.length).toBeGreaterThan(0);
const runs = channel_owner_thread!.comment!.content.runs! as TextRun[];
expect(runs[0].bold).toBeTruthy();
expect(runs[2].italics).toBeTruthy();
expect(runs[4].strikethrough).toBeTruthy();
})
it('should retrieve next batch of comments', async () => {
const next = await comment_section.getContinuation();
expect(next.contents.length).toBeGreaterThan(0);
});
it('should retrieve comment replies', async () => {
const thread = comment_section.contents.first();
expect(thread?.has_replies).toBe(true);
const full_thread = await thread?.getReplies();
expect(full_thread?.comment?.comment_id).toBe(thread?.comment?.comment_id);
expect(full_thread?.replies?.length).toBeLessThanOrEqual(10);
});
});
describe('General', () => {
it('should create sessions without a player instance', async () => {
const nop_yt = await Innertube.create({ retrieve_player: false });
expect(nop_yt.session.player).toBeUndefined();
});
it('should create a session from data generated locally', async () => {
const loc_yt = await Innertube.create({ generate_session_locally: true, retrieve_player: false });
expect(loc_yt.session.context).toBeDefined();
});
it('should resolve a URL', async () => {
const url = await yt.resolveURL('https://www.youtube.com/@linustechtips');
expect(url.payload.browseId).toBe(CHANNELS[0].ID);
});
it('should retrieve playlist', async () => {
const playlist = await yt.getPlaylist('PLLw0AzOz95FU7w2juhPECP9NyGhbZmz_t');
expect(playlist.items.length).toBeLessThanOrEqual(100);
});
it('should retrieve channel', async () => {
const channel = await yt.getChannel(CHANNELS[0].ID);
expect(channel.videos.length).toBeGreaterThan(0);
expect(channel.shelves.length).toBeGreaterThan(0);
const videos_tab = await channel.getVideos();
expect(videos_tab.videos.length).toBeGreaterThan(0);
const filtered_list = await videos_tab.applyFilter('Popular');
expect(filtered_list.videos.length).toBeGreaterThan(0);
});
it('should detect missing channel tabs', async () => {
const channel = await yt.getChannel(CHANNELS[2].ID);
expect(channel.has_home).toBe(true);
expect(channel.has_videos).toBe(true);
expect(channel.has_shorts).toBe(false);
expect(channel.has_live_streams).toBe(false);
expect(channel.has_playlists).toBe(true);
expect(channel.has_community).toBe(true);
expect(channel.has_channels).toBe(true);
expect(channel.has_about).toBe(true);
expect(channel.has_search).toBe(true);
})
it('should have no channel tabs', async () => {
const channel = await yt.getChannel(CHANNELS[3].ID);
expect(channel.has_home).toBe(false);
expect(channel.has_videos).toBe(false);
expect(channel.has_shorts).toBe(false);
expect(channel.has_live_streams).toBe(false);
expect(channel.has_playlists).toBe(false);
expect(channel.has_community).toBe(false);
expect(channel.has_channels).toBe(false);
expect(channel.has_about).toBe(false);
expect(channel.has_search).toBe(false);
})
it('should retrieve home feed', async () => {
const homefeed = await yt.getHomeFeed();
expect(homefeed.header).toBeDefined();
expect(homefeed.contents).toBeDefined();
expect(homefeed.videos.length).toBeGreaterThan(0);
});
it('should retrieve trending content', async () => {
const trending = await yt.getTrending();
expect(trending.videos.length).toBeGreaterThan(0);
});
it('should download video', async () => {
const result = await download(VIDEOS[1].ID, yt);
expect(result).toBeTruthy();
}, 30000);
});
describe('YouTube Music', () => {
let search: any;
it('should search', async () => {
search = await yt.music.search(VIDEOS[1].QUERY);
expect(search.songs?.contents.length).toBeLessThanOrEqual(3);
});
it('should retrieve search suggestions', async () => {
const suggestions = await yt.music.getSearchSuggestions(VIDEOS[1].QUERY);
expect(suggestions.length).toBeLessThanOrEqual(10);
});
it('should retrieve track info', async () => {
const info = await yt.music.getInfo(VIDEOS[1].ID);
expect(info.basic_info.id).toBe(VIDEOS[1].ID);
});
it('should retrieve the "Related" tab', async () => {
const info = await yt.music.getInfo(VIDEOS[1].ID);
const related = await info.getRelated();
expect((related as any).length).toBeGreaterThan(3);
});
it('should retrieve albums', async () => {
const album = await yt.music.getAlbum(search.albums?.contents[0]?.id);
expect(album.contents).toBeDefined();
});
it('should retrieve artists', async () => {
const artist = await yt.music.getArtist(search.artists?.contents[0]?.id);
expect(artist.sections).toBeDefined();
});
it('should retrieve playlists', async () => {
const playlist = await yt.music.getPlaylist(search.playlists?.contents[0]?.id);
expect(playlist.items).toBeDefined();
});
});
describe('YouTube Kids', () => {
it('should search', async () => {
const search = await yt.kids.search('cocomelon');
expect(search.estimated_results).toBeDefined();
expect(search.contents?.length).toBeGreaterThan(0);
});
it('should retrieve home feed', async () => {
const homefeed = await yt.kids.getHomeFeed();
expect(homefeed.contents).toBeDefined();
expect(homefeed.videos.length).toBeGreaterThan(0);
});
it('should retrieve video info', async () => {
const info = await yt.kids.getInfo(VIDEOS[6].ID);
expect(info.basic_info?.id).toBe(VIDEOS[6].ID);
});
it('should retrieve a channel', async () => {
const channel = await yt.kids.getChannel(CHANNELS[1].ID);
expect(channel.videos.length).toBeGreaterThan(0);
});
});
});
async function download(id: string, yt: Innertube): Promise<boolean> {
// TODO: add back info
// let got_video_info = false;
const stream = await yt.download(id, { type: 'video+audio' });
const file = fs.createWriteStream(`./${id}.mp4`);
for await (const chunk of Utils.streamToIterable(stream)) {
file.write(chunk);
}
return fs.existsSync(`./${id}.mp4`); // && got_video_info;
}