@solidrt/test 0.0.72 → 0.0.74

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.
Files changed (3) hide show
  1. package/AGENTS.md +14 -0
  2. package/package.json +3 -3
  3. package/src/index.ts +65 -1
package/AGENTS.md CHANGED
@@ -59,6 +59,20 @@ agents/testing.md.
59
59
  app registered with `registerDebug` and returns its value: the way to
60
60
  put a loaded app into a state its UI reaches slowly. A `gamepad` event
61
61
  in `app.input` seats and drives a synthetic pad, as `send_input` does.
62
+ - `await app.camera({ width, height, data })` makes an RGBA8 image (a
63
+ `decodeImage` result, or pixels the test builds) what the camera sees:
64
+ every camera the app opens shows it, with no device and no prompt, and
65
+ an open one shows it from the next frame. Call it before the app opens
66
+ its camera; call it again to show another image. A QR code in it is
67
+ decoded as from a real camera, so `settle()` after the open and read
68
+ the app's barcode state.
69
+ - `await app.microphone({ sampleRate, samples })` makes a mono float clip
70
+ what the microphone hears: every microphone the app opens at that rate
71
+ holds it from the open, with no device and no prompt (`settle()` after
72
+ the mount, the open resolves through the engine), and an open one hears it now
73
+ (a runtime module reading the session included). Call it before the
74
+ app opens its microphone; call it again to say more. An open at another
75
+ rate fails naming both rates.
62
76
  - Past a node's record: `locator.outline()` is the subtree as an outline
63
77
  (kind, label, text, box per line, no ids) to pin a layout or print it;
64
78
  `locator.pixel(x, y)` is `[r, g, b, a]` at a point of the node and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/test",
3
- "version": "0.0.72",
3
+ "version": "0.0.74",
4
4
  "license": "MIT",
5
5
  "funding": "https://github.com/sponsors/wellawaretech",
6
6
  "author": "Antoine van Wel",
@@ -14,9 +14,9 @@
14
14
  "AGENTS.md"
15
15
  ],
16
16
  "devDependencies": {
17
- "@solidrt/flux-types": "0.0.72"
17
+ "@solidrt/flux-types": "0.0.74"
18
18
  },
19
19
  "peerDependencies": {
20
- "@solidrt/core": "0.0.72"
20
+ "@solidrt/core": "0.0.74"
21
21
  }
22
22
  }
package/src/index.ts CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { test as register, expect } from "flux:test"
18
18
  import * as gui from "flux:test/gui"
19
- import { capture, debug, frame as stepFrame, frameRate, inputPlan, inputStep, link, painted as paintFrame, setFrameRate, settle, time, windowReady } from "sol:test"
19
+ import { camera as installCamera, capture, debug, frame as stepFrame, frameRate, inputPlan, inputStep, link, microphone as installMicrophone, painted as paintFrame, setFrameRate, settle, time, windowReady } from "sol:test"
20
20
  import { createElement, insert, render } from "@solidrt/core"
21
21
 
22
22
  export { expect }
@@ -158,6 +158,20 @@ export interface PointerOptions {
158
158
  meta?: boolean
159
159
  }
160
160
 
161
+ /** An RGBA8 image, tightly packed rows top to bottom: the shape `decodeImage` returns (and the web's `ImageData`). */
162
+ export interface CameraImage {
163
+ width: number
164
+ height: number
165
+ data: Uint8Array
166
+ }
167
+
168
+ /** A mono clip: float samples at a sample rate, the shape `read()` of a microphone session delivers. */
169
+ export interface MicrophoneClip {
170
+ /** Hz; the rate the app opens its microphone at. */
171
+ sampleRate: number
172
+ samples: Float32Array
173
+ }
174
+
161
175
  /** The app under test, as a test's function receives it. */
162
176
  export interface TestApp {
163
177
  /**
@@ -191,6 +205,40 @@ export interface TestApp {
191
205
  * `settle()` after it.
192
206
  */
193
207
  link(link: string): Promise<void>
208
+ /**
209
+ * Makes `image` what the camera sees: every camera the app opens from
210
+ * now on shows it (a `createCamera` or `open` resolves at once, no device
211
+ * and no permission prompt), and a camera already open shows it from the
212
+ * next frame. Call it before `mount` or `load` so the app's own open finds
213
+ * it; call it again to show the app another image. A QR code in the
214
+ * image is decoded like one in front of a real camera: `settle()` after
215
+ * the open and the app's barcode handler has run. Pass a `decodeImage`
216
+ * result, or an image the test builds.
217
+ *
218
+ * @example
219
+ * await app.camera(decodeImage(png))
220
+ * let cam = createCamera({ scan: ["qr"] })
221
+ * await app.settle()
222
+ * expect(cam.barcode()?.data).toBe("solidrt")
223
+ */
224
+ camera(image: CameraImage): Promise<void>
225
+ /**
226
+ * Makes `clip` what the microphone hears: every microphone the app opens
227
+ * at the clip's rate from now on holds it from the open (a
228
+ * `createMicrophone` or `open` resolves at once, no device, no prompt), and a
229
+ * microphone already open hears it now, so a module reading the session
230
+ * on its own thread gets it too. Call it before `mount` or `load` so the
231
+ * app's own open finds a microphone; call it again to say something else
232
+ * to an open one. An open at another rate than the clip's fails naming
233
+ * both rates. Results a module or recognizer produces land through the
234
+ * engine: `settle()` after the install.
235
+ *
236
+ * @example
237
+ * await app.microphone({ sampleRate: 16000, samples: tone })
238
+ * let mic = createMicrophone()
239
+ * expect(mic.read()).toEqual(tone)
240
+ */
241
+ microphone(clip: MicrophoneClip): Promise<void>
194
242
  /**
195
243
  * Calls a debug command the app registered (`registerDebug` of
196
244
  * `sol:dev`), runs a frame, and returns what the command returned. The
@@ -497,6 +545,22 @@ let app: TestApp = {
497
545
  if (!link(target)) throw new Error(`link: the app does not listen for links (no onLink handler), so ${JSON.stringify(target)} went nowhere`)
498
546
  await app.frame()
499
547
  },
548
+ async camera(image) {
549
+ let ok = image !== null && typeof image === "object" && image.data instanceof Uint8Array
550
+ if (!ok) throw new TypeError("camera: pass { width, height, data }, with data a Uint8Array of RGBA8 (what decodeImage returns)")
551
+ let { width, height, data } = image
552
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) throw new TypeError(`camera: the size must be positive integers, got ${width} x ${height}`)
553
+ if (data.length !== width * height * RGBA) throw new TypeError(`camera: ${width} x ${height} RGBA8 is ${width * height * RGBA} bytes, got ${data.length}`)
554
+ installCamera(width, height, data)
555
+ await app.frame()
556
+ },
557
+ async microphone(clip) {
558
+ let ok = clip !== null && typeof clip === "object" && clip.samples instanceof Float32Array
559
+ if (!ok) throw new TypeError("microphone: pass { sampleRate, samples }, with samples a Float32Array of mono samples")
560
+ let { sampleRate, samples } = clip
561
+ if (!Number.isInteger(sampleRate) || sampleRate <= 0) throw new TypeError(`microphone: the sample rate must be a positive integer, got ${sampleRate}`)
562
+ installMicrophone(sampleRate, samples)
563
+ },
500
564
  async debug(name, args) {
501
565
  let result = debug(name, args === undefined ? null : JSON.stringify(args))
502
566
  await app.frame()