cabana: thorough tests for desired bit dragging functionality

main
Andy Haden 2017-06-26 21:05:20 -07:00
parent 8638258eba
commit 9586813cb4
8 changed files with 403 additions and 29 deletions

View File

@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"aphrodite": "^1.2.1",
"classnames": "^2.2.5",
"create-react-class": "^15.5.3",
"cuint": "^0.2.2",
"file-saver": "^1.3.3",
@ -13,17 +14,17 @@
"int64-buffer": "^0.1.9",
"moment": "^2.18.1",
"prop-types": "^15.5.10",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-infinite": "^0.11.0",
"react-list": "^0.8.6",
"react-measure": "^1.4.7",
"react-test-renderer": "^15.6.1",
"react-vega": "^3.0.0",
"vega": "git+ssh://git@github.com/commaai/vega.git#HEAD",
"vega-tooltip": "^0.4.0"
},
"devDependencies": {
"worker-loader": "^0.8.0",
"autoprefixer": "6.7.2",
"babel-core": "6.22.1",
"babel-eslint": "7.1.1",
@ -38,6 +39,7 @@
"css-loader": "0.26.1",
"detect-port": "1.1.0",
"dotenv": "2.0.0",
"enzyme": "^2.9.1",
"eslint": "3.16.1",
"eslint-config-react-app": "^0.6.2",
"eslint-loader": "1.6.0",
@ -61,7 +63,8 @@
"webpack": "1.14.0",
"webpack-dev-server": "1.16.2",
"webpack-manifest-plugin": "1.1.0",
"whatwg-fetch": "2.0.2"
"whatwg-fetch": "2.0.2",
"worker-loader": "^0.8.0"
},
"scripts": {
"start": "python simple-cors-http-server.py & python server.py & node scripts/start.js",
@ -74,6 +77,7 @@
}
},
"jest": {
"timers": "fake",
"collectCoverageFrom": [
"src/**/*.{js,jsx}"
],

View File

@ -0,0 +1,290 @@
/*
Tests for AddSignals component
note: 'right' and 'left' in test descriptions
refer to the sides of the bit matrix
as displayed to the user.
*/
import AddSignals from '../../components/AddSignals';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import {StyleSheetTestUtils} from 'aphrodite';
// Prevents style injection from firing after test finishes
// and jsdom is torn down.
beforeEach(() => {
StyleSheetTestUtils.suppressStyleInjection();
});
afterEach(() => {
StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
});
// signal creation
function createAddSignals(signals) {
if(signals === undefined) {
signals = {};
}
const message = {signals,
address: 0,
entries: [
{relTime: 0,
hexData: '0000000000000000'}
]};
const component = shallow(<AddSignals
message={message}
messageIndex={0}
onConfirmedSignalChange={() => {}} />);
return component;
}
test('double clicking adds a signal', () => {
const component = createAddSignals();
const firstBit = component.find('.bit').first();
firstBit.simulate('dblclick');
const newSignal = Object.values(component.state('signals'))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(1);
});
test('dragging right to left across a byte creates a little endian signal', () => {
const component = createAddSignals();
const leftBitInByte = component.find('.bit').first();
const rightBitInByte = component.find('.bit').at(7);
rightBitInByte.simulate('mousedown');
leftBitInByte.simulate('mouseup');
const newSignal = Object.values(component.state('signals'))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(8);
expect(newSignal.isLittleEndian).toBe(true);
expect(newSignal.startBit).toBe(0);
});
test('dragging left to right across a byte creates a big endian signal', () => {
const component = createAddSignals();
const leftBitInByte = component.find('.bit').first();
const rightBitInByte = component.find('.bit').at(7);
leftBitInByte.simulate('mousedown');
rightBitInByte.simulate('mouseup');
const newSignal = Object.values(component.state('signals'))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(8);
expect(newSignal.isLittleEndian).toBe(false);
expect(newSignal.startBit).toBe(7);
});
test('dragging from the left of byte 0 to right of byte 1 creates a big endian signal spanning both bytes', () => {
const component = createAddSignals();
const leftBitInByte = component.find('.bit').first();
const rightBitInByte = component.find('.bit').at(15);
leftBitInByte.simulate('mousedown');
rightBitInByte.simulate('mouseup');
const newSignal = Object.values(component.state('signals'))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(16);
expect(newSignal.isLittleEndian).toBe(false);
expect(newSignal.startBit).toBe(7);
});
test('dragging from the right of byte 0 to the left of byte 1 creates a little endian signal spanning both bytes', () => {
const component = createAddSignals();
const leftBitInByteOne = component.find('.bit').at(8); // left of byte 1
const rightBitInByteZero = component.find('.bit').at(7); // right of byte 0
rightBitInByteZero.simulate('mousedown');
leftBitInByteOne.simulate('mouseup');
const newSignal = Object.values(component.state('signals'))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(16);
expect(newSignal.isLittleEndian).toBe(true);
expect(newSignal.startBit).toBe(0);
});
test('dragging from the left of byte 1 to the right of byte 0 creates a little endian signal spanning both bytes', () => {
const component = createAddSignals();
const leftBitInByteOne = component.find('.bit').at(8);
const rightBitInByteZero = component.find('.bit').at(7);
leftBitInByteOne.simulate('mousedown');
rightBitInByteZero.simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(16);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});
test('dragging from the right of byte 1 to the left of byte 0 creates a big endian signal spanning both bytes', () => {
const component = createAddSignals();
const leftBitInByteZero = component.find('.bit').at(0);
const rightBitInByteOne = component.find('.bit').at(15);
rightBitInByteOne.simulate('mousedown');
leftBitInByteZero.simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(16);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
// signal mutation
test('dragging a one-bit big-endian signal to the right should extend it to the right of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 7, size: 1, isLittleEndian: false});
const signalBit = component.find('.bit').at(0);
signalBit.simulate('mousedown');
for(let i = 1; i < 8; i++) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(7);
bitAtRightOfFirstByte.simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
test('dragging a one-bit little-endian signal to the right should extend it to the right of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 7, size: 1, isLittleEndian: true});
const signalBit = component.find('.bit').at(0);
signalBit.simulate('mousedown');
for(let i = 1; i < 8; i++) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(7);
bitAtRightOfFirstByte.simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});
test('dragging a one-bit big-endian signal to the left should extend it to the left of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 0, size: 1, isLittleEndian: false});
const signalBit = component.find('.bit').at(7);
signalBit.simulate('mousedown');
for(let i = 6; i > -1; i--) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(0);
bitAtRightOfFirstByte.simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
test('extending a two-bit big-endian signal by its LSB should extend it to the right of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 7, size: 2, isLittleEndian: false});
const lsb = component.find('.bit').at(1);
lsb.simulate('mousedown');
for(let i = 0; i < 8; i++) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(7);
bitAtRightOfFirstByte.simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
test('a two-bit little-endian signal should extend by its LSB to the end of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 6, size: 2, isLittleEndian: true});
const lsb = component.find('.bit').at(1);
lsb.simulate('mousedown');
for(let i = 0; i < 8; i++) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(7);
bitAtRightOfFirstByte.simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});
test('dragging the lsb of a little-endian signal spanning an entire byte should not be allowed to pass the MSB', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 0, size: 8, isLittleEndian: true});
const lsb = component.find('.bit').at(7);
lsb.simulate('mousedown');
const bitPastMsb = component.find('.bit').at(15);
bitPastMsb.simulate('mouseenter');
bitPastMsb.simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});
test('dragging the lsb of a big-endian signal towards the msb in the same byte should contract the signal', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 7, size: 8, isLittleEndian: false});
const lsb = component.find('.bit').at(7);
lsb.simulate('mousedown');
for(let i = 6; i > 0; i--) {
component.find('.bit').at(i).simulate('mouseenter');
}
component.find('.bit').at(1).simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(2);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
test('a big endian signal spanning one byte should switch to little endian preserving its bit coverage', () => {
const component = shallowAddSignals();
component.instance().createSignal({startBit: 0, size: 8, isLittleEndian: true});
const lsb = component.find('.bit').at(7);
lsb.simulate('mousedown');
const bitPastMsb = component.find('.bit').at(15);
bitPastMsb.simulate('mouseenter');
bitPastMsb.simulate('mouseup');
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});

View File

@ -0,0 +1,28 @@
import SignalLegend from '../../components/SignalLegend';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import {StyleSheetTestUtils} from 'aphrodite';
// Prevents style injection from firing after test finishes
// and jsdom is torn down.
beforeEach(() => {
StyleSheetTestUtils.suppressStyleInjection();
});
afterEach(() => {
StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
});
test('a little endian signal spanning one byte should switch to big endian preserving its bit coverage', () => {
});
test('a big endian signal spanning two bytes should switch to little endian preserving its bit coverage', () => {
});
test("a big endian signal spanning one and a half bytes should switch to little endian preserving the first byte's coverage", () => {
});

View File

@ -1,7 +1,7 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, css } from 'aphrodite/no-important';
import { StyleSheet } from 'aphrodite/no-important';
import css from '../utils/css';
import {hash} from '../utils/string';
import SignalLegend from './SignalLegend';
import Signal from '../models/can/signal';
@ -22,7 +22,6 @@ export default class AddSignals extends Component {
static propTypes = {
message: PropTypes.object,
onConfirmedSignalChange: PropTypes.func,
onClose: PropTypes.func,
messageIndex: PropTypes.number,
onSignalPlotChange: PropTypes.func,
plottedSignals: PropTypes.array
@ -140,14 +139,19 @@ export default class AddSignals extends Component {
if(dragStartBit !== null) {
if(dragSignal !== null) {
if(dragStartBit === dragSignal.startBit) {
if(dragStartBit === dragSignal.startBit && dragSignal.size > 1) {
if(!dragSignal.isLittleEndian) {
// should not be able to drag the msb past the lsb
const startBigEndian = DbcUtils.bigEndianBitIndex(dragStartBit);
const hoveredBigEndian = DbcUtils.bigEndianBitIndex(bitIdx);
const lsbPos = (startBigEndian + dragSignal.size - 1);
if(hoveredBigEndian > lsbPos) {
const lsbBigEndian = dragSignal.lsbBitNumber();
if(hoveredBigEndian > lsbBigEndian) {
return;
}
} else {
// should not be able to drag the lsb past the msb
if(bitIdx > dragSignal.msbBitIndex()) {
return;
}
}
@ -161,10 +165,33 @@ export default class AddSignals extends Component {
} else {
dragSignal.size -= Math.abs(diff);
}
dragSignal.startBit += diff;
signals[dragSignal.name] = dragSignal;
dragStartBit = dragSignal.startBit;
} else if(dragSignal.size === 1) {
// 1-bit signals can be dragged in either direction
if(Math.floor(bitIdx / 8) === Math.floor(dragStartBit / 8)) {
if(bitIdx > dragStartBit) {
if(dragSignal.isLittleEndian) {
dragSignal.size = bitIdx - dragSignal.startBit;
} else {
dragSignal.startBit = bitIdx;
dragSignal.size = bitIdx - dragStartBit + 1;
dragStartBit = bitIdx;
}
} else {
if(dragSignal.isLittleEndian) {
dragSignal.startBit = bitIdx;
dragSignal.size = dragStartBit - bitIdx + 1;
dragStartBit = bitIdx;
} else {
dragSignal.size = dragStartBit - bitIdx + 1;
dragStartBit = bitIdx;
}
}
}
} else if(dragSignal.isLittleEndian && dragStartBit === dragSignal.msbBitIndex()) {
const diff = bitIdx - dragStartBit;
if(dragSignal.bitDescription(bitIdx) === null) {
@ -216,11 +243,11 @@ export default class AddSignals extends Component {
dragSignal: dragSignal || null})
}
createSignalAtBit(bitIdx, size) {
createSignal({startBit, size, isLittleEndian}) {
const signal = new Signal({name: this.nextNewSignalName(),
startBit: bitIdx,
startBit,
size: size,
isLittleEndian: false});
isLittleEndian});
const {signals} = this.state;
signals[signal.name] = signal;
@ -236,18 +263,37 @@ export default class AddSignals extends Component {
return;
}
}
const isDragAcrossSingleByte = Math.floor(dragEndBit / 8) === Math.floor(dragStartBit / 8);
const isDragDirectionUp = !isDragAcrossSingleByte && dragEndBit < dragStartBit;
let isLittleEndian;
if(isDragAcrossSingleByte || !isDragDirectionUp) {
isLittleEndian = (dragStartBit % 8 < 4);
} else {
isLittleEndian = (dragStartBit % 8 >= 4);
}
let size, startBit = dragStartBit;
if(Math.floor(dragEndBit / 8) === Math.floor(dragStartBit / 8)){
if(isDragAcrossSingleByte){
size = Math.abs(dragEndBit - dragStartBit) + 1;
} else {
if(dragEndBit < dragStartBit) {
startBit = dragEndBit;
if(isLittleEndian) {
if(dragEndBit > dragStartBit) {
startBit = dragStartBit;
size = dragEndBit - dragStartBit + 1;
} else {
startBit = dragEndBit;
size = dragStartBit - dragEndBit + 1;
}
} else {
if(dragEndBit < dragStartBit) {
startBit = dragEndBit;
}
size = Math.abs(DbcUtils.bigEndianBitIndex(dragEndBit) - DbcUtils.bigEndianBitIndex(dragStartBit)) + 1;
}
size = Math.abs(DbcUtils.bigEndianBitIndex(dragEndBit) - DbcUtils.bigEndianBitIndex(dragStartBit)) + 1;
}
this.createSignalAtBit(startBit, size);
this.createSignal({startBit, size, isLittleEndian});
}
}
onBitMouseUp(dragEndBit, signal) {
@ -285,7 +331,7 @@ export default class AddSignals extends Component {
bitIsContainedInSelection(bitIdx, isLittleEndian = false) {
const {dragStartBit, dragCurrentBit} = this.state;
if(isLittleEndian) {
if(isLittleEndian || (dragStartBit % 8 < 4)) {
return dragStartBit !== null && dragCurrentBit !== null
&& bitIdx >= dragStartBit && bitIdx <= dragCurrentBit;
} else {
@ -297,13 +343,12 @@ export default class AddSignals extends Component {
}
}
onBitDoubleClick(bitIdx, signal) {
onBitDoubleClick(startBit, signal) {
if(signal === undefined) {
this.createSignalAtBit(bitIdx, 1);
this.createSignal({startBit, size: 1, isLittleEndian: false});
}
}
bitMatrix() {
const {bits} = this.state;
const rows = [];
@ -327,15 +372,14 @@ export default class AddSignals extends Component {
} else if(this.bitIsContainedInSelection(bitIdx)) {
bitStyle = Styles.bitSelectedStyle;
}
const className = css(Styles.bit, bitStyle);
const className = css('bit', Styles.bit, bitStyle);
rowBits.push((<td key={j.toString()}
className={className}
onMouseEnter={() => this.onBitHover(bitIdx, signal)}
onMouseLeave={() => this.onSignalHoverEnd(signal)}
onMouseDown={this.onBitMouseDown.bind(this, bitIdx, signal)}
onMouseUp={this.onBitMouseUp.bind(this, bitIdx, signal)}
onDoubleClick={(() => this.onBitDoubleClick(bitIdx, signal))
}
onDoubleClick={(() => this.onBitDoubleClick(bitIdx, signal))}
><span>
{this.bitValue(i, j)}
</span>

View File

@ -1,6 +1,6 @@
import React, {Component} from 'react';
import { StyleSheet, css } from 'aphrodite/no-important';
import PropTypes from 'prop-types';
import {css} from 'aphrodite/no-important';
import Modal from './Modal';
import DBC from '../models/can/dbc';

View File

@ -185,7 +185,6 @@ export default class RouteSeeker extends Component {
}
}
render() {
const {seekedBarStyle, markerStyle, tooltipStyle} = this.state;
return (<div className={this.props.className}>

View File

@ -93,7 +93,7 @@ export default class SignalLegendEntry extends Component {
let titleCol = <td>{title}</td>;
return <tr>{titleCol}<td>{valueCol}</td></tr>;
return <tr key={field}>{titleCol}<td>{valueCol}</td></tr>;
}
updateField(field, value) {

View File

@ -66,14 +66,23 @@ export default class Signal {
}
lsbBitIndex() {
// Returns LSB bit index in matrix order (see AddSignals.js)
if(this.isLittleEndian) {
return this.startBit;
} else {
const lsbBitNumber = DbcUtils.bigEndianBitIndex(this.startBit) + this.size - 1;
const lsbBitNumber = this.lsbBitNumber();
return DbcUtils.matrixBitNumber(lsbBitNumber);
}
}
lsbBitNumber() {
// Returns LSB bit number in big endian ordering
return DbcUtils.bigEndianBitIndex(this.startBit) + this.size - 1;
}
msbBitIndex() {
if(this.isLittleEndian) {
return this.startBit + this.size - 1;