// favela appview index — Bare-native unit test (run: `bare test/appview.test.mjs`). No network:
// fold synthetic posts into the Hyperbee index, assert timeline + author-feed order + pagination.
import '../lib/bare-globals.js'
import { storeDir } from './_store-dir.mjs'
import Corestore from 'corestore'
import { Appview } from '../lib/appview.js'
import { mkGetRecordStore } from './mk-record-store.mjs'
const assert = (cond, msg) => { if (!cond) throw new Error('FAIL: ' + msg); console.log(' ok -', msg) }
// hypercore 11 storage (RocksDB) needs a disk PATH, not random-access-memory. A unique dir under the
// gitignored data/ makes each run isolated + disposable.
const dir = storeDir('appview-test')
const store = new Corestore(dir)
const av = await new Appview(store, { getRecordStore: mkGetRecordStore(dir + '/rec') }).ready()
// three posts, two authors, deliberately out of time order
await av.putPost({ did: 'did:plc:a', rkey: '1', record: { text: 'a-old', createdAt: '2026-07-01T00:00:00.000Z' } })
await av.putPost({ did: 'did:plc:b', rkey: '1', record: { text: 'b-mid', createdAt: '2026-07-05T00:00:00.000Z' } })
await av.putPost({ did: 'did:plc:a', rkey: '2', record: { text: 'a-new', createdAt: '2026-07-10T00:00:00.000Z' } })
const tl = await av.getTimeline({ limit: 10 })
assert(tl.posts.length === 3, 'timeline has all 3 posts (merged across authors)')
assert(tl.posts[0].record.text === 'a-new', 'timeline newest-first (a-new)')
assert(tl.posts[2].record.text === 'a-old', 'timeline oldest-last (a-old)')
const feed = await av.getAuthorFeed('did:plc:a', { limit: 10 })
assert(feed.posts.length === 2, "author 'a' feed has exactly its 2 posts")
assert(feed.posts.every(p => p.did === 'did:plc:a'), 'author feed is scoped to the DID')
assert(feed.posts[0].record.text === 'a-new', 'author feed newest-first')
const p1 = await av.getTimeline({ limit: 2 })
assert(p1.posts.length === 2 && p1.cursor, 'page 1 = 2 posts + a cursor')
const p2 = await av.getTimeline({ limit: 2, cursor: p1.cursor })
assert(p2.posts.length === 1 && !p2.cursor, 'page 2 = remaining 1 post, no cursor')
assert(p2.posts[0].record.text === 'a-old', 'pagination reaches the oldest post')
// ── per-viewer author scope (the community filter) ──
const onlyA = await av.getTimeline({ limit: 10, authors: ['did:plc:a'] })
assert(onlyA.posts.length === 2 && onlyA.posts.every(p => p.did === 'did:plc:a'), 'authors filter scopes the timeline to one DID')
assert(onlyA.posts[0].record.text === 'a-new', 'filtered timeline is still newest-first')
const bothAB = await av.getTimeline({ limit: 10, authors: ['did:plc:a', 'did:plc:b'] })
assert(bothAB.posts.length === 3, 'authors filter over the union of DIDs merges their posts')
const unknown = await av.getTimeline({ limit: 10, authors: ['did:plc:notindexed'] })
assert(unknown.posts.length === 0 && !unknown.cursor, 'a scope of DIDs the node does not index yields an empty page')
const emptyScope = await av.getTimeline({ limit: 10, authors: [] })
assert(emptyScope.posts.length === 3, 'an EMPTY scope falls back to the global timeline (not an empty page)')
// filtered pagination still yields `limit` matches per page + a correct cursor
const fp1 = await av.getTimeline({ limit: 1, authors: ['did:plc:a'] })
assert(fp1.posts.length === 1 && fp1.posts[0].record.text === 'a-new' && fp1.cursor, 'filtered page 1 = 1 match + cursor')
const fp2 = await av.getTimeline({ limit: 1, authors: ['did:plc:a'], cursor: fp1.cursor })
assert(fp2.posts.length === 1 && fp2.posts[0].record.text === 'a-old', 'filtered page 2 skips the non-matching b post to the next a post')
console.log('\nAPPVIEW OK (Bare-native): timeline + author feed + reverse-chron pagination + per-viewer author scope')
await av.close()
try { await store.close() } catch {}