import { describe, expect, it } from 'vitest'; import { issueState, issueThreads } from '../src/issues.js'; describe('issueState and issueThreads', () => { const OWNER = 'did:plc:owner'; /** @param {Partial>} value */ const entry = (value) => ({ by: { did: 'did:plc:filer', handle: 'filer' }, ...value, }); it('reads the state from the owner and nobody else', () => { const thread = [ entry({ uri: 'at://a', createdAt: '2026-08-01T00:00:00Z' }), entry({ uri: 'at://b', createdAt: '2026-08-02T00:00:00Z', disposition: 'resolved', }), ]; // The filer marking their own issue resolved is a comment, not a state. expect(issueState(thread, OWNER).state).toBe('open'); thread.push( entry({ by: { did: OWNER, handle: 'owner' }, uri: 'at://c', createdAt: '2026-08-03T00:00:00Z', disposition: 'resolved', }), ); expect(issueState(thread, OWNER)).toMatchObject({ state: 'resolved' }); }); it('reads the latest word, so reopened takes back a decline', () => { const owner = { did: OWNER, handle: 'owner' }; const thread = [ entry({ uri: 'at://a', createdAt: '2026-08-01T00:00:00Z' }), entry({ by: owner, uri: 'at://b', createdAt: '2026-08-02T00:00:00Z', disposition: 'declined', }), ]; expect(issueState(thread, OWNER)).toMatchObject({ state: 'declined' }); thread.push( entry({ by: owner, uri: 'at://c', createdAt: '2026-08-03T00:00:00Z', disposition: 'reopened', }), ); expect(issueState(thread, OWNER)).toMatchObject({ state: 'open', said: { uri: 'at://c', disposition: 'reopened' }, }); }); it('leaves the state alone when anybody but the owner reopens', () => { const thread = [ entry({ uri: 'at://a', createdAt: '2026-08-01T00:00:00Z' }), entry({ by: { did: OWNER, handle: 'owner' }, uri: 'at://b', createdAt: '2026-08-02T00:00:00Z', disposition: 'declined', }), entry({ uri: 'at://c', createdAt: '2026-08-03T00:00:00Z', disposition: 'reopened', }), ]; expect(issueState(thread, OWNER)).toMatchObject({ state: 'declined' }); }); it('reads a word off Object.prototype as no disposition at all', () => { const thread = [ entry({ by: { did: OWNER, handle: 'owner' }, uri: 'at://a', createdAt: '2026-08-01T00:00:00Z', disposition: 'constructor', }), ]; expect(issueState(thread, OWNER)).toEqual({ state: 'open' }); }); it('groups replies with their opener, newest activity first', () => { const threads = issueThreads( [ entry({ uri: 'at://old', title: 'old', createdAt: '2026-08-01T00:00:00Z', }), entry({ uri: 'at://young', title: 'young', createdAt: '2026-08-02T00:00:00Z', }), entry({ uri: 'at://answer', createdAt: '2026-08-03T00:00:00Z', replyTo: { uri: 'at://old' }, }), ], OWNER, ); expect(threads.map((t) => t.root.title)).toEqual(['old', 'young']); expect(threads[0].thread).toHaveLength(2); }); });