Files
website/src/lib/projects.test.ts
T
stan 079399635d
Deploy production / deploy (push) Successful in 1m22s
trackers: media hub with webtoons, podcasts and videos; shows and anime filters
- /trackers/media: one card per child (reading, movies, shows, webtoons, music, podcasts,
  videos) with quick stats, a combined currently-consuming feed and a mixed watchlist
- webtoons, podcasts, videos: new owner-editable trackers on one generic media.items table,
  JSON import (owner form or scripts/media-run.sh)
- shows: /trackers/shows -> /trackers/movies?type=show; anime flag on movies.titles
  (backfilled, set from TMDB, owner toggle) and ?type=anime filter
- nav: media replaces the top-level reading, movies and music entries; their URLs are kept
2026-10-26 23:34:22 +00:00

78 lines
3.3 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { activeGames, pokemonEntryFor, sortedGames, visibleTrackers } from './projects';
describe('trackers', () => {
it('lists tasks, money, media first for the owner', () => {
expect(
visibleTrackers(true)
.map((t) => t.name)
.slice(0, 4)
).toEqual(['tasks', 'money', 'media', 'spotting']);
});
it('hides private trackers when logged out', () => {
const names = visibleTrackers(false).map((t) => t.name);
expect(names).not.toContain('tasks');
expect(names).not.toContain('health');
expect(names.slice(0, 3)).toEqual(['money', 'media', 'spotting']);
});
it('uses short single-word names', () => {
for (const t of visibleTrackers(true)) expect(t.name).toMatch(/^[a-z]+$/);
});
it('locks every owner-only tracker, drops the private: prefix', () => {
const owner = visibleTrackers(true);
const pub = new Set(visibleTrackers(false).map((t) => t.name));
for (const t of owner) {
if (!pub.has(t.name)) expect(t.locked).toBe(true);
expect(t.subtitle).not.toMatch(/^private:/);
}
expect(owner.find((t) => t.name === 'money')?.locked).toBe(true);
expect(owner.find((t) => t.name === 'media')?.locked).toBeUndefined();
});
it('nests reading, movies, shows, webtoons, music, podcasts and videos under media', () => {
const media = visibleTrackers(false).find((t) => t.name === 'media')!;
expect(media.href).toBe('/trackers/media');
expect(media.children!.map((c) => c.name)).toEqual([
'reading',
'movies',
'shows',
'webtoons',
'music',
'podcasts',
'videos'
]);
expect(media.children!.find((c) => c.name === 'music')!.href).toBe('/trackers/music');
const top = visibleTrackers(true).map((t) => t.name);
for (const n of ['reading', 'movies', 'music']) expect(top).not.toContain(n);
});
it('locks movies and shows inside media when the movies tracker is private', () => {
const pub = visibleTrackers(false, [], ['movies', 'shows']).find((t) => t.name === 'media')!;
expect(pub.children!.map((c) => c.name)).not.toContain('movies');
const own = visibleTrackers(true, [], ['movies', 'shows']).find((t) => t.name === 'media')!;
expect(own.children!.find((c) => c.name === 'shows')?.locked).toBe(true);
});
it('splits games into active first and non-active (flagged) after', () => {
const off = ['lol', 'black-ops', 'pokemon-x-y'];
const games = visibleTrackers(true, off).find((t) => t.name === 'games')!.children!;
const inactive = games.filter((g) => g.inactive).map((g) => g.href);
expect(inactive).toEqual([
'/trackers/games/lol',
'/trackers/games/black-ops',
'/trackers/games/pokemon-x-y'
]);
expect(games.slice(-3).every((g) => g.inactive)).toBe(true);
expect(games.filter((g) => !g.inactive).every((g) => g.featured)).toBe(true);
expect(activeGames(off)).toHaveLength(sortedGames(off).length - 3);
expect(
visibleTrackers(true)
.find((t) => t.name === 'games')!
.children!.some((g) => g.inactive)
).toBe(false);
});
it('maps Pokémon journal games to their generation entry', () => {
expect(pokemonEntryFor('platinum')).toBe('pokemon-diamond-pearl-platinum');
expect(pokemonEntryFor('soulsilver')).toBe('pokemon-heartgold-soulsilver');
expect(pokemonEntryFor('the-teal-mask-violet')).toBe('pokemon-scarlet-violet');
expect(pokemonEntryFor('champions')).toBeUndefined();
});
});