'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const { anchorsFor, topTagsFor, ANCHOR_WEIGHTS, MAX_ANCHORS } = require('../discovery/anchors'); const user = (settings = { discoveryEnabled: true }) => ({ name: 'ann', settings }); const manga = (id, anilist, title = `Manga ${id}`) => ({ id, title, meta: anilist ? { 'x-anilist': { id: anilist } } : {} }); test('anchors are empty without opt-in', () => { const library = [manga('a', 1)]; assert.deepEqual(anchorsFor({ user: user({}), db: {}, library, greatest: { entries: [] } }), []); assert.deepEqual(anchorsFor({ user: user({ discoveryEnabled: false }), db: {}, library, greatest: { entries: [] } }), []); }); test('weights, dedupe and cap', () => { const greatest = { entries: [{ id: 'g1', title: 'Great One', metadata: { id: 7 } }, { id: 'g2', title: 'No Meta' }] }; const db = { greatestRatings: { ann: { g1: 'good', g2: 'flame' } } }; const anchors = anchorsFor({ user: user(), db, library: [manga('m7', 7, 'Owned Seven')], greatest }); assert.deepEqual(anchors, [{ anilistId: 7, weight: 3, source: 'good', title: 'Great One' }]); assert.equal(ANCHOR_WEIGHTS.flame, 5); const many = Array.from({ length: 20 }, (_, i) => manga(`m${i}`, i + 1)); const capped = anchorsFor({ user: user(), db: {}, library: many, greatest: { entries: [] } }); assert.equal(capped.length, MAX_ANCHORS); assert.equal(capped.length, 15); assert.deepEqual(capped.map(a => a.anilistId), Array.from({ length: 15 }, (_, i) => i + 1)); assert.equal(capped[0].source, 'owned'); }); test('reading anchors need the reading opt-in', () => { const library = [manga('m1', 1), manga('m2', 2)]; const db = { progress: { ann: { m1: { c1: { done: true } }, m2: { c1: { done: false } } } } }; const off = anchorsFor({ user: user(), db, library, greatest: { entries: [] } }); assert.deepEqual(off.map(a => [a.anilistId, a.source]), [[1, 'owned'], [2, 'owned']]); const on = anchorsFor({ user: user({ discoveryEnabled: true, discoveryReadingActivity: true }), db, library, greatest: { entries: [] } }); assert.deepEqual(on.map(a => [a.anilistId, a.source, a.weight, a.title]), [[1, 'read', 3, 'Manga m1'], [2, 'owned', 1, 'Manga m2']]); }); test('feedback keys parse anilist ids and ignore others', () => { const db = { discoveryFeedback: { ann: { 'anilist:10': 'interested', 'anilist:11': 'maybe', 'anilist:12': 'dismissed', 'anilist:13': 'hidden', 'greatest:x': 'interested', 'anilist:abc': 'interested' } } }; const anchors = anchorsFor({ user: user(), db, library: [], greatest: { entries: [] } }); assert.deepEqual(anchors, [ { anilistId: 10, weight: 4, source: 'interested', title: null }, { anilistId: 11, weight: 2, source: 'maybe', title: null }]); }); test('top tags weigh anchors, skip low rank and adult tags', () => { const records = { 1: { tags: [{ name: 'Action', rank: 100, kind: 'genre', isAdult: false }, { name: 'Ecchi', rank: 90, kind: 'tag', isAdult: true }, { name: 'Faint', rank: 59, kind: 'tag', isAdult: false }] }, 2: { tags: [{ name: 'Action', rank: 100, kind: 'genre', isAdult: false }, { name: 'Zeta', rank: 60, kind: 'tag', isAdult: false }, { name: 'Alpha', rank: 80, kind: 'tag', isAdult: false }] } }; const anchors = [{ anilistId: 1, weight: 5 }, { anilistId: 2, weight: 3 }, { anilistId: 3, weight: 5 }]; const recordOf = id => records[id] || null; assert.deepEqual(topTagsFor(anchors, recordOf), [ { name: 'Action', kind: 'genre' }, { name: 'Alpha', kind: 'tag' }, { name: 'Zeta', kind: 'tag' }]); assert.deepEqual(topTagsFor(anchors, recordOf, { mature: true }), [ { name: 'Action', kind: 'genre' }, { name: 'Ecchi', kind: 'tag' }, { name: 'Alpha', kind: 'tag' }]); }); test('top tags drop tags without a rank and survive malformed records', () => { const records = { 1: { tags: [{ name: 'NoRank', kind: 'tag', isAdult: false }, { name: 'NullRank', rank: null, kind: 'tag', isAdult: false }, null, 7, { name: 'Kept', rank: 60, kind: 'tag', isAdult: false }] }, 2: { tags: 5 }, 3: { tags: { name: 'Object', rank: 90 } }, }; const anchors = [{ anilistId: 1, weight: 5 }, { anilistId: 2, weight: 3 }, { anilistId: 3, weight: 3 }]; assert.deepEqual(topTagsFor(anchors, id => records[id]), [{ name: 'Kept', kind: 'tag' }]); });