diff --git a/.gitignore b/.gitignore index b9b7b32..64a343c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ node_modules/ build/ prebuilds/ npm-debug.log + +# Logged telem data +test/*.json diff --git a/README.md b/README.md index ec30e06..6e82fd0 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,36 @@ -# node-irsdk - -[![Build status](https://ci.appveyor.com/api/projects/status/ukyuuq9004wy9h5b/branch/master?svg=true)](https://ci.appveyor.com/project/apihlaja/node-irsdk/branch/master) -[![Greenkeeper badge](https://badges.greenkeeper.io/apihlaja/node-irsdk.svg)](https://greenkeeper.io/) -[![Dependencies](https://img.shields.io/david/apihlaja/node-irsdk.svg)](https://david-dm.org/apihlaja/node-irsdk) -[![devDependencies](https://img.shields.io/david/dev/apihlaja/node-irsdk.svg)](https://david-dm.org/apihlaja/node-irsdk?type=dev) -[![npm version](https://img.shields.io/npm/v/node-irsdk.svg)](https://www.npmjs.com/package/node-irsdk) - -Unofficial [iRacing](http://www.iracing.com/) SDK implementation for Node.js. - -**node-irsdk** provides data access (live telemetry and session info) and most of available commands. You can find some usage examples from [utils](https://github.com/apihlaja/node-irsdk/tree/master/utils) directory, and there is some [data samples](https://github.com/apihlaja/node-irsdk/tree/master/sample-data) too. - -* [GitHub repo](https://github.com/apihlaja/node-irsdk) -* [documentation](https://apihlaja.github.io/node-irsdk) -* [forum thread](http://members.iracing.com/jforum/posts/list/3329583.page) - -Other iRSDK implementations: - -* [Official C/C++ SDK](http://members.iracing.com/jforum/posts/list/1470675.page) -* [iRacingSdkWrapper (C#)](https://github.com/NickThissen/iRacingSdkWrapper) -* [pyirsdk (python3)](https://github.com/kutu/pyirsdk/) -* [iRacingSDK.Net](https://github.com/vipoo/iRacingSDK.Net) - - -## Installing - -Make sure you have [Node.js](https://nodejs.org/) v6 x64 or later. - -`npm install --save node-irsdk` - - -## API documentation - +# node-irsdk + +[![Build status](https://ci.appveyor.com/api/projects/status/ukyuuq9004wy9h5b/branch/master?svg=true)](https://ci.appveyor.com/project/apihlaja/node-irsdk/branch/master) +[![Greenkeeper badge](https://badges.greenkeeper.io/apihlaja/node-irsdk.svg)](https://greenkeeper.io/) +[![Dependencies](https://img.shields.io/david/apihlaja/node-irsdk.svg)](https://david-dm.org/apihlaja/node-irsdk) +[![devDependencies](https://img.shields.io/david/dev/apihlaja/node-irsdk.svg)](https://david-dm.org/apihlaja/node-irsdk?type=dev) +[![npm version](https://img.shields.io/npm/v/node-irsdk.svg)](https://www.npmjs.com/package/node-irsdk) + +Unofficial [iRacing](http://www.iracing.com/) SDK implementation for Node.js. + +**node-irsdk** provides data access (live telemetry and session info) and most of available commands. You can find some usage examples from [utils](https://github.com/apihlaja/node-irsdk/tree/master/utils) directory, and there is some [data samples](https://github.com/apihlaja/node-irsdk/tree/master/sample-data) too. + +* [GitHub repo](https://github.com/apihlaja/node-irsdk) +* [documentation](https://apihlaja.github.io/node-irsdk) +* [forum thread](http://members.iracing.com/jforum/posts/list/3329583.page) + +Other iRSDK implementations: + +* [Official C/C++ SDK](http://members.iracing.com/jforum/posts/list/1470675.page) +* [iRacingSdkWrapper (C#)](https://github.com/NickThissen/iRacingSdkWrapper) +* [pyirsdk (python3)](https://github.com/kutu/pyirsdk/) +* [iRacingSDK.Net](https://github.com/vipoo/iRacingSDK.Net) + + +## Installing + +Make sure you have [Node.js](https://nodejs.org/) v6 x64 or later. + +`npm install --save node-irsdk` + + +## API documentation + ### irsdk @@ -54,12 +54,10 @@ Initialize JsIrSdk, can be done once before using getInstance first time. | [opts.sessionInfoUpdateInterval] | Integer | 0 | SessionInfo update interval, milliseconds | | [opts.sessionInfoParser] | [sessionInfoParser](#iracing..sessionInfoParser) | | Custom parser for session info | - + ```js -var irsdk = require('node-irsdk') -// look for telemetry updates only once per 100 ms -var iracing = irsdk.init({telemetryUpdateInterval: 100}) -``` +var irsdk = require('node-irsdk') // look for telemetry updates only once per 100 ms var iracing = irsdk.init({telemetryUpdateInterval: 100}) +``` #### irsdk.getInstance() ⇒ [iracing](#iracing) @@ -67,11 +65,10 @@ Get initialized instance of JsIrSdk **Kind**: static method of [irsdk](#module_irsdk) **Returns**: [iracing](#iracing) - Running instance of JsIrSdk - + ```js -var irsdk = require('node-irsdk') -var iracing = irsdk.getInstance() -``` +var irsdk = require('node-irsdk') var iracing = irsdk.getInstance() +``` ### iracing ⇐ events.EventEmitter @@ -120,14 +117,14 @@ var iracing = irsdk.getInstance() #### new JsIrSdk() -JsIrSdk is javascript implementation of iRacing SDK. - +JsIrSdk is javascript implementation of iRacing SDK. + Don't use constructor directly, use [getInstance](#module_irsdk.getInstance). - + ```js var iracing = require('node-irsdk').getInstance() -``` +``` #### iracing.Consts : [IrSdkConsts](#IrSdkConsts) @@ -157,13 +154,10 @@ Change camera tool state | --- | --- | --- | | state | [CameraState](#IrSdkConsts.CameraState) | new state | - + ```js -// hide UI and enable mouse aim -var States = iracing.Consts.CameraState -var state = States.CamToolActive | States.UIHidden | States.UseMouseAimMode -iracing.camControls.setState(state) -``` +// hide UI and enable mouse aim var States = iracing.Consts.CameraState var state = States.CamToolActive | States.UIHidden | States.UseMouseAimMode iracing.camControls.setState(state) +``` ##### camControls.switchToCar(carNum, [camGroupNum], [camNum]) @@ -177,29 +171,25 @@ Switch camera, focus on car | [camGroupNum] | Integer | Select camera group | | [camNum] | Integer | Select camera | - + ```js -// show car #2 -iracing.camControls.switchToCar(2) +// show car #2 iracing.camControls.switchToCar(2) -``` - +``` + ```js -// show car #02 -iracing.camControls.switchToCar('02') +// show car #02 iracing.camControls.switchToCar('02') -``` - +``` + ```js -// show leader -iracing.camControls.switchToCar('leader') +// show leader iracing.camControls.switchToCar('leader') -``` - +``` + ```js -// show car #2 using cam group 3 -iracing.camControls.switchToCar(2, 3) -``` +// show car #2 using cam group 3 iracing.camControls.switchToCar(2, 3) +``` ##### camControls.switchToPos(position, [camGroupNum], [camNum]) @@ -213,10 +203,10 @@ Switch camera, focus on position | [camGroupNum] | Integer | Select camera group | | [camNum] | Integer | Select camera | - + ```js iracing.camControls.switchToPos(2) // show P2 -``` +``` #### iracing.playbackControls : Object @@ -241,20 +231,20 @@ Replay and playback controls Play replay **Kind**: static method of [playbackControls](#iracing+playbackControls) - + ```js iracing.playbackControls.play() -``` +``` ##### playbackControls.pause() Pause replay **Kind**: static method of [playbackControls](#iracing+playbackControls) - + ```js iracing.playbackControls.pause() -``` +``` ##### playbackControls.fastForward([speed]) @@ -266,10 +256,10 @@ fast-forward replay | --- | --- | --- | --- | | [speed] | Integer | 2 | FF speed, something between 2-16 works | - + ```js iracing.playbackControls.fastForward() // double speed FF -``` +``` ##### playbackControls.rewind([speed]) @@ -281,10 +271,10 @@ rewind replay | --- | --- | --- | --- | | [speed] | Integer | 2 | RW speed, something between 2-16 works | - + ```js iracing.playbackControls.rewind() // double speed RW -``` +``` ##### playbackControls.slowForward([divider]) @@ -296,10 +286,10 @@ slow-forward replay, slow motion | --- | --- | --- | --- | | [divider] | Integer | 2 | divider of speed, something between 2-17 works | - + ```js iracing.playbackControls.slowForward(2) // half speed -``` +``` ##### playbackControls.slowBackward([divider]) @@ -311,10 +301,10 @@ slow-backward replay, reverse slow motion | --- | --- | --- | --- | | [divider] | Integer | 2 | divider of speed, something between 2-17 works | - + ```js iracing.playbackControls.slowBackward(2) // half speed RW -``` +``` ##### playbackControls.search(searchMode) @@ -326,10 +316,10 @@ Search things from replay | --- | --- | --- | | searchMode | [RpySrchMode](#IrSdkConsts.RpySrchMode) | what to search | - + ```js iracing.playbackControls.search('nextIncident') -``` +``` ##### playbackControls.searchTs(sessionNum, sessionTimeMS) @@ -342,11 +332,10 @@ Search timestamp | sessionNum | Integer | Session number | | sessionTimeMS | Integer | Session time in milliseconds | - + ```js -// jump to 2nd minute of 3rd session -iracing.playbackControls.searchTs(2, 2*60*1000) -``` +// jump to 2nd minute of 3rd session iracing.playbackControls.searchTs(2, 2*60*1000) +``` ##### playbackControls.searchFrame(frameNum, rpyPosMode) @@ -359,10 +348,10 @@ Go to frame. Frame counting can be relative to begin, end or current. | frameNum | Integer | Frame number | | rpyPosMode | [RpyPosMode](#IrSdkConsts.RpyPosMode) | Is frame number relative to begin, end or current frame | - + ```js iracing.playbackControls.searchFrame(1, 'current') // go to 1 frame forward -``` +``` #### iracing.telemetry @@ -401,10 +390,10 @@ Execute any of available commands, excl. FFB command Reload all car textures **Kind**: instance method of [iracing](#iracing) - + ```js iracing.reloadTextures() // reload all paints -``` +``` #### iracing.reloadTexture(carIdx) @@ -416,10 +405,10 @@ Reload car's texture | --- | --- | --- | | carIdx | Integer | car to reload | - + ```js iracing.reloadTexture(1) // reload paint of carIdx=1 -``` +``` #### iracing.execChatCmd(cmd, [arg]) @@ -432,10 +421,10 @@ Execute chat command | cmd | [ChatCommand](#IrSdkConsts.ChatCommand) | | | [arg] | Integer | Command argument, if needed | - + ```js iracing.execChatCmd('cancel') // close chat window -``` +``` #### iracing.execChatMacro(num) @@ -447,10 +436,10 @@ Execute chat macro | --- | --- | --- | | num | Integer | Macro's number (0-15) | - + ```js iracing.execChatMacro(1) // macro 1 -``` +``` #### iracing.execPitCmd(cmd, [arg]) @@ -463,14 +452,10 @@ Execute pit command | cmd | [PitCommand](#IrSdkConsts.PitCommand) | | | [arg] | Integer | Command argument, if needed | - + ```js -// full tank, no tires, no tear off -iracing.execPitCmd('clear') -iracing.execPitCmd('fuel', 999) // 999 liters -iracing.execPitCmd('lf') // new left front -iracing.execPitCmd('lr', 200) // new left rear, 200 kPa -``` +// full tank, no tires, no tear off iracing.execPitCmd('clear') iracing.execPitCmd('fuel', 999) // 999 liters iracing.execPitCmd('lf') // new left front iracing.execPitCmd('lr', 200) // new left rear, 200 kPa +``` #### iracing.execTelemetryCmd(cmd) @@ -482,82 +467,70 @@ Control telemetry logging (ibt file) | --- | --- | --- | | cmd | [TelemCommand](#IrSdkConsts.TelemCommand) | Command: start/stop/restart | - + ```js iracing.execTelemetryCmd('restart') -``` +``` #### "Connected" iRacing, sim, is started **Kind**: event emitted by [iracing](#iracing) - + ```js -iracing.on('Connected', function (evt) { - console.log(evt) -}) -``` +iracing.on('Connected', function (evt) { console.log(evt) }) +``` #### "Disconnected" iRacing, sim, was closed **Kind**: event emitted by [iracing](#iracing) - + ```js -iracing.on('Disconnected', function (evt) { - console.log(evt) -}) -``` +iracing.on('Disconnected', function (evt) { console.log(evt) }) +``` #### "TelemetryDescription" Telemetry description, contains description of available telemetry values **Kind**: event emitted by [iracing](#iracing) - + ```js -iracing.on('TelemetryDescription', function (data) { - console.log(evt) -}) -``` +iracing.on('TelemetryDescription', function (data) { console.log(evt) }) +``` #### "Telemetry" Telemetry update **Kind**: event emitted by [iracing](#iracing) - + ```js -iracing.on('Telemetry', function (evt) { - console.log(evt) -}) -``` +iracing.on('Telemetry', function (evt) { console.log(evt) }) +``` #### "SessionInfo" SessionInfo update **Kind**: event emitted by [iracing](#iracing) - + ```js -iracing.on('SessionInfo', function (evt) { - console.log(evt) -}) -``` +iracing.on('SessionInfo', function (evt) { console.log(evt) }) +``` #### "update" any update event **Kind**: event emitted by [iracing](#iracing) - + ```js -iracing.on('update', function (evt) { - console.log(evt) -}) -``` +iracing.on('update', function (evt) { console.log(evt) }) +``` #### iracing~sessionInfoParser ⇒ Object @@ -576,10 +549,10 @@ Parser for SessionInfo YAML IrSdkConsts, iRacing SDK constants/enums. **Kind**: global constant - + ```js var IrSdkConsts = require('node-irsdk').getInstance().Consts -``` +``` * [IrSdkConsts](#IrSdkConsts) * [.BroadcastMsg](#IrSdkConsts.BroadcastMsg) @@ -592,6 +565,11 @@ var IrSdkConsts = require('node-irsdk').getInstance().Consts * [.PitCommand](#IrSdkConsts.PitCommand) * [.TelemCommand](#IrSdkConsts.TelemCommand) * [.CamFocusAt](#IrSdkConsts.CamFocusAt) + * [.CamCameraState](#IrSdkConsts.CamCameraState) + * [.SessionState](#IrSdkConsts.SessionState) + * [.EngineWarnings](#IrSdkConsts.EngineWarnings) + * [.Flags](#IrSdkConsts.Flags) + * [.PitSvFlags](#IrSdkConsts.PitSvFlags) @@ -620,7 +598,7 @@ Available command messages. #### IrSdkConsts.CameraState -Camera state +Camera state Camera state is bitfield; use these values to compose a new state. **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) @@ -751,32 +729,141 @@ When switching camera, these can be used instead of car number / position | Exciting | -1 | | | Driver | 0 | Use car number / position instead of this | + +#### IrSdkConsts.CamCameraState +Camera states -## Development +**Kind**: static enum of [IrSdkConsts](#IrSdkConsts) +**Properties** -To develop `node-irsdk` itself, you need working working installation of -[node-gyp](https://github.com/nodejs/node-gyp#on-windows). +| Name | Default | +| --- | --- | +| IsSessionScreen | 1 | +| IsScenicActive | 2 | +| CamToolActive | 4 | +| UIHidden | 8 | +| UseAutoShotSelection | 16 | +| UseTemporaryEdits | 32 | +| UseKeyAcceleration | 64 | +| UseKey10xAcceleration | 128 | +| UseMouseAimMode | 256 | -Useful commands: + -* `npm install` builds binary addon -* `npm run ready` runs tests and updates docs +#### IrSdkConsts.SessionState +Session states -### Making a new release +**Kind**: static enum of [IrSdkConsts](#IrSdkConsts) +**Properties** -1. Check if license file needs updating -2. `npm run ready` -3. Tag new version using `npm version`, push to Github -4. Wait for Appveyor to upload binaries -5. `npm publish` -6. Add release notes to Github, helper `npm run release-notes`. +| Name | Default | +| --- | --- | +| Invalid | 0 | +| GetInCar | 1 | +| Warmup | 2 | +| ParadeLaps | 3 | +| Racing | 4 | +| Checkered | 5 | +| CoolDown | 6 | + + -## License +#### IrSdkConsts.EngineWarnings +Engine warnings states + +**Kind**: static enum of [IrSdkConsts](#IrSdkConsts) +**Properties** -Released under the [MIT License](https://github.com/apihlaja/node-irsdk/blob/master/LICENSE.md). +| Name | Default | +| --- | --- | +| waterTempWarning | 1 | +| fuelPressureWarning | 2 | +| oilPressureWarning | 4 | +| engineStalled | 8 | +| pitSpeedLimiter | 16 | +| revLimiterActive | 32 | + -## Credits +#### IrSdkConsts.Flags +Session Flag states -Parts of original irsdk used, license available here: https://github.com/apihlaja/node-irsdk/blob/master/src/cpp/irsdk/irsdk_defines.h (BSD-3-Clause) +**Kind**: static enum of [IrSdkConsts](#IrSdkConsts) +**Properties** + +| Name | Default | +| --- | --- | +| checkered | 1 | +| white | 2 | +| green | 4 | +| yellow | 8 | +| red | 16 | +| blue | 32 | +| debris | 64 | +| crossed | 128 | +| yellowWaving | 256 | +| oneLapToGreen | 512 | +| greenHeld | 1024 | +| tenToGo | 2048 | +| fiveToGo | 4096 | +| randomWaving | 8192 | +| caution | 16384 | +| cautionWaving | 32768 | +| black | 65536 | +| disqualify | 131072 | +| servicible | 262144 | +| furled | 524288 | +| repair | 1048576 | +| startHidden | 268435456 | +| startReady | 536870912 | +| startSet | 1073741824 | +| startGo | 2147483648 | + + + +#### IrSdkConsts.PitSvFlags +Session Pit states + +**Kind**: static enum of [IrSdkConsts](#IrSdkConsts) +**Properties** + +| Name | Default | +| --- | --- | +| LFTireChange | 1 | +| RFTireChange | 2 | +| LRTireChange | 4 | +| RRTireChange | 8 | +| FuelFill | 16 | +| WindshieldTearoff | 32 | +| FastRepair | 64 | + + + +## Development + +To develop `node-irsdk` itself, you need working working installation of +[node-gyp](https://github.com/nodejs/node-gyp#on-windows). + +Useful commands: + +* `npm install` builds binary addon +* `npm run ready` runs tests and updates docs + +### Making a new release + +1. Check if license file needs updating +2. `npm run ready` +3. Tag new version using `npm version`, push to Github +4. Wait for Appveyor to upload binaries +5. `npm publish` +6. Add release notes to Github, helper `npm run release-notes`. + +## License + +Released under the [MIT License](https://github.com/apihlaja/node-irsdk/blob/master/LICENSE.md). + + +## Credits + +Parts of original irsdk used, license available here: https://github.com/apihlaja/node-irsdk/blob/master/src/cpp/irsdk/irsdk_defines.h (BSD-3-Clause) diff --git a/binding.gyp b/binding.gyp index 27fa061..a7f5d58 100644 --- a/binding.gyp +++ b/binding.gyp @@ -1,4 +1,4 @@ -{ +{ "targets": [ { "target_name": "IrSdkNodeBindings", diff --git a/src/IrSdkConsts.js b/src/IrSdkConsts.js index b75066e..9f847d1 100644 --- a/src/IrSdkConsts.js +++ b/src/IrSdkConsts.js @@ -157,6 +157,86 @@ var IrSdkConsts = { Exciting: -1, /** Use car number / position instead of this */ Driver: 0 + }, + /** Camera states + @enum + */ + CamCameraState: + { + IsSessionScreen: 0x0001, + IsScenicActive: 0x0002, + CamToolActive: 0x0004, + UIHidden: 0x0008, + UseAutoShotSelection: 0x0010, + UseTemporaryEdits: 0x0020, + UseKeyAcceleration: 0x0040, + UseKey10xAcceleration: 0x0080, + UseMouseAimMode: 0x0100 + }, + /** Session states + @enum + */ + SessionState: { + Invalid: 0, + GetInCar: 1, + Warmup: 2, + ParadeLaps: 3, + Racing: 4, + Checkered: 5, + CoolDown: 6 + }, + /** Engine warnings states + @enum + */ + EngineWarnings: { + waterTempWarning: 0x0001, + fuelPressureWarning: 0x0002, + oilPressureWarning: 0x0004, + engineStalled: 0x0008, + pitSpeedLimiter: 0x0010, + revLimiterActive: 0x0020 + }, + /** Session Flag states + @enum + */ + Flags: { + checkered: 0x00000001, + white: 0x00000002, + green: 0x00000004, + yellow: 0x00000008, + red: 0x00000010, + blue: 0x00000020, + debris: 0x00000040, + crossed: 0x00000080, + yellowWaving: 0x00000100, + oneLapToGreen: 0x00000200, + greenHeld: 0x00000400, + tenToGo: 0x00000800, + fiveToGo: 0x00001000, + randomWaving: 0x00002000, + caution: 0x00004000, + cautionWaving: 0x00008000, + black: 0x00010000, + disqualify: 0x00020000, + servicible: 0x00040000, + furled: 0x00080000, + repair: 0x00100000, + startHidden: 0x10000000, + startReady: 0x20000000, + startSet: 0x40000000, + startGo: 0x80000000 + }, + /** Session Pit states + @enum + */ + PitSvFlags: { + LFTireChange: 0x0001, + RFTireChange: 0x0002, + LRTireChange: 0x0004, + RRTireChange: 0x0008, + FuelFill: 0x0010, + WindshieldTearoff: 0x0020, + FastRepair: 0x0040 } } diff --git a/test/smoke-test.js b/test/smoke-test.js index 48892e4..401cc60 100644 --- a/test/smoke-test.js +++ b/test/smoke-test.js @@ -1,8 +1,10 @@ // tests if node-irsdk works at all.. var expect = require('chai').expect +var Consts = require('../src/IrSdkConsts') var irsdk = require('../') +var fs = require('fs') irsdk.init({ telemetryUpdateInterval: 0, @@ -25,6 +27,7 @@ iracing.once('Connected', function () { console.log('TelemetryDescription event received') expect(data).to.exist.and.to.be.an('object') desc = data + fs.writeFileSync('test/TelemetryDescription.json', JSON.stringify(desc, null, 2), 'utf8') checkTelemetryValues(telemetry, desc) done('desc') }) @@ -35,6 +38,7 @@ iracing.once('Connected', function () { expect(data).to.have.property('timestamp').that.is.a('date') expect(data).to.have.property('values').that.is.an('object') telemetry = data + fs.writeFileSync('test/Telemetry.json', JSON.stringify(telemetry, null, 2), 'utf8') checkTelemetryValues(telemetry, desc) done('telemetry') }) @@ -58,6 +62,14 @@ var checkTelemetryValues = function (telemetry, desc) { console.log('got telemetry and its description, validating output..') for (var telemetryVarName in desc) { + if (typeof telemetry.values[telemetryVarName] === 'string') { + var enumKey = Object.keys(Consts).find(function (key) { + return key.toLowerCase() === telemetryVarName.toLowerCase() + }) + if (enumKey) { + return Consts[enumKey] + } + } if (desc.hasOwnProperty(telemetryVarName)) { console.log('checking ' + telemetryVarName) var varDesc = desc[telemetryVarName] @@ -79,24 +91,20 @@ var checkTelemetryValues = function (telemetry, desc) { } var validateValue = function (val, desc) { if (desc.type !== 'bitField') { - if (desc.unit.substr(0, 5) === 'irsdk') { - expect(val).to.be.a('string', 'enums should be converted to strings') - } else { - if (desc.type === 'bool') { - expect(val).to.be.a('boolean') - } - if (desc.type === 'int') { - expect(val).to.be.a('number') - } - if (desc.type === 'float') { - expect(val).to.be.a('number') - } - if (desc.type === 'double') { - expect(val).to.be.a('number') - } - if (desc.type === 'char') { - expect(val).to.be.a('string').and.have.length(1) - } + if (desc.type === 'bool') { + expect(val).to.be.a('boolean') + } + if (desc.type === 'int') { + expect(val).to.be.a('number') + } + if (desc.type === 'float') { + expect(val).to.be.a('number') + } + if (desc.type === 'double') { + expect(val).to.be.a('number') + } + if (desc.type === 'char') { + expect(val).to.be.a('string').and.have.length(1) } } else { // expect bitField to be converted to array