@gwigz/slua

testing

Mock utilities for unit testing SLua modules

Work in progress

This module is under active development and has not been battle-tested in production. The API may change between releases.

The testing module mocks ll, LLEvents, LLTimers, coroutine, Vector, Quaternion, UUID, and every SLua constant, so you can unit-test modules in Bun, Vitest, or Jest without a Second Life runtime.

This module is for test environments only. It does not compile to Lua, so never import it from a production SLua script.

Usage

import { describe, it, expect, beforeEach, afterEach } from "bun:test"
import { setup, teardown, notecard, emit } from "./modules/testing"
import { loadConfig } from "./modules/config"

beforeEach(() => setup())
afterEach(() => teardown())

it("loads config from notecard", () => {
  notecard("settings.yml", ["CHANNEL: -123", "MESSAGE: Hello"])

  const config = { CHANNEL: 0, MESSAGE: "" }

  loadConfig("settings.yml", { config }, () => {
    expect(config.CHANNEL).toBe(-123)
    expect(config.MESSAGE).toBe("Hello")
  })
})

API

Prop

Type

Mock behaviour

ll

The ll namespace is a Proxy, so any function it does not mock is a no-op. Functions that return request IDs (RequestAgentData, HTTPRequest, etc.) return auto-incrementing UUIDs.

Each setup() installs a fresh copy of the mock, so tests can freely override functions (ll.RequestAgentData = () => "fixed-id") without leaking into later tests.

Chat functions (Say, Whisper, Shout, OwnerSay, RegionSay, RegionSayTo) are recorded and inspectable via chatMessages():

const avatarKey = "fc4d0b7a-6a5c-4b6b-8143-c724f820c12e"

ll.Say(0, "hello")
ll.RegionSayTo(avatarKey, 0, "psst")

expect(chatMessages()).toEqual([
  { func: "Say", channel: 0, text: "hello" },
  { func: "RegionSayTo", channel: 0, text: "psst", target: avatarKey },
])

The log is cleared on every setup() and teardown().

Vector, Quaternion, and UUID

Constructible mock classes with the statics and instance math (add, mul, neg, etc.) from the SLua API (Vector.zero, Vector.normalize, Quaternion.identity, UUID.create, and so on), also installed under their lowercase runtime names (vector, quaternion, uuid). Quaternion math is intentionally simple (slerp is implemented as nlerp), so precise rotation math should not be asserted against these mocks.

LLEvents

Full on/off/once implementation backed by an internal handler map. Use emit() to trigger events in tests.

LLTimers

every() and once() register callbacks into a set. Use tick() to fire them.

coroutine

A fresh mock coroutine namespace is created per setup() call. Provides create, resume, running, yield, status, isyieldable, wrap, and close. Use setCoroutineYieldValue() to control what yield() returns.

On this page