Files
meshtastic-web/packages/transport-web-serial/src/transport.test.ts
T
Dan DitomasoandGitHub c729d3b25e fix: resolve lint warnings/errors and apply formatting (#1024)
* fix: resolve lint warnings/errors and apply formatting

Fix 10 oxlint issues (2 errors, 8 warnings):
- Remove unused catch parameters in Security.tsx and ImportDialog.tsx
- Remove stray expression in Generator.tsx
- Add eslint-disable for debounced useCallback in FilterControl.tsx
- Remove unnecessary deps (resolveDB, store) in bindStoreToDevice.ts
- Prefix unused variant param in AppSidebar.tsx
- Memoize tabs arrays in DeviceConfig, RadioConfig, ModuleConfig
- Fix channels type in RadioConfig TabItem

Also applies oxfmt formatting across all files.

* updating lock file

* update protobuf package

* fix: regenerate pnpm-lock.yaml and exclude jsr protobufs from minimumReleaseAge

* prevented http card from always being polled

* updated pnpm config fiile

* updated actions

* removed biome config and lint system leftovers

* updating protobuf package
2026-03-11 21:54:25 -04:00

232 lines
6.2 KiB
TypeScript

import { Types, Utils } from "@meshtastic/core";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { runTransportContract } from "../../../tests/utils/transportContract.ts";
import { TransportWebSerial } from "./transport.ts";
function stubCoreTransforms() {
const toDevice = () =>
new TransformStream<Uint8Array, Uint8Array>({
transform(chunk, controller) {
controller.enqueue(chunk);
},
});
// maps raw bytes -> DeviceOutput.packet
const fromDeviceFactory = () =>
new TransformStream<Uint8Array, Types.DeviceOutput>({
transform(chunk, controller) {
controller.enqueue({ type: "packet", data: chunk });
},
});
const transform = Utils.toDeviceStream;
const restoreTo = vi
.spyOn(Utils, "toDeviceStream", "get")
.mockReturnValue(toDevice as unknown as typeof transform);
const restoreFrom = vi
.spyOn(Utils, "fromDeviceStream")
.mockImplementation(
() => fromDeviceFactory() as unknown as TransformStream<Uint8Array, Types.DeviceOutput>,
);
return {
restore: () => {
restoreTo.mockRestore();
restoreFrom.mockRestore();
},
};
}
function stubNavigatorSerial() {
type SerialDisconnectHandler = (ev: { port?: any }) => void;
const handlers = new Set<SerialDisconnectHandler>();
const serialStub = {
addEventListener: (type: string, handler: EventListenerOrEventListenerObject) => {
if (type === "disconnect") {
handlers.add(handler as any as SerialDisconnectHandler);
}
},
removeEventListener: (type: string, handler: EventListenerOrEventListenerObject) => {
if (type === "disconnect") {
handlers.delete(handler as any as SerialDisconnectHandler);
}
},
dispatchDisconnect(port: any) {
for (const h of handlers) {
h({ port });
}
},
requestPort: vi.fn(async () => new FakeSerialPort()),
};
const nav: any = (globalThis as any).navigator ?? {};
const hadNavigator = !!(globalThis as any).navigator;
const originalSerial = nav.serial;
if (!hadNavigator) {
Object.defineProperty(globalThis as any, "navigator", {
value: nav,
configurable: true,
writable: false,
});
}
Object.defineProperty(nav, "serial", {
value: serialStub,
configurable: true,
enumerable: true,
writable: true,
});
return {
serialStub,
restore: () => {
if (hadNavigator) {
if (originalSerial === undefined) {
delete (globalThis as any).navigator.serial;
} else {
Object.defineProperty((globalThis as any).navigator, "serial", {
value: originalSerial,
configurable: true,
enumerable: true,
writable: true,
});
}
} else {
delete (globalThis as any).navigator;
}
},
};
}
class FakeSerialPort {
readable: ReadableStream<Uint8Array>;
writable: WritableStream<Uint8Array>;
lastWritten?: Uint8Array;
private _readController!: ReadableStreamDefaultController<Uint8Array>;
constructor() {
this.readable = new ReadableStream<Uint8Array>({
start: (controller) => {
this._readController = controller;
},
});
this.writable = new WritableStream<Uint8Array>({
write: async (chunk) => {
this.lastWritten = chunk;
},
});
}
open(_options?: { baudRate?: number }): Promise<void> {
return Promise.resolve();
}
close(): Promise<void> {
try {
this._readController.close();
} catch {}
return Promise.resolve();
}
pushIncoming(bytes: Uint8Array) {
this._readController.enqueue(bytes);
}
}
describe("TransportWebSerial (contract)", () => {
let transforms: { restore(): void } | undefined;
let navSerial: { serialStub: any; restore(): void } | undefined;
beforeEach(() => {
transforms = stubCoreTransforms();
navSerial = stubNavigatorSerial();
});
afterEach(() => {
transforms?.restore();
navSerial?.restore();
vi.restoreAllMocks();
});
runTransportContract({
name: "TransportWebSerial",
setup: () => {},
teardown: () => {},
create: async () => {
const fake = new FakeSerialPort();
const transport = await TransportWebSerial.createFromPort(fake as any);
(globalThis as any).__ws = { fake, serial: navSerial!.serialStub };
await Promise.resolve();
return transport;
},
pushIncoming: async (bytes) => {
(globalThis as any).__ws.fake.pushIncoming(bytes);
await Promise.resolve();
},
assertLastWritten: (bytes) => {
expect((globalThis as any).__ws.fake.lastWritten).toEqual(bytes);
},
triggerDisconnect: async () => {
(globalThis as any).__ws.serial.dispatchDisconnect((globalThis as any).__ws.fake);
await Promise.resolve();
},
});
});
describe("TransportWebSerial (extras)", () => {
let transforms: { restore(): void } | undefined;
let navSerial: { serialStub: any; restore(): void } | undefined;
beforeEach(() => {
transforms = stubCoreTransforms();
navSerial = stubNavigatorSerial();
});
afterEach(() => {
transforms?.restore();
navSerial?.restore();
vi.restoreAllMocks();
});
it("emits DeviceDisconnected('serial-disconnected') on OS disconnect event", async () => {
const fake = new FakeSerialPort();
const transport = await TransportWebSerial.createFromPort(fake as any);
(globalThis as any).__ws = { fake, serial: navSerial!.serialStub };
const reader = transport.fromDevice.getReader();
// drain statuses until connected
for (let i = 0; i < 3; i++) {
const { value } = await reader.read();
if (!value || value.type !== "status") {
break;
}
if (value.data.status === Types.DeviceStatusEnum.DeviceConnected) {
break;
}
}
// fire OS-level disconnect
navSerial!.serialStub.dispatchDisconnect(fake as any);
await Promise.resolve();
let saw = false;
for (let i = 0; i < 6; i++) {
const { value } = await reader.read();
if (value?.type === "status" && value.data.reason === "serial-disconnected") {
saw = true;
break;
}
}
expect(saw).toBe(true);
reader.releaseLock();
await transport.disconnect();
});
});