@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
22import test from "node:test" ;
33
44import { describeHidDevice } from "./hid-diagnostics.ts" ;
5- import { createSupportedClient , listLogicalDevices , logicalDeviceGroups } from "./device-clients.ts" ;
5+ import { createSupportedClient , listLogicalDevices , logicalDeviceGroups , prepareAutoConnectClient } from "./device-clients.ts" ;
66
77function supportedWebHidDevice ( options : {
88 vendorId ?: number ;
@@ -108,3 +108,67 @@ test("Bridge devices keep their native per-physical-device identity", () => {
108108 assert . equal ( logicalDeviceGroups ( devices ) . length , 2 ) ;
109109 assert . equal ( listLogicalDevices ( devices ) . length , 2 ) ;
110110} ) ;
111+
112+ function answeringBridgeRazer ( key : string , answers : boolean , usages = [ [ 0x01 , 0x02 ] ] ) : HIDDevice {
113+ let reply = new Uint8Array ( 90 ) ;
114+ const device = {
115+ ...bridgeDevice ( key , "Razer Basilisk V3 Pro 35K" ) ,
116+ productId : 0x00cc ,
117+ collections : usages . map ( ( [ usagePage , usage ] ) => ( {
118+ usagePage, usage, inputReports : [ ] , outputReports : [ ] , featureReports : [ ] , children : [ ] ,
119+ } ) ) ,
120+ opened : false ,
121+ async open ( ) { this . opened = true ; } ,
122+ async close ( ) { this . opened = false ; } ,
123+ async sendFeatureReport ( reportId : number , data : Uint8Array ) {
124+ assert . equal ( reportId , 0 ) ;
125+ assert . equal ( data [ 6 ] , 0x00 ) ;
126+ assert . equal ( data [ 7 ] , 0x81 , "automatic selection must only probe firmware" ) ;
127+ if ( ! answers ) throw new Error ( "HidD_SetFeature: Incorrect function" ) ;
128+ reply = new Uint8Array ( data ) ;
129+ reply [ 0 ] = 0x02 ;
130+ } ,
131+ async receiveFeatureReport ( ) { return new DataView ( reply . buffer ) ; } ,
132+ } ;
133+ return device as unknown as HIDDevice ;
134+ }
135+
136+ test ( "automatic Bridge selection rejects a dead Razer sibling without closing the working interface" , async ( t ) => {
137+ Object . defineProperty ( globalThis , "window" , { configurable : true , value : { setTimeout } } ) ;
138+ t . after ( ( ) => Reflect . deleteProperty ( globalThis , "window" ) ) ;
139+ const working = answeringBridgeRazer ( "working" , true ) ;
140+ await working . open ( ) ;
141+ const sibling = answeringBridgeRazer ( "sibling" , false , [ [ 0x01 , 0x80 ] , [ 0x0c , 0x01 ] ] ) ;
142+ const candidate = createSupportedClient ( sibling ) ;
143+ assert . ok ( candidate ) ;
144+
145+ assert . equal ( await prepareAutoConnectClient ( candidate ) , null ) ;
146+ assert . equal ( sibling . opened , false ) ;
147+ assert . equal ( working . opened , true ) ;
148+ } ) ;
149+
150+ test ( "automatic Bridge selection keeps answering Razer interfaces regardless of collection shape" , async ( t ) => {
151+ Object . defineProperty ( globalThis , "window" , { configurable : true , value : { setTimeout } } ) ;
152+ t . after ( ( ) => Reflect . deleteProperty ( globalThis , "window" ) ) ;
153+ for ( const usages of [ [ [ 0x01 , 0x02 ] ] , [ [ 0x0c , 0x01 ] ] , [ ] ] ) {
154+ const device = answeringBridgeRazer ( "working" , true , usages ) ;
155+ const candidate = createSupportedClient ( device ) ;
156+ assert . ok ( candidate ) ;
157+ const prepared = await prepareAutoConnectClient ( candidate ) ;
158+ assert . ok ( prepared ) ;
159+ assert . equal ( prepared . device , device ) ;
160+ assert . equal ( device . opened , true ) ;
161+ await prepared . close ( ) ;
162+ }
163+ } ) ;
164+
165+ test ( "automatic selection leaves specialized Razer and WebHID clients on their existing paths" , async ( ) => {
166+ for ( const device of [
167+ { ...bridgeDevice ( "v4" ) , productId : 0x00e5 } ,
168+ supportedWebHidDevice ( ) ,
169+ ] ) {
170+ const client = createSupportedClient ( device as HIDDevice ) ;
171+ assert . ok ( client ) ;
172+ assert . equal ( await prepareAutoConnectClient ( client ) , client ) ;
173+ }
174+ } ) ;
0 commit comments