11import { describe , expect , test , vi } from "vitest" ;
2+ import { readFile } from "node:fs/promises" ;
3+ import { runInNewContext } from "node:vm" ;
24import { createTmuApp } from "../src/app" ;
35import {
46 BackgroundSoundsError ,
@@ -7,6 +9,19 @@ import {
79} from "../src/background-sounds" ;
810
911describe ( "Background Sounds runtime boundary" , ( ) => {
12+ test . each ( [
13+ "comfortSoundsAvailable" , "comfortSoundsEnabled" , "setComfortSoundsEnabled:" ,
14+ "selectedComfortSound" , "setSelectedComfortSound:" , "relativeVolume" , "setRelativeVolume:" ,
15+ ] ) ( "bundled helper rejects a missing %s selector" , async ( missingSelector ) => {
16+ const response = await runBundledHelper ( { missingSelector } ) ;
17+ expect ( response ) . toMatchObject ( { protocolVersion : 1 , ok : false , code : "contract-mismatch" } ) ;
18+ } ) ;
19+
20+ test ( "bundled helper rejects macOS when Background Sounds is unavailable" , async ( ) => {
21+ const response = await runBundledHelper ( { available : false } ) ;
22+ expect ( response ) . toMatchObject ( { protocolVersion : 1 , ok : false , code : "unavailable" } ) ;
23+ } ) ;
24+
1025 test . each ( [
1126 [ "linux" , "25.5.0" , false ] ,
1227 [ "darwin" , "24.9.0" , false ] ,
@@ -104,6 +119,33 @@ describe("Background Sounds runtime boundary", () => {
104119 } ) ;
105120} ) ;
106121
122+ async function runBundledHelper ( options : { missingSelector ?: string ; available ?: boolean } ) : Promise < Record < string , unknown > > {
123+ const source = await readFile ( new URL ( "../src/background-sounds.jxa" , import . meta. url ) , "utf8" ) ;
124+ const required = new Set ( [
125+ "comfortSoundsAvailable" , "comfortSoundsEnabled" , "setComfortSoundsEnabled:" ,
126+ "selectedComfortSound" , "setSelectedComfortSound:" , "relativeVolume" , "setRelativeVolume:" ,
127+ ] ) ;
128+ const settings = {
129+ comfortSoundsAvailable : options . available ?? true ,
130+ respondsToSelector : ( selector : string ) => required . has ( selector ) && selector !== options . missingSelector ,
131+ } ;
132+ const dollar = Object . assign ( ( _value : unknown ) => ( { } ) , {
133+ NSSelectorFromString : ( selector : string ) => selector ,
134+ NSBundle : { bundleWithPath : ( ) => ( { load : true } ) } ,
135+ NSClassFromString : ( name : string ) => name === "HUComfortSoundsSettings" ? { sharedInstance : settings } : null ,
136+ } ) ;
137+ const context : {
138+ ObjC : { import : ( ) => undefined ; unwrap : ( value : unknown ) => unknown } ;
139+ $ : typeof dollar ;
140+ __result ?: string ;
141+ } = {
142+ ObjC : { import : ( ) => undefined , unwrap : ( value : unknown ) => value } ,
143+ $ : dollar ,
144+ } ;
145+ runInNewContext ( `${ source } \nglobalThis.__result = run(["probe"]);` , context ) ;
146+ return JSON . parse ( context . __result ! ) as Record < string , unknown > ;
147+ }
148+
107149describe ( "Background Sounds session lifecycle" , ( ) => {
108150 const snapshot = {
109151 enabled : false ,
0 commit comments