OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid / webui / test / rig.test.js

139 lines · javascript ref:
import test from 'brittle'
import EventEmitter from 'bare-events'
import { Rig } from '../lib/rig.js'
import { FakeUnit, waitFor, sleep } from './helpers.js'

// Discovery is injected: tests announce units directly.
class FakeDiscovery extends EventEmitter {
  constructor () { super(); this.available = true; this.reason = '' }
  start () {}
  stop () {}
  announce (fake) {
    this.emit('up', { name: fake.name, host: fake.name + '.local', addr: '127.0.0.1', port: fake.port, proto: 2 })
  }
}

class MemStore {
  constructor (data = {}) { this.data = data; this.saves = 0 }
  load () { return this.data }
  save (data) { this.data = JSON.parse(JSON.stringify(data)); this.saves++ }
}

function makeRig (store = new MemStore()) {
  const discovery = new FakeDiscovery()
  const rig = new Rig({ store, discovery, readbackMs: 100, unitOpts: { backoffStartMs: 100 } })
  rig.start()
  return { rig, discovery, store }
}

const pwms = (fake) => fake.lines.filter((l) => l.startsWith('pwm'))

test('starts disconnected; a discovered unit is listed, unticked, and untouched', async (t) => {
  const fake = await new FakeUnit('u1').listen()
  const { rig, discovery } = makeRig()
  discovery.announce(fake)
  const u = rig.snapshot().units.find((x) => x.id === 'u1')
  t.ok(u, 'listed')
  t.is(u.enabled, false, 'new units start unticked')
  t.is(rig.snapshot().connected, false)
  rig.setConnected(true)
  await sleep(300)
  t.is(fake.accepted, 0, 'unticked unit never contacted')
  rig.stop()
  await fake.close()
})

test('first connect adopts the unit\'s actual look instead of overwriting it', async (t) => {
  const fake = await new FakeUnit('u1').listen()
  fake.values = [65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // slot 1 blue, as Blender left it
  const { rig, discovery } = makeRig()
  discovery.announce(fake)
  rig.updateUnit('u1', { enabled: true })
  rig.setConnected(true)
  t.ok(await waitFor(() => rig.snapshot().units[0].slots !== null), 'slots adopted from state')
  const s = rig.snapshot().units[0].slots[0]
  t.alike([s.r, s.g, s.b, s.level], [0, 0, 1, 1], 'slot 1 reads as full blue')
  t.is(pwms(fake).length, 0, 'nothing pushed over the existing look')
  rig.stop()
  await fake.close()
})

test('editing a slot while connected drives the unit', async (t) => {
  const fake = await new FakeUnit('u1').listen()
  const { rig, discovery } = makeRig()
  discovery.announce(fake)
  rig.updateUnit('u1', { enabled: true, fadeMs: 0 })
  rig.setConnected(true)
  await waitFor(() => rig.snapshot().units[0].slots !== null)
  rig.setSlot('u1', 1, { r: 1, g: 0, b: 0, level: 1 })
  t.ok(await waitFor(() => pwms(fake).some((l) => l === 'pwm 0 0 0 0 0 30000 0 0 0 0 0 0')), 'slot 2 red at the 30000 cap')
  rig.stop()
  await fake.close()
})

test('disconnect closes every unit; edits while disconnected send nothing', async (t) => {
  const fake = await new FakeUnit('u1').listen()
  const { rig, discovery } = makeRig()
  discovery.announce(fake)
  rig.updateUnit('u1', { enabled: true })
  rig.setConnected(true)
  await waitFor(() => fake.conns.size === 1)
  rig.setConnected(false)
  t.ok(await waitFor(() => fake.conns.size === 0), 'closed')
  const before = fake.lines.length
  rig.setSlot('u1', 0, { r: 1, g: 1, b: 1, level: 1 })
  await sleep(200)
  t.is(fake.lines.length, before, 'nothing sent')
  rig.stop()
  await fake.close()
})

test('blackout while disconnected is one-shot and darkens the model', async (t) => {
  const fake = await new FakeUnit('u1').listen()
  const { rig, discovery } = makeRig()
  discovery.announce(fake)
  rig.updateUnit('u1', { slots: [{ r: 1, g: 1, b: 1, level: 1 }, null, null, null] })
  rig.blackout()
  t.ok(await waitFor(() => fake.lines.includes('stopfade')), 'stopfade sent')
  t.ok(await waitFor(() => fake.conns.size === 0), 'released')
  t.is(rig.snapshot().units[0].slots[0].level, 0, 'model shows dark')
  rig.stop()
  await fake.close()
})

test('settings survive a restart; connection state does not', async (t) => {
  const fake = await new FakeUnit('u1').listen()
  const store = new MemStore()
  const a = makeRig(store)
  a.discovery.announce(fake)
  a.rig.updateUnit('u1', { enabled: true, label: 'Studio', redMax: 20000 })
  a.rig.setConnected(true)
  a.rig.saveLook('warm')
  a.rig.flushSave()
  a.rig.stop()
  const b = makeRig(store)
  const u = b.rig.snapshot().units.find((x) => x.id === 'u1')
  t.is(u.label, 'Studio')
  t.is(u.redMax, 20000)
  t.is(u.enabled, true)
  t.is(b.rig.snapshot().connected, false, 'a restart comes back disconnected')
  t.alike(b.rig.snapshot().looks, ['warm'])
  b.rig.stop()
  await fake.close()
})

test('recalling a look fades every unit in it', async (t) => {
  const fake = await new FakeUnit('u1').listen()
  const { rig, discovery } = makeRig()
  discovery.announce(fake)
  rig.updateUnit('u1', { enabled: true, slots: [{ r: 0, g: 1, b: 0, level: 1 }, null, null, null] })
  rig.saveLook('green')
  rig.updateUnit('u1', { slots: [{ r: 0, g: 0, b: 0, level: 0 }, null, null, null] })
  rig.setConnected(true)
  await waitFor(() => fake.conns.size === 1)
  rig.recallLook('green', 2000)
  t.ok(await waitFor(() => fake.lines.some((l) => l.startsWith('pwm2 1000 2 0 65535 0'))), 'pwm2 over 2 s to green')
  rig.stop()
  await fake.close()
})