Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion controller/node-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"dev:server": "npm run start --prefix server",
"dev:start-client": "vite --port 8081 --host",
"build": "vite build",
"lint": "eslint server/*.js --fix && eslint client/*.js --fix"
"lint": "eslint server/*.js --fix && eslint client/*.js --fix",
"test": "node --test"
},
"author": "Ivo Zivkov",
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions controller/node-js/server/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function DriveValue () {
}

this.read = () => {
return Math.round(value, 3)
return Math.round(value * 1000) / 1000
}
}

Expand Down Expand Up @@ -121,4 +121,4 @@ function DriveCommandReducer () {
}
}

module.exports = { Commands, CommandHandler }
module.exports = { Commands, CommandHandler, DriveValue }
97 changes: 97 additions & 0 deletions controller/node-js/test/commands.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Unit tests for the OpenBot node-js controller command logic.
*
* These exercise the pure server-side command mapping that ultimately drives
* the robot's left/right motors, so a regression here could send wrong motor
* commands. Run with: `npm test` (uses Node's built-in test runner).
*/

const test = require('node:test')
const assert = require('node:assert')

const { Commands, DriveValue } = require('../server/commands.js')

// Creates a CommandHandler and captures every command string it sends.
function makeHandler () {
const sent = []
const commands = new Commands(msg => sent.push(msg))
return { handler: commands.getCommandHandler(), sent }
}

test('goForward sends full forward drive command', () => {
const { handler, sent } = makeHandler()
handler.goForward()
assert.deepStrictEqual(JSON.parse(sent[0]), { driveCmd: { l: 1, r: 1 } })
})

test('goBackward sends full reverse drive command', () => {
const { handler, sent } = makeHandler()
handler.goBackward()
assert.deepStrictEqual(JSON.parse(sent[0]), { driveCmd: { l: -1, r: -1 } })
})

test('forwardLeft mixes left/right correctly', () => {
const { handler, sent } = makeHandler()
handler.forwardLeft()
assert.deepStrictEqual(JSON.parse(sent[0]), { driveCmd: { l: -0.5, r: 1 } })
})

test('forwardRight mixes right/left correctly', () => {
const { handler, sent } = makeHandler()
handler.forwardRight()
assert.deepStrictEqual(JSON.parse(sent[0]), { driveCmd: { l: 1, r: -0.5 } })
})

test('rotateLeft / rotateRight send opposing wheel commands', () => {
const { handler, sent } = makeHandler()
handler.rotateLeft()
assert.deepStrictEqual(JSON.parse(sent[0]), { driveCmd: { l: -1, r: 1 } })

const { handler: handler2, sent: sent2 } = makeHandler()
handler2.rotateRight()
assert.deepStrictEqual(JSON.parse(sent2[0]), { driveCmd: { l: 1, r: -1 } })
})

test('reset sends a zero drive command', () => {
const { handler, sent } = makeHandler()
handler.reset()
assert.deepStrictEqual(JSON.parse(sent[0]), { driveCmd: { l: 0, r: 0 } })
})

test('consecutive identical drive commands are de-duplicated', () => {
const { handler, sent } = makeHandler()
handler.goForward()
handler.goForward()
handler.goForward()
assert.strictEqual(sent.length, 1)
})

test('different drive commands are each sent', () => {
const { handler, sent } = makeHandler()
handler.goForward()
handler.goBackward()
assert.strictEqual(sent.length, 2)
})

test('sendCommand forwards the raw command string', () => {
const { handler, sent } = makeHandler()
handler.sendCommand('NOISE')
assert.strictEqual(sent[0], '{command: NOISE }')
})

test('DriveValue clamps to [-1, 1] and read() rounds to 3 decimals', () => {
const dv = new DriveValue()
assert.strictEqual(dv.reset(), 0)
assert.strictEqual(dv.min(), -1)
assert.strictEqual(dv.max(), 1)

// Before the fix this returned Math.round(0.12345) === 0 (2nd arg ignored).
dv.write(0.12345)
assert.strictEqual(dv.read(), 0.123)

dv.write(0.9999)
assert.strictEqual(dv.read(), 1)

dv.write(-0.4567)
assert.strictEqual(dv.read(), -0.457)
})
Loading