OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-social / hyper-board / test / board.test.js

329 lines · javascript ref:
import test from 'brittle'
import { Board } from '../lib/board.js'
import { tmpdir } from 'os'
import { join } from 'path'
import { randomBytes } from 'crypto'

function tmpStorage () {
  return join(tmpdir(), 'hyper-board-test-' + randomBytes(8).toString('hex'))
}

test('Board - ready creates default lanes', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  const lanes = await board.listLanes()
  t.is(lanes.length, 4)
  t.is(lanes[0].name, 'backlog')
  t.is(lanes[1].name, 'todo')
  t.is(lanes[2].name, 'in-progress')
  t.is(lanes[3].name, 'done')

  await board.close()
})

test('Board - create and get card', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  const card = await board.createCard({
    title: 'Fix bug',
    description: 'Critical production bug',
    lane: 'todo',
    priority: 'high',
    tags: ['bug', 'production']
  })

  t.ok(card.id)
  t.is(card.title, 'Fix bug')
  t.is(card.lane, 'todo')
  t.is(card.priority, 'high')
  t.alike(card.tags, ['bug', 'production'])
  t.ok(card.created > 0)

  const fetched = await board.getCard(card.id)
  t.is(fetched.title, 'Fix bug')

  await board.close()
})

test('Board - update card', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  const card = await board.createCard({ title: 'Task A' })
  const updated = await board.updateCard(card.id, {
    title: 'Task A (updated)',
    description: 'Now with details',
    assignee: 'fleeky'
  })

  t.is(updated.title, 'Task A (updated)')
  t.is(updated.assignee, 'fleeky')
  t.is(updated.id, card.id)
  t.is(updated.created, card.created)
  t.ok(updated.updated >= card.updated)

  await board.close()
})

test('Board - delete card', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  const card = await board.createCard({ title: 'Delete me' })
  t.ok(await board.getCard(card.id))

  const ok = await board.deleteCard(card.id)
  t.ok(ok)
  t.is(await board.getCard(card.id), null)

  // Deleting non-existent returns false
  const nope = await board.deleteCard('nonexistent')
  t.is(nope, false)

  await board.close()
})

test('Board - move card between lanes', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  const card = await board.createCard({ title: 'Move me', lane: 'backlog' })
  t.is(card.lane, 'backlog')

  const moved = await board.moveCard(card.id, 'in-progress')
  t.is(moved.lane, 'in-progress')

  const fetched = await board.getCard(card.id)
  t.is(fetched.lane, 'in-progress')

  await board.close()
})

test('Board - list cards with lane filter', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'A', lane: 'todo' })
  await board.createCard({ title: 'B', lane: 'todo' })
  await board.createCard({ title: 'C', lane: 'done' })

  const todo = await board.listCards({ lane: 'todo' })
  t.is(todo.length, 2)

  const done = await board.listCards({ lane: 'done' })
  t.is(done.length, 1)

  const all = await board.listCards()
  t.is(all.length, 3)

  await board.close()
})

test('Board - list cards with assignee filter', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'A', assignee: 'alice' })
  await board.createCard({ title: 'B', assignee: 'bob' })
  await board.createCard({ title: 'C', assignee: 'alice' })

  const alice = await board.listCards({ assignee: 'alice' })
  t.is(alice.length, 2)

  await board.close()
})

test('Board - list cards with tag filter', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'A', tags: ['bug'] })
  await board.createCard({ title: 'B', tags: ['feature'] })
  await board.createCard({ title: 'C', tags: ['bug', 'urgent'] })

  const bugs = await board.listCards({ tag: 'bug' })
  t.is(bugs.length, 2)

  await board.close()
})

test('Board - priority sorting', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'Normal', priority: 'normal' })
  await board.createCard({ title: 'Urgent', priority: 'urgent' })
  await board.createCard({ title: 'Low', priority: 'low' })
  await board.createCard({ title: 'High', priority: 'high' })

  const cards = await board.listCards()
  t.is(cards[0].title, 'Urgent')
  t.is(cards[1].title, 'High')
  t.is(cards[2].title, 'Normal')
  t.is(cards[3].title, 'Low')

  await board.close()
})

test('Board - add and remove lane', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.addLane('review', 2)
  const lanes = await board.listLanes()
  t.ok(lanes.find(l => l.name === 'review'))

  await board.removeLane('review')
  const lanes2 = await board.listLanes()
  t.absent(lanes2.find(l => l.name === 'review'))

  await board.close()
})

test('Board - cannot remove lane with cards', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'Blocker', lane: 'todo' })

  try {
    await board.removeLane('todo')
    t.fail('Should have thrown')
  } catch (err) {
    t.ok(err.message.includes('Cannot remove'))
    t.ok(err.message.includes('1 cards'))
  }

  await board.close()
})

test('Board - duplicate lane name rejected', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  try {
    await board.addLane('todo')
    t.fail('Should have thrown')
  } catch (err) {
    t.ok(err.message.includes('already exists'))
  }

  await board.close()
})

test('Board - search by title', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'Fix authentication bug' })
  await board.createCard({ title: 'Add new feature' })
  await board.createCard({ title: 'Bug in login page' })

  const results = await board.search('bug')
  t.is(results.length, 2)

  await board.close()
})

test('Board - search by description', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'Task', description: 'needs performance optimization' })
  await board.createCard({ title: 'Other' })

  const results = await board.search('performance')
  t.is(results.length, 1)
  t.is(results[0].title, 'Task')

  await board.close()
})

test('Board - search by tag', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'A', tags: ['frontend'] })
  await board.createCard({ title: 'B', tags: ['backend'] })

  const results = await board.search('frontend')
  t.is(results.length, 1)

  await board.close()
})

test('Board - stats', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'A', lane: 'todo' })
  await board.createCard({ title: 'B', lane: 'todo' })
  await board.createCard({ title: 'C', lane: 'done' })

  const stats = await board.stats()
  t.is(stats.totalCards, 3)
  t.is(stats.totalLanes, 4)
  t.is(stats.byLane.todo, 2)
  t.is(stats.byLane.done, 1)
  t.is(stats.byLane.backlog, 0)

  await board.close()
})

test('Board - view returns lanes with cards', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  await board.createCard({ title: 'A', lane: 'todo' })
  await board.createCard({ title: 'B', lane: 'done' })

  const view = await board.view()
  t.is(view.length, 4) // all 4 default lanes

  const todoView = view.find(l => l.name === 'todo')
  t.is(todoView.cards.length, 1)
  t.is(todoView.cards[0].title, 'A')

  const doneView = view.find(l => l.name === 'done')
  t.is(doneView.cards.length, 1)

  const backlogView = view.find(l => l.name === 'backlog')
  t.is(backlogView.cards.length, 0)

  await board.close()
})

test('Board - persistence', async (t) => {
  const storage = tmpStorage()

  // Create and populate
  const b1 = new Board(storage)
  await b1.ready()
  await b1.createCard({ title: 'Persistent', lane: 'todo' })
  await b1.close()

  // Reopen
  const b2 = new Board(storage)
  await b2.ready()
  const cards = await b2.listCards()
  t.is(cards.length, 1)
  t.is(cards[0].title, 'Persistent')
  await b2.close()
})

test('Board - update nonexistent card throws', async (t) => {
  const board = new Board(tmpStorage())
  await board.ready()

  try {
    await board.updateCard('nonexistent', { title: 'X' })
    t.fail('Should have thrown')
  } catch (err) {
    t.ok(err.message.includes('not found'))
  }

  await board.close()
})