1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-15 12:25:15 +03:00

Refactor msp to modules (#3214)

* feat: refactor everything to modules

* fix: a lot of circular deps fixes

* feat: use vitest instead of karma
This commit is contained in:
Tomas Chmelevskij 2023-01-14 22:11:37 +01:00 committed by GitHub
parent 654dca2a19
commit c086395def
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
82 changed files with 1660 additions and 1376 deletions

View file

@ -1,60 +0,0 @@
const commonjs = require("@rollup/plugin-commonjs");
const resolve = require("@rollup/plugin-node-resolve").default;
const rollupReplace = require("@rollup/plugin-replace");
const NODE_ENV = process.env.NODE_ENV || 'test';
module.exports = function(config) {
config.set({
reporters: ['tfs', 'spec','junit'],
basePath: '../',
frameworks: ['mocha', 'chai', 'sinon-chai'],
files: [
'./node_modules/jquery/dist/jquery.min.js',
'./node_modules/jquery-textcomplete/dist/jquery.textcomplete.min.js',
'./node_modules/bluebird/js/browser/bluebird.min.js',
'./node_modules/jbox/dist/jBox.min.js',
'./src/js/msp.js',
'./src/js/serial.js',
'./src/js/data_storage.js',
{ pattern: './src/js/localization.js', type: 'module', watched: false },
{ pattern: './src/js/gui.js', type: 'module', watched: false },
'./src/js/CliAutoComplete.js',
{ pattern: './src/js/tabs/cli.js', type: 'module', watched: false },
'./src/js/phones_ui.js',
'./test/**/*.js',
],
browsers: ['ChromeHeadlessNoSandbox'],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox'],
},
},
tfsReporter: {
outputDir: 'testresults',
outputFile: 'test_results.xml',
},
junitReporter: {
outputDir: 'test-results-junit',
},
singleRun: true,
preprocessors: {
'./src/js/localization.js': ['rollup'],
'./src/js/tabs/cli.js': ['rollup'],
'./src/js/gui.js': ['rollup'],
},
rollupPreprocessor: {
plugins: [
rollupReplace({
'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
}),
resolve(),
commonjs(),
],
output: {
format: 'esm',
},
},
});
};

26
test/setup.js Normal file
View file

@ -0,0 +1,26 @@
import "bluebird";
import { JSDOM } from "jsdom";
import $ from "jquery";
import { vi } from "vitest";
import jBox from "jbox";
// Note: this can go away once jquery is used as module everywhere
const { window } = new JSDOM("");
$(window);
globalThis.$ = $;
globalThis.jQuery = $;
globalThis.jBox = jBox;
Object.defineProperty(window, "matchMedia", {
writable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(), // deprecated
removeListener: vi.fn(), // deprecated
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});

View file

@ -1,279 +0,0 @@
class MockAnalytics {
EVENT_CATEGORIES = {};
sendEvent() {
// Empty
}
}
let tracking;
describe('TABS.cli', () => {
function toArrayBuffer(string) {
const bufferOut = new ArrayBuffer(string.length);
const bufView = new Uint8Array(bufferOut);
for (let i = 0; i < string.length; i++) {
bufView[i] = string.charCodeAt(i);
}
return bufferOut;
}
describe('output', () => {
const cliTab = $('<div>').addClass('tab-cli');
const cliOutput = $('<div>').addClass('wrapper');
const cliPrompt = $('<textarea name="commands">');
cliTab.append($('<div>').addClass('window').append(cliOutput));
cliTab.append(cliPrompt);
CliAutoComplete.setEnabled(false); // not testing the client-side autocomplete
before(() => {
tracking = new MockAnalytics();
$('body').append(cliTab);
CONFIGURATOR.cliValid = true;
TABS.cli.GUI.windowWrapper = cliOutput;
});
after(() => cliTab.remove());
beforeEach(() => {
cliOutput.empty();
cliPrompt.val('');
TABS.cli.cliBuffer = "";
});
it('ambiguous auto-complete results', () => {
TABS.cli.cliBuffer = 'se';
TABS.cli.read({
data: toArrayBuffer('\r\033[Kserialpassthrough\tservo\r\n# ser'),
});
// Ambigous auto-complete from firmware is preceded with an \r carriage return
// which only renders a line break on Mac
const expectedValue = GUI.operating_system !== "Windows" ?
'se<br>serialpassthrough\tservo<br>' :
'seserialpassthrough\tservo<br>';
expect(cliOutput.html()).to.equal(expectedValue);
expect(cliPrompt.val()).to.equal('ser');
});
it('unambiguous auto-complete result', () => {
TABS.cli.read({
data: toArrayBuffer('serialpassthrough'),
});
expect(cliOutput.html()).to.equal('');
expect(cliPrompt.val()).to.equal('serialpassthrough');
});
it('unambiguous auto-complete result with partial buffer', () => {
TABS.cli.cliBuffer = 'serial';
TABS.cli.read({
data: toArrayBuffer('passthrough'),
});
expect(cliOutput.html()).to.equal('');
expect(cliPrompt.val()).to.equal('serialpassthrough');
});
it("escape characters are skipped", () => {
TABS.cli.read({
data: toArrayBuffer('\033[K'),
});
expect(cliOutput.html()).to.equal('');
expect(cliPrompt.val()).to.equal('');
});
});
function triggerEnterKey(input) {
const enterKeycode = 13;
const event = $.Event("keypress");
event.which = enterKeycode;
input.trigger(event);
}
function triggerTabKey(input) {
const tabKeycode = 9;
const event = $.Event("keydown");
event.which = tabKeycode;
input.trigger(event);
}
const backspaceCode = String.fromCharCode(127);
describe('input', () => {
const content = $('<div>').attr('id', 'content');
const cliTab = $('<div>').addClass('tab-cli');
const cliPrompt = $('<textarea name="commands">');
cliTab.append(cliPrompt);
beforeEach(() => {
$('body')
.append(content);
// Stub loading of template.
sinon.stub($.fn, 'load').callsFake((file, callback) => {
content.append(cliTab);
callback();
});
sinon.stub(TABS.cli, 'send');
sinon.stub(Promise, 'reduce').callsFake((items, cb) => {
items.forEach((line, idx) => cb(0, line, idx));
});
sinon.stub(window, 'Promise').callsFake(resolve => resolve(0));
sinon.stub(GUI, 'timeout_add').withArgs('CLI_send_slowly')
.callsFake((name, cb) => {
cb();
});
TABS.cli.cliBuffer = "";
});
afterEach(() => {
content.remove();
$.fn.load.restore();
TABS.cli.send.restore();
Promise.reduce.restore();
Promise.restore();
GUI.timeout_add.restore();
});
beforeEach(() => {
cliPrompt.val('');
content.empty();
});
it('tab key triggers serial message with appended tab char', done => {
TABS.cli.initialize(() => {
cliPrompt.val('serial');
triggerTabKey(cliPrompt);
expect(TABS.cli.send).to.have.been.calledOnce;
expect(TABS.cli.send).to.have.been.calledWith('serial\t');
done();
});
});
it('second auto complete in row', done => {
TABS.cli.initialize(() => {
TABS.cli.cliBuffer = '# ser';
cliPrompt.val('seri');
triggerTabKey(cliPrompt);
expect(TABS.cli.send).to.have.been.calledOnce;
expect(TABS.cli.send).to.have.been.calledWith('i\t');
done();
});
});
it('auto-complete command with trailing space', done => {
TABS.cli.initialize(() => {
TABS.cli.cliBuffer = '# get ';
cliPrompt.val('get r');
triggerTabKey(cliPrompt);
expect(TABS.cli.send).to.have.been.calledOnce;
expect(TABS.cli.send).to.have.been.calledWith('r\t');
done();
});
});
it('auto-complete after delete characters', done => {
TABS.cli.initialize(() => {
TABS.cli.cliBuffer = '# serial';
cliPrompt.val('ser');
triggerTabKey(cliPrompt);
expect(TABS.cli.send).to.have.been.calledOnce;
expect(TABS.cli.send).to.have.been.calledWith(backspaceCode.repeat(3) + '\t');
done();
});
});
it('enter after autocomplete', done => {
TABS.cli.initialize(() => {
TABS.cli.cliBuffer = '# servo';
cliPrompt.val('servo');
triggerEnterKey(cliPrompt);
expect(TABS.cli.send).to.have.been.calledOnce;
expect(TABS.cli.send).to.have.been.calledWith('\n');
done();
});
});
it('enter after autocomplete', done => {
TABS.cli.initialize(() => {
TABS.cli.cliBuffer = '# ser';
cliPrompt.val('servo');
triggerEnterKey(cliPrompt);
expect(TABS.cli.send).to.have.been.calledOnce;
expect(TABS.cli.send).to.have.been.calledWith('vo\n');
done();
});
});
it('enter after deleting characters', done => {
TABS.cli.initialize(() => {
TABS.cli.cliBuffer = '# serial';
cliPrompt.val('ser');
triggerEnterKey(cliPrompt);
expect(TABS.cli.send).to.have.been.calledOnce;
expect(TABS.cli.send).to.have.been.calledWith(backspaceCode.repeat(3) + '\n');
done();
});
});
it('cliBuffer is cleared on startup', done => {
TABS.cli.cliBuffer = '# serial';
TABS.cli.initialize(() => {
expect(TABS.cli.cliBuffer).to.equal('');
done();
});
});
it('exit upon cleanup clears cliBuffer first', done => {
CONFIGURATOR.connectionValid = true;
TABS.cli.cliValid = true;
TABS.cli.initialize(() => {
const commandInBuffer = 'resource';
TABS.cli.cliBuffer = `# ${commandInBuffer}`;
TABS.cli.cleanup();
expect(TABS.cli.send).to.have.been.calledOnce;
expect(TABS.cli.send).to.have.been.calledWith(backspaceCode.repeat(commandInBuffer.length) + 'exit\r');
done();
});
});
});
});

303
test/tabs/cli.test.js Normal file
View file

@ -0,0 +1,303 @@
import { Promise } from "bluebird";
import {
describe,
it,
expect,
beforeAll,
afterAll,
afterEach,
beforeEach,
vi,
} from "vitest";
import CliAutoComplete from "../../src/js/CliAutoComplete";
import { cli } from "../../src/js/tabs/cli";
import "jquery-textcomplete";
import $ from "jquery";
import CONFIGURATOR from "../../src/js/data_storage";
import GUI from "../../src/js/gui";
class MockAnalytics {
sendEvent() {
// Empty
}
}
MockAnalytics.prototype.EVENT_CATEGORIES = {};
function toArrayBuffer(string) {
const bufferOut = new ArrayBuffer(string.length);
const bufView = new Uint8Array(bufferOut);
for (let i = 0; i < string.length; i++) {
bufView[i] = string.charCodeAt(i);
}
return bufferOut;
}
function triggerEnterKey(input) {
const enterKeycode = 13;
const event = $.Event("keypress");
event.which = enterKeycode;
input.trigger(event);
}
function triggerTabKey(input) {
const tabKeycode = 9;
const event = $.Event("keydown");
event.which = tabKeycode;
input.trigger(event);
}
const backspaceCode = String.fromCharCode(127);
beforeAll(() => {
window.tracking = new MockAnalytics();
});
describe("cli", () => {
describe("output", () => {
let cliTab;
let cliOutput;
let cliPrompt;
beforeAll(() => {
cliTab = $("<div>").addClass("tab-cli");
cliOutput = $("<div>").addClass("wrapper");
cliPrompt = $('<textarea name="commands">');
cliTab.append($("<div>").addClass("window").append(cliOutput));
cliTab.append(cliPrompt);
CliAutoComplete.setEnabled(false); // not testing the client-side autocomplete
$("body").append(cliTab);
CONFIGURATOR.cliValid = true;
cli.GUI.windowWrapper = cliOutput;
});
afterAll(() => {
cliTab.remove();
});
beforeEach(() => {
cliOutput.empty();
cliPrompt.val("");
cli.cliBuffer = "";
});
it("ambiguous auto-complete results", () => {
cli.cliBuffer = "se";
cli.read({
data: toArrayBuffer(
"\r\x1B[Kserialpassthrough\tservo\r\n# ser"
),
});
// Ambigous auto-complete from firmware is preceded with an \r carriage return
// which only renders a line break on Mac
const expectedValue =
GUI.operating_system !== "Windows"
? "se<br>serialpassthrough\tservo<br>"
: "seserialpassthrough\tservo<br>";
expect(cliOutput.html()).toEqual(expectedValue);
expect(cliPrompt.val()).toEqual("ser");
});
it("unambiguous auto-complete result", () => {
cli.read({
data: toArrayBuffer("serialpassthrough"),
});
expect(cliOutput.html()).toEqual("");
expect(cliPrompt.val()).toEqual("serialpassthrough");
});
it("unambiguous auto-complete result with partial buffer", () => {
cli.cliBuffer = "serial";
cli.read({
data: toArrayBuffer("passthrough"),
});
expect(cliOutput.html()).toEqual("");
expect(cliPrompt.val()).toEqual("serialpassthrough");
});
it("escape characters are skipped", () => {
cli.read({
data: toArrayBuffer("\x1B[K"),
});
expect(cliOutput.html()).toEqual("");
expect(cliPrompt.val()).toEqual("");
});
});
describe("input", () => {
let content;
let cliTab;
let cliPrompt;
beforeAll(() => {
content = $("<div>").attr("id", "content");
cliTab = $("<div>").addClass("tab-cli");
cliPrompt = $('<textarea name="commands">');
cliTab.append(cliPrompt);
$("body").append(content);
vi.spyOn($.fn, "load").mockImplementation((file, callback) => {
content.append(cliTab);
// callback();
});
vi.mock("../src/js/tabs/cli", async (importOriginal) => {
const mod = await importOriginal();
return {
...mod,
send: () => {},
};
});
vi.spyOn(Promise, "reduce").mockImplementation((items, cb) => {
items.forEach((line, idx) => cb(0, line, idx));
});
vi.spyOn(Promise, "Promise").mockResolvedValue(0);
vi.spyOn(GUI, "timeout_add").mockImplementation((name, cb) => {
cb();
});
cli.cliBuffer = "";
});
afterEach(() => {
content.remove();
vi.resetAllMocks();
});
beforeEach(() => {
cliPrompt.val("");
content.empty("");
});
it("tab key triggers serial message with appended tab char", (done) => {
cli.initialize(() => {
cliPrompt.val("serial");
triggerTabKey(cliPrompt);
expect(cli.send).toHaveBeenCalledOnce();
expect(cli.send).toHaveBeenCalledWith("serial\t");
done();
});
});
it("second auto complete in row", (done) => {
cli.initialize(() => {
cli.cliBuffer = "# ser";
cliPrompt.val("seri");
triggerTabKey(cliPrompt);
expect(cli.send).toHaveBeenCalledOnce();
expect(cli.send).toHaveBeenCalledWith("i\t");
done();
});
});
it("auto-complete command with trailing space", (done) => {
cli.initialize(() => {
cli.cliBuffer = "# get ";
cliPrompt.val("get r");
triggerTabKey(cliPrompt);
expect(cli.send).toHaveBeenCalledOnce();
expect(cli.send).toHaveBeenCalledWith("r\t");
done();
});
});
it("auto-complete after delete characters", (done) => {
cli.initialize(() => {
cli.cliBuffer = "# serial";
cliPrompt.val("ser");
triggerTabKey(cliPrompt);
expect(cli.send).toHaveBeenCalledOnce();
expect(cli.send).toHaveBeenCalledWith(
`${backspaceCode.repeat(3)}\t`
);
done();
});
});
it("enter after autocomplete", (done) => {
cli.initialize(() => {
cli.cliBuffer = "# servo";
cliPrompt.val("servo");
triggerEnterKey(cliPrompt);
expect(cli.send).toHaveBeenCalledOnce();
expect(cli.send).toHaveBeenCalledWith("\n");
done();
});
});
it("enter after autocomplete", (done) => {
cli.initialize(() => {
cli.cliBuffer = "# ser";
cliPrompt.val("servo");
triggerEnterKey(cliPrompt);
expect(cli.send).toHaveBeenCalledOnce();
expect(cli.send).toHaveBeenCalledWith("vo\n");
done();
});
});
it("enter after deleting characters", (done) => {
cli.initialize(() => {
cli.cliBuffer = "# serial";
cliPrompt.val("ser");
triggerEnterKey(cliPrompt);
expect(cli.send).toHaveBeenCalledOnce();
expect(cli.send).toHaveBeenCalledWith(
`${backspaceCode.repeat(3)}\n`
);
done();
});
});
it("cliBuffer is cleared on startup", (done) => {
cli.cliBuffer = "# serial";
cli.initialize(() => {
expect(cli.cliBuffer).to.equal("");
done();
});
});
it("exit upon cleanup clears cliBuffer first", (done) => {
CONFIGURATOR.connectionValid = true;
cli.cliValid = true;
cli.initialize(() => {
const commandInBuffer = "resource";
cli.cliBuffer = `# ${commandInBuffer}`;
cli.cleanup();
expect(cli.send).toHaveBeenCalledOnce();
expect(cli.send).toHaveBeenCalledWith(
`${backspaceCode.repeat(commandInBuffer.length)}exit\r`,
);
done();
});
});
});
});