~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
116 lines · 3.3 KB Raw History
import { describe, expect, it } from 'vitest';
import { issueState, issueThreads } from '../src/issues.js';

describe('issueState and issueThreads', () => {
  const OWNER = 'did:plc:owner';
  /** @param {Partial<Record<string, unknown>>} 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);
  });
});