@rezless/vitest 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +333 -0
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# rezless
|
|
2
|
+
|
|
3
|
+
A **Vitest-style test framework for LSL** (Linden Scripting Language).
|
|
4
|
+
|
|
5
|
+
Tests run in milliseconds, in-process, with a virtual clock and mockable
|
|
6
|
+
`ll*` calls — no Second Life or OpenSim dependency.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
10
|
+
import { loadScript, vm } from '@rezless/vitest'
|
|
11
|
+
import type { Script } from '@rezless/vitest'
|
|
12
|
+
|
|
13
|
+
describe('greeter.lsl', () => {
|
|
14
|
+
let s: Script
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
s = await loadScript('./greeter.lsl')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('greets a toucher by name', () => {
|
|
20
|
+
s.start()
|
|
21
|
+
s.fire('touch_start', {
|
|
22
|
+
num_detected: 1,
|
|
23
|
+
detected: [{ key: 'aaa-...', name: 'Alice' }],
|
|
24
|
+
})
|
|
25
|
+
expect(s).toHaveSaid(0, 'Hello, Alice!')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('reminds after 60s of silence', () => {
|
|
29
|
+
s.start()
|
|
30
|
+
s.fire('touch_start', { num_detected: 1, detected: [{ key: 'k', name: 'Alice' }] })
|
|
31
|
+
expect(s).toBeInState('waiting')
|
|
32
|
+
s.advanceTime(60_000)
|
|
33
|
+
expect(s).toHaveSaid(0, 'Anyone there?')
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Why
|
|
39
|
+
|
|
40
|
+
Up to now, the only way to test an LSL script has been to rez it
|
|
41
|
+
in-world and exercise it by hand. rezless gives you a real type-checked
|
|
42
|
+
TypeScript / Vitest experience with deterministic time, mockable `ll*`
|
|
43
|
+
calls, and rich assertions over chat output, state transitions, HTTP
|
|
44
|
+
requests, and script globals.
|
|
45
|
+
|
|
46
|
+
## Packages
|
|
47
|
+
|
|
48
|
+
| Package | Purpose |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `@rezless/parser` | Hand-written recursive-descent LSL parser |
|
|
51
|
+
| `@rezless/vm` | Tree-walking interpreter, virtual clock, dispatch |
|
|
52
|
+
| `@rezless/vitest` | `loadScript()` + custom matchers |
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
### Use it from another project
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pnpm add -D @rezless/vitest vitest
|
|
60
|
+
# or: npm i -D @rezless/vitest vitest
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`vitest` is a peer dependency. Then in your test file:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { loadScript } from '@rezless/vitest'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Develop on rezless itself
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
pnpm install
|
|
73
|
+
pnpm gen # regenerate ll* stub tables from vendor/kwdb.xml
|
|
74
|
+
pnpm typecheck
|
|
75
|
+
pnpm build # per-package tsc emit
|
|
76
|
+
pnpm test
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
### `loadScript(input)` → `Promise<Script>`
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
await loadScript('./script.lsl')
|
|
85
|
+
await loadScript({
|
|
86
|
+
source: `default { state_entry() { llSay(0, "hi"); } }`,
|
|
87
|
+
filename: 'inline.lsl', // optional, used in diagnostics
|
|
88
|
+
randomSeed: 42, // optional, default 1
|
|
89
|
+
owner: 'aaaa-bbbb-...', // optional, NULL_KEY default
|
|
90
|
+
objectKey: 'cccc-dddd-...', // optional, derived from filename
|
|
91
|
+
objectName: 'StarterCube', // optional, "Object" default
|
|
92
|
+
scriptName: 'greeter', // optional, derived from filename
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Parse errors throw `LslParseError` with `file:line:col` location info.
|
|
97
|
+
|
|
98
|
+
### `Script` handle
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// Drive events
|
|
102
|
+
s.start() // run state_entry of default
|
|
103
|
+
s.fire(eventName, payload) // payload binds to handler params by kwdb-spec name
|
|
104
|
+
s.advanceTime(ms) // advance virtual clock; drains timer + queued events
|
|
105
|
+
s.deliverChat({ channel, name, key, message }) // fire `listen` on matching active listens
|
|
106
|
+
s.respondToHttp(key, { status, body }) // fire `http_response` for a captured llHTTPRequest
|
|
107
|
+
s.respondToLastHttp({ status, body }) // same, for the most recent
|
|
108
|
+
s.respondToDataserver(key, value) // fire `dataserver` for a captured llRequest*
|
|
109
|
+
s.respondToLastDataserver(value) // same, for the most recent
|
|
110
|
+
s.mock(name, impl) // override any ll* function (real or stubbed) per-test
|
|
111
|
+
s.reset() // re-init globals + return to default + state_entry
|
|
112
|
+
|
|
113
|
+
// Inspect
|
|
114
|
+
s.currentState // current LSL state name
|
|
115
|
+
s.now // virtual time in ms
|
|
116
|
+
s.timerInterval // configured llSetTimerEvent interval (sec, 0 = unset)
|
|
117
|
+
s.running // false while paused via llSetScriptState
|
|
118
|
+
s.global(name) // read a script global
|
|
119
|
+
s.setGlobal(name, value) // seed a script global
|
|
120
|
+
s.chat // [{ channel, text, type, to? }, ...]
|
|
121
|
+
s.calls // [{ name, args, returned }, ...]
|
|
122
|
+
s.callsOf(name) // filter call log by ll* name
|
|
123
|
+
s.httpRequests // [{ key, url, method, body, headers, mimetype, ... }]
|
|
124
|
+
s.dataserverRequests // [{ key, source, args, fulfilled }]
|
|
125
|
+
s.listens // [{ handle, channel, name, key, message, active }]
|
|
126
|
+
s.linkedMessages // [{ target, num, str, id }] sent by THIS script
|
|
127
|
+
s.clearLinkedMessages() // drop captured llMessageLinked entries
|
|
128
|
+
s.linksetData // ReadonlyMap<string, { value, password }> (shared LSD store)
|
|
129
|
+
s.seedLinksetData(entries) // pre-populate LSD without firing events
|
|
130
|
+
s.text // current llSetText: { text, color, alpha } or null
|
|
131
|
+
s.objectDesc // current llSetObjectDesc value
|
|
132
|
+
s.dead // true once llDie has run
|
|
133
|
+
|
|
134
|
+
// Multi-script context (auto-allocated for single-script tests)
|
|
135
|
+
s.host // the Prim instance hosting this script
|
|
136
|
+
s.linkset // the Linkset (shared clock, LSD, link_message bus)
|
|
137
|
+
s.scriptName // inventory name; backs llGetScriptName
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `loadLinkset(input)` → `Promise<LoadedLinkset>`
|
|
141
|
+
|
|
142
|
+
For tests that exercise more than one script — sibling scripts in the same
|
|
143
|
+
prim, or scripts spread across a multi-prim linkset — use `loadLinkset`.
|
|
144
|
+
Every script shares one virtual clock, one Linkset Data store, and one
|
|
145
|
+
`link_message` bus, so `llMessageLinked`, `linkset_data`, `llGetTime`,
|
|
146
|
+
`llResetOtherScript`, and `llSetScriptState` all behave as they do in-world.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { loadLinkset } from '@rezless/vitest'
|
|
150
|
+
|
|
151
|
+
const { linkset, prims, scripts } = await loadLinkset({
|
|
152
|
+
prims: [
|
|
153
|
+
{
|
|
154
|
+
name: 'Root',
|
|
155
|
+
scripts: [
|
|
156
|
+
{ source: './counter.lsl', name: 'counter' },
|
|
157
|
+
{ source: { source: peekerSource, filename: 'peeker.lsl' }, name: 'peeker' },
|
|
158
|
+
],
|
|
159
|
+
inventory: [
|
|
160
|
+
// notecards, textures, … addressable via llGetInventory* / llGetNotecardLine
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
{ name: 'Display', scripts: [{ source: './display.lsl', name: 'display' }] },
|
|
164
|
+
],
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
scripts['counter'].start()
|
|
168
|
+
scripts['display'].start()
|
|
169
|
+
scripts['counter'].fire('touch_start', { num_detected: 1 })
|
|
170
|
+
expect(scripts['display'].text?.text).toBe('count: 1')
|
|
171
|
+
|
|
172
|
+
// Linkset-wide helpers
|
|
173
|
+
linkset.advanceTime(1_000) // shared clock; every script's timers advance together
|
|
174
|
+
linkset.deliverChat({ channel: 0, name: 'Alice', key: 'k', message: 'hi' })
|
|
175
|
+
linkset.linkedMessages // every llMessageLinked across the linkset
|
|
176
|
+
linkset.clearLinkedMessages()
|
|
177
|
+
linkset.clock.now // current virtual time in ms
|
|
178
|
+
linkset.clock.pendingEvents() // snapshot of queued one-shot events:
|
|
179
|
+
// [{ at, target, event, payload }, ...]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`scripts` is a flat name → `Script` lookup keyed by inventory name; duplicate
|
|
183
|
+
names across prims throw to prevent silent overwrites. Each `Script` is the
|
|
184
|
+
same handle described above, so all matchers, mocks, and inspectors keep
|
|
185
|
+
working — they're just scoped to one script in a larger system.
|
|
186
|
+
|
|
187
|
+
`loadScript` continues to work for single-script tests; under the hood it
|
|
188
|
+
auto-allocates a 1-prim/1-script linkset, so existing tests are unaffected.
|
|
189
|
+
|
|
190
|
+
### Custom matchers
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
expect(s).toHaveSaid(0, 'hello')
|
|
194
|
+
expect(s).toBeInState('waiting')
|
|
195
|
+
expect(s).toHaveCalledFunction('llSetTimerEvent', 60.0)
|
|
196
|
+
expect(s).toHaveSentHTTP({ url: '...', method: 'POST', body: '...' })
|
|
197
|
+
expect(s).toHaveListened(7, { key: ownerKey })
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Implemented `ll*` functions
|
|
201
|
+
|
|
202
|
+
The real-impl set covers the most common use cases. Anything outside it
|
|
203
|
+
falls through to a typed stub that returns the kwdb-documented default
|
|
204
|
+
and records the call. Use `s.mock(name, fn)` to provide your own
|
|
205
|
+
behaviour for any function the script under test calls. Builtin
|
|
206
|
+
function delays (e.g. the 0.2s delay on `llSetLinkPrimitiveParams`) are
|
|
207
|
+
honored centrally in the dispatcher.
|
|
208
|
+
|
|
209
|
+
* **chat**: `llSay`, `llShout`, `llWhisper`, `llOwnerSay`
|
|
210
|
+
* **listen**: `llListen`, `llListenRemove`, `llListenControl`
|
|
211
|
+
* **time**: `llSetTimerEvent`, `llSleep`, `llGetTime`, `llGetAndResetTime`, `llResetTime`
|
|
212
|
+
* **HTTP**: `llHTTPRequest`, `llHTTPResponse`
|
|
213
|
+
* **linked**: `llMessageLinked` (full `LINK_THIS` / `LINK_SET` / `LINK_ALL_OTHERS` / `LINK_ALL_CHILDREN` / `LINK_ROOT` / specific-link routing across the linkset, with correct `sender_num`)
|
|
214
|
+
* **link info**: `llGetLinkNumber`, `llGetNumberOfPrims`, `llGetLinkKey`, `llGetLinkName`
|
|
215
|
+
* **dataserver**: `llRequestAgentData`, `llRequestInventoryData`, `llRequestSimulatorData`, `llRequestUsername`, `llRequestDisplayName`
|
|
216
|
+
* **detected**: `llDetectedKey`/`Name`/`Owner`/`Group`/`Pos`/`Rot`/`Vel`/`Type`/`LinkNumber`/`Grab`/`TouchPos`
|
|
217
|
+
* **math**: `llAbs`, `llFabs`, `llRound` (banker's), `llCeil`, `llFloor`, `llPow`, `llSqrt`, `llSin`/`Cos`/`Tan`/`Asin`/`Acos`/`Atan2`, `llLog`/`Log10`, seeded `llFrand`, `llVecMag`/`Norm`/`Dist`, `llRot2Euler`/`Euler2Rot`
|
|
218
|
+
* **string**: `llStringLength`, `llSubStringIndex`, `llGetSubString`, `llDeleteSubString`, `llInsertString`, `llStringTrim`, `llToLower`/`Upper`, `llReplaceSubString`, `llEscapeURL`/`UnescapeURL`
|
|
219
|
+
* **list**: `llGetListLength`, `llList2Integer`/`Float`/`String`/`Key`/`Vector`/`Rot`, `llList2List`, `llDeleteSubList`, `llListInsertList`, `llListReplaceList`, `llListFindList`, `llDumpList2String`, `llCSV2List`, `llParseString2List`
|
|
220
|
+
* **json**: `llJson2List`, `llJsonGetValue`, `llJsonSetValue`, `llJsonValueType`, `llList2Json` — full LSL JSON semantics including the FDDx-range type-tag sentinels (`JSON_OBJECT`/`ARRAY`/`NUMBER`/`STRING`/`NULL`/`TRUE`/`FALSE`/`INVALID`/`DELETE`), `JSON_APPEND`, the empty-value short-circuit (`{"k":,}` → `JSON_NULL`) on `Get`/`Type`, duplicate-key collapse on `Set`, force-fit auto-creation of nested objects/arrays through deep specifier paths, and the kept-as-is rule for nested JSON-shaped strings in `llList2Json`.
|
|
221
|
+
* **identity**: `llGetOwner`, `llGetCreator`, `llGetKey`, `llGetObjectName`, `llSetObjectName`, `llGetScriptName`
|
|
222
|
+
* **hash**: `llMD5String`, `llSHA1String`, `llSHA256String`, `llHMAC`
|
|
223
|
+
* **base64**: `llStringToBase64`/`Base64ToString`, `llIntegerToBase64`/`Base64ToInteger`
|
|
224
|
+
* **object**: `llSetText`, `llSetObjectDesc`, `llGetObjectDesc`, `llDie` (kills every script in the linkset), `llResetScript`
|
|
225
|
+
* **inventory**: `llGetInventoryNumber`/`Name`/`Type`/`Key`/`Creator`/`Desc`/`AcquireTime`/`PermMask` against the host prim's inventory; notecards via `llGetNotecardLine` / `llGetNumberOfNotecardLines` (correct EOF / NAK semantics, ISO 8601 UTC acquire-time strings)
|
|
226
|
+
* **script control**: `llResetOtherScript`, `llSetScriptState`, `llGetScriptState` — pausing parks events for replay on resume, and the timer cadence realigns on resume so paused scripts don't get a flood of catch-up timer events
|
|
227
|
+
* **linkset data**: `llLinksetDataWrite`/`Read`/`Delete`/`WriteProtected`/`ReadProtected`/`DeleteProtected`/`DeleteFound`, `llLinksetDataReset`, `llLinksetDataAvailable`, `llLinksetDataCountKeys`/`CountFound`, `llLinksetDataListKeys`/`FindKeys` — fires the `linkset_data` event on every script in the linkset with `LINKSETDATA_UPDATE` / `DELETE` / `RESET` / `MULTIDELETE`. The store survives `llResetScript`. Inspect via `s.linksetData` (a `ReadonlyMap<string, { value, password }>`); pre-populate via `s.seedLinksetData([['k', { value: 'v' }]])`.
|
|
228
|
+
* **primitive params**: `llSetPrimitiveParams`, `llSetLinkPrimitiveParams`, `llSetLinkPrimitiveParamsFast`, `llGetPrimitiveParams`, `llGetLinkPrimitiveParams` — full `PRIM_*` rule surface (position / rotation / size / colour / alpha / texture / glow / fullbright / material / physics / flexible / point light / projector / sculpt / GLTF PBR / sit target / click action / text / name / desc / omega / damage / temp-on-rez / cast shadows / …). Honors `PRIM_LINK_TARGET` mid-rule redirects, `ALL_SIDES` expansion, multi-prim get concatenation, unknown-rule termination on get, and the 0.2 s delay on the slow link variant.
|
|
229
|
+
* **prim accessors** (alternative single-rule shortcuts that read/write the same backing store): `llSetPos`/`llGetPos`/`llGetLocalPos`/`llGetRootPosition`, `llSetRot`/`llSetLocalRot`/`llGetRot`/`llGetLocalRot`/`llGetRootRotation`, `llSetScale`/`llGetScale`/`llScaleByFactor`/`llGetMaxScaleFactor`/`llGetMinScaleFactor`, `llSetColor`/`llGetColor`/`llSetAlpha`/`llGetAlpha`, `llSetTexture`/`llGetTexture`/`llGetTextureOffset`/`llGetTextureScale`/`llGetTextureRot`/`llScaleTexture`/`llOffsetTexture`/`llRotateTexture`, `llSetLinkColor`/`llSetLinkAlpha`/`llSetLinkTexture`, `llSetTextureAnim`/`llSetLinkTextureAnim`/`llSetLinkTextureAnimOverrideMe`, `llSetRenderMaterial`/`llGetRenderMaterial`, `llSetStatus`/`llGetStatus`/`llSetLinkStatus`, `llSetClickAction`, `llTargetOmega`, `llSetPhysicsMaterial`/`llGetPhysicsMaterial`, `llPassCollisions`/`llPassTouches`, `llSitTarget`/`llLinkSitTarget`/`llAvatarOnSitTarget`/`llAvatarOnLinkSitTarget`/`llSetLinkSitFlags`/`llGetLinkSitFlags`.
|
|
230
|
+
|
|
231
|
+
The full LSL constant surface (every `PRIM_*`, `STATUS_*`, `LINK_*`,
|
|
232
|
+
`INVENTORY_*`, `MASK_*`, `LINKSETDATA_*`, …) is re-exported from
|
|
233
|
+
`@rezless/vitest`, so tests can compare numeric returns against named
|
|
234
|
+
constants instead of magic numbers.
|
|
235
|
+
|
|
236
|
+
## Coverage
|
|
237
|
+
|
|
238
|
+
`@rezless/vitest` ships an LSL coverage collector that tracks four kinds of
|
|
239
|
+
coverage per script: **statement**, **branch** (if / while / for / do-while
|
|
240
|
+
arms), **function** (user functions and event handlers), and **state**
|
|
241
|
+
("did we ever enter `state idle`?"). Off by default; opt in once in your
|
|
242
|
+
`vitest.config.ts`:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { defineConfig } from 'vitest/config'
|
|
246
|
+
import { LslCoverageReporter } from '@rezless/vitest/reporter'
|
|
247
|
+
|
|
248
|
+
export default defineConfig({
|
|
249
|
+
test: {
|
|
250
|
+
reporters: ['default', new LslCoverageReporter()],
|
|
251
|
+
coverage: {
|
|
252
|
+
// your @vitest/coverage-v8 config (provider, include, exclude, ...)
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The reporter is dormant during plain `vitest run`. When you invoke
|
|
259
|
+
`vitest run --coverage`, both pipelines fire:
|
|
260
|
+
|
|
261
|
+
- JS coverage from `@vitest/coverage-v8` lands at `coverage/`.
|
|
262
|
+
- LSL coverage lands at `coverage/lsl/`:
|
|
263
|
+
- `lcov.info` — opens in VS Code Coverage Gutters / Codecov / Coveralls
|
|
264
|
+
- `coverage-final.json` — Istanbul-shaped, mergeable with JS coverage
|
|
265
|
+
- `html/index.html` — browseable per-file source view with line annotations
|
|
266
|
+
- per-file pass/total table on stdout
|
|
267
|
+
|
|
268
|
+
Two alternative activation paths:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
// Per-test, programmatic — useful for in-test assertions.
|
|
272
|
+
const s = await loadScript({ source, filename, coverage: true })
|
|
273
|
+
s.start()
|
|
274
|
+
expect(s.coverage!.functions.find((f) => f.name === 'bump')!.hits).toBeGreaterThan(0)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
# Without the reporter installed — env var collects, CLI renders.
|
|
279
|
+
LSL_COVERAGE=1 pnpm test
|
|
280
|
+
pnpm exec rezless-coverage
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
By default, reports for synthetic filenames (`<inline>`, names without a
|
|
284
|
+
path separator, paths that don't exist on disk) are omitted — they're
|
|
285
|
+
almost always throwaway test fixtures. Pass `--include-fixtures` to the
|
|
286
|
+
CLI, or `{ includeFixtures: true }` to the reporter, to keep them.
|
|
287
|
+
|
|
288
|
+
`examples/coverage/` has a runnable demo.
|
|
289
|
+
|
|
290
|
+
## Examples
|
|
291
|
+
|
|
292
|
+
`examples/` ships working scripts with tests:
|
|
293
|
+
|
|
294
|
+
* **hello** — minimal `state_entry { llSay(...) }` + matchers.
|
|
295
|
+
* **greeter** — touch + name greeting + state transition + reminder timer.
|
|
296
|
+
* **remote** — owner-only listen on a custom channel + command dispatch.
|
|
297
|
+
* **fetcher** — `llHTTPRequest` + `http_response` + status handling.
|
|
298
|
+
* **nametag** — `llRequestAgentData` + `dataserver` + floating text.
|
|
299
|
+
* **mocking** — patterns for `script.mock(...)`: pinning a real built-in
|
|
300
|
+
to a deterministic value, providing custom logic for an unimplemented
|
|
301
|
+
function, observing call args via the call log, and stateful mocks
|
|
302
|
+
that aggregate across multiple events.
|
|
303
|
+
* **scoreboard** — Linkset Data: per-name counters, regex key lookup
|
|
304
|
+
via `llLinksetDataFindKeys`, `linkset_data` event handling, and
|
|
305
|
+
state that survives `llResetScript`.
|
|
306
|
+
* **multi-script** — two scripts in different prims of the same linkset:
|
|
307
|
+
a counter in the root prim drives a display in a child prim via
|
|
308
|
+
`llMessageLinked`, and a third script writing the same LSD key
|
|
309
|
+
refreshes the display via the broadcast `linkset_data` event.
|
|
310
|
+
Wired up with `loadLinkset({ prims: [...] })`.
|
|
311
|
+
* **coverage** — opt-in coverage tracking: a small voter script
|
|
312
|
+
exercising statement / branch / function / state coverage with
|
|
313
|
+
in-test assertions on `script.coverage`.
|
|
314
|
+
* **json** — JSON config blob with nested paths: `set theme.color red`
|
|
315
|
+
/ `get theme.color` / `keys` commands, persisted to Linkset Data so
|
|
316
|
+
the document survives `llResetScript`. Exercises all five JSON
|
|
317
|
+
builtins, nested-object force-fit on `llJsonSetValue`, and the
|
|
318
|
+
bare-word/sentinel round-trip on `llJsonGetValue`.
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
The rezless project is licensed under the **Apache License, Version 2.0** —
|
|
323
|
+
see [`LICENSE`](LICENSE).
|
|
324
|
+
|
|
325
|
+
LSL function/event/constant signatures come from
|
|
326
|
+
[Sei-Lisa/kwdb](https://github.com/Sei-Lisa/kwdb), vendored at
|
|
327
|
+
`vendor/kwdb.xml` and licensed under **LGPL-3.0-or-later**. Files
|
|
328
|
+
mechanically derived from it (`packages/vm/src/generated/*.ts`) remain
|
|
329
|
+
under that licence; see [`NOTICE`](NOTICE) for the full attribution.
|
|
330
|
+
|
|
331
|
+
Apache 2.0 is one-way compatible with LGPL-3 (per the FSF), so a
|
|
332
|
+
combined work that includes both can be redistributed under LGPL-3
|
|
333
|
+
terms — which is what consumers receive when they use rezless.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rezless/vitest",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Vitest-style DSL for testing Linden Scripting Language (LSL) scripts.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "i-doll",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@rezless/parser": "0.1.
|
|
56
|
-
"@rezless/vm": "0.1.
|
|
55
|
+
"@rezless/parser": "0.1.2",
|
|
56
|
+
"@rezless/vm": "0.1.2"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"vitest": "^4.1.5"
|