Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
87806bb
Add new-page E2E tests
microbit-grace Oct 11, 2024
f213876
Prettier
microbit-grace Oct 25, 2024
149ba99
Setup cookies
microbit-grace Oct 25, 2024
2caa80b
Remove WIP data samples test
microbit-grace Oct 25, 2024
9c13361
Add workflow 10 mins build timeout
microbit-grace Oct 25, 2024
44be331
WIP mock connections
microbit-grace Jan 22, 2025
3cde798
Merge branch 'e2e' of https://github.com/microbit-foundation/ml-train…
microbit-grace Jan 22, 2025
ea855be
Fix start session and resume session tests
microbit-grace Jan 22, 2025
3a8e616
Update to the latest microbit-connection library
microbit-grace Jan 23, 2025
2c97803
Happy bluetooth connection flow
microbit-grace Jan 23, 2025
bd67da5
Bluetooth no device selected for flashing test
microbit-grace Jan 23, 2025
cfae3f5
Comment out E2E tests temporarily
microbit-grace Jan 23, 2025
274c4c2
Bluetooth no device selected for connecting test
microbit-grace Jan 23, 2025
c1bf6a4
Add radio bridge happy flow test
microbit-grace Jan 23, 2025
52893a5
Radio no device selected for flashing
microbit-grace Jan 24, 2025
15c63e3
Use cookie mechanism to activate isMockDeviceMode
microbit-grace Jan 24, 2025
3c5c44a
Fix minor lint
microbit-grace Jan 24, 2025
d353173
Make connection dialog tests more reliable by removing loading steps
microbit-grace Jan 24, 2025
49c48f3
Uncomment build.yml e2e workflow
microbit-grace Jan 24, 2025
ad89519
Add unused methods into mock usb & bluetooth
microbit-grace Jan 24, 2025
b5c81bb
Merge branch 'main' of https://github.com/microbit-foundation/ml-trai…
microbit-grace Feb 14, 2025
e0f1c60
Adjust to fit changes in microbit-connection lib
microbit-grace Feb 14, 2025
d70f96a
Update mocks following changes in microbit-connection
microbit-grace Feb 14, 2025
b1fbba4
Add test model test
microbit-grace Feb 17, 2025
a61a634
Remove not needed type casting
microbit-grace Feb 17, 2025
db61b1d
Empty commit because CloudFlare is taking an unusually long time?
microbit-grace Feb 17, 2025
c928fd8
Enter bluetooth pattern via input fields instead
microbit-grace Feb 17, 2025
5c2100c
More straight line test code. Removed some awkward tests in favour of…
microbit-grace Feb 17, 2025
0278cf8
Upgrade microbit-connection
microbit-grace Feb 17, 2025
360cc63
Organise imports
microbit-grace Feb 17, 2025
fa911b2
Fix build
microbit-grace Feb 17, 2025
8e9c791
Attempt to fix E2E test.
microbit-grace Feb 18, 2025
1f28286
Inline connection happy flow dialog steps and reinstate other tests
microbit-grace Feb 19, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ concurrency:

jobs:
build:
timeout-minutes: 10
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@microbit/makecode-embed": "^0.1.0",
"@microbit/microbit-connection": "^0.0.0-alpha.33",
"@microbit/microbit-connection": "^0.0.0-alpha.35",
"@microbit/ml-header-generator": "^0.4.3",
"@microbit/smoothie": "^1.37.0-microbit.2",
"@tensorflow/tfjs": "^4.20.0",
Expand Down
34 changes: 29 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ChakraProvider, useToast } from "@chakra-ui/react";
import { MakeCodeFrameDriver } from "@microbit/makecode-embed/react";
import {
createRadioBridgeConnection,
createWebBluetoothConnection,
createWebUSBConnection,
} from "@microbit/microbit-connection";
import React, { ReactNode, useEffect, useMemo, useRef } from "react";
import { useIntl } from "react-intl";
import {
createBrowserRouter,
Outlet,
RouterProvider,
ScrollRestoration,
createBrowserRouter,
useNavigate,
} from "react-router-dom";
import "theme-package/fonts/fonts.css";
import { BufferedDataProvider } from "./buffered-data-hooks";
import EditCodeDialog from "./components/EditCodeDialog";
import ErrorBoundary from "./components/ErrorBoundary";
Expand All @@ -25,9 +31,14 @@ import { ConnectProvider } from "./connect-actions-hooks";
import { ConnectStatusProvider } from "./connect-status-hooks";
import { ConnectionStageProvider } from "./connection-stage-hooks";
import { deployment, useDeployment } from "./deployment";
import { MockWebBluetoothConnection } from "./device/mockBluetooth";
import { MockRadioBridgeConnection } from "./device/mockRadioBridge";
import { MockWebUSBConnection } from "./device/mockUsb";
import { ProjectProvider } from "./hooks/project-hooks";
import { LoggingProvider } from "./logging/logging-hooks";
import { hasMakeCodeMlExtension } from "./makecode/utils";
import TranslationProvider from "./messages/TranslationProvider";
import { PostImportDialogState } from "./model";
import CodePage from "./pages/CodePage";
import DataSamplesPage from "./pages/DataSamplesPage";
import HomePage from "./pages/HomePage";
Expand All @@ -43,16 +54,29 @@ import {
createNewPageUrl,
createTestingModelPageUrl,
} from "./urls";
import { hasMakeCodeMlExtension } from "./makecode/utils";
import { PostImportDialogState } from "./model";
import "theme-package/fonts/fonts.css";

export interface ProviderLayoutProps {
children: ReactNode;
}

const isMockDeviceMode = () =>
// We use a cookie set from the e2e tests. Avoids having separate test and live builds.
Boolean(
document.cookie.split("; ").find((row) => row.startsWith("mockDevice="))
);

const logging = deployment.logging;

const usb = isMockDeviceMode()
? new MockWebUSBConnection()
: createWebUSBConnection({ logging });
const bluetooth = isMockDeviceMode()
? new MockWebBluetoothConnection()
: createWebBluetoothConnection({ logging });
const radioBridge = isMockDeviceMode()
? new MockRadioBridgeConnection(usb)
: createRadioBridgeConnection(usb, { logging });

const Providers = ({ children }: ProviderLayoutProps) => {
const deployment = useDeployment();
const { ConsentProvider } = deployment.compliance;
Expand All @@ -63,7 +87,7 @@ const Providers = ({ children }: ProviderLayoutProps) => {
<ConsentProvider>
<TranslationProvider>
<ConnectStatusProvider>
<ConnectProvider>
<ConnectProvider {...{ usb, bluetooth, radioBridge }}>
<BufferedDataProvider>
<ConnectionStageProvider>
{children}
Expand Down
18 changes: 9 additions & 9 deletions src/connect-actions-hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ const ConnectContext = createContext<ConnectContextValue | null>(null);

interface ConnectProviderProps {
children: ReactNode;
usb: MicrobitWebUSBConnection;
bluetooth: MicrobitWebBluetoothConnection;
radioBridge: MicrobitRadioBridgeConnection;
}

export const ConnectProvider = ({ children }: ConnectProviderProps) => {
const usb = useRef(new MicrobitWebUSBConnection()).current;
const logging = useRef(useLogging()).current;
const bluetooth = useRef(
new MicrobitWebBluetoothConnection({ logging })
).current;
const radioBridge = useRef(
new MicrobitRadioBridgeConnection(usb, { logging })
).current;
export const ConnectProvider = ({
children,
usb,
bluetooth,
radioBridge,
}: ConnectProviderProps) => {
const [isInitialized, setIsInitialized] = useState<boolean>(false);

useEffect(() => {
Expand Down
81 changes: 81 additions & 0 deletions src/device/mockBluetooth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
BoardVersion,
ConnectionStatus,
ConnectionStatusEvent,
DeviceConnectionEventMap,
LedMatrix,
MicrobitWebBluetoothConnection,
ServiceConnectionEventMap,
TypedEventTarget,
} from "@microbit/microbit-connection";

export class MockWebBluetoothConnection
extends TypedEventTarget<DeviceConnectionEventMap & ServiceConnectionEventMap>
implements MicrobitWebBluetoothConnection
{
status: ConnectionStatus = ConnectionStatus.NO_AUTHORIZED_DEVICE;
private connectResults: ConnectionStatus[] = [];

constructor() {
super();
// Make globally available to allow e2e tests to configure interactions.
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
(window as any).mockBluetooth = this;
}
private setStatus(newStatus: ConnectionStatus) {
this.status = newStatus;
this.dispatchTypedEvent("status", new ConnectionStatusEvent(newStatus));
}

async initialize(): Promise<void> {
this.setStatus(ConnectionStatus.NO_AUTHORIZED_DEVICE);
await new Promise((resolve) => setTimeout(resolve, 100));
}

dispose(): void {}

mockConnectResults(results: ConnectionStatus[]) {
this.connectResults = results;
}

async connect(): Promise<ConnectionStatus> {
if (this.connectResults.length > 0) {
for (const result of this.connectResults) {
this.setStatus(result);
await new Promise((resolve) => setTimeout(resolve, 100));
}
return this.status;
}
this.setStatus(ConnectionStatus.CONNECTING);
await new Promise((resolve) => setTimeout(resolve, 100));
this.setStatus(ConnectionStatus.CONNECTED);
return this.status;
}

getBoardVersion(): BoardVersion | undefined {
return "V2";
}

async disconnect(): Promise<void> {}
async serialWrite(_data: string): Promise<void> {}
setNameFilter(_name: string): void {}

clearDevice(): void {
this.setStatus(ConnectionStatus.NO_AUTHORIZED_DEVICE);
}

async getAccelerometerData(): Promise<undefined> {}
async getAccelerometerPeriod(): Promise<undefined> {}
async setAccelerometerPeriod(_value: number): Promise<void> {}
async setLedText(_text: string): Promise<void> {}
async getLedScrollingDelay(): Promise<undefined> {}
async setLedScrollingDelay(_delayInMillis: number): Promise<void> {}
async getLedMatrix(): Promise<undefined> {}
async setLedMatrix(_matrix: LedMatrix): Promise<void> {}
async getMagnetometerData(): Promise<undefined> {}
async getMagnetometerBearing(): Promise<undefined> {}
async getMagnetometerPeriod(): Promise<undefined> {}
async setMagnetometerPeriod(_value: number): Promise<void> {}
async triggerMagnetometerCalibration(): Promise<void> {}
async uartWrite(_data: Uint8Array): Promise<void> {}
}
57 changes: 57 additions & 0 deletions src/device/mockRadioBridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
BoardVersion,
ConnectionStatus,
ConnectionStatusEvent,
DeviceConnectionEventMap,
MicrobitRadioBridgeConnection,
MicrobitWebUSBConnection,
ServiceConnectionEventMap,
TypedEventTarget,
} from "@microbit/microbit-connection";

export class MockRadioBridgeConnection
extends TypedEventTarget<DeviceConnectionEventMap & ServiceConnectionEventMap>
implements MicrobitRadioBridgeConnection
{
status: ConnectionStatus;

constructor(private delegate: MicrobitWebUSBConnection) {
super();
this.status = this.statusFromDelegate();
}

private statusFromDelegate(): ConnectionStatus {
return this.delegate.status == ConnectionStatus.CONNECTED
? ConnectionStatus.DISCONNECTED
: this.delegate.status;
}

async initialize(): Promise<void> {}
dispose(): void {}
setRemoteDeviceId(_deviceId: number): void {}
async disconnect(): Promise<void> {}

private setStatus(newStatus: ConnectionStatus) {
this.status = newStatus;
this.dispatchTypedEvent("status", new ConnectionStatusEvent(newStatus));
}

async connect(): Promise<ConnectionStatus> {
await this.delegate.connect();
this.setStatus(ConnectionStatus.CONNECTING);
await new Promise((resolve) => setTimeout(resolve, 100));
this.setStatus(ConnectionStatus.CONNECTED);
return this.status;
}

getBoardVersion(): BoardVersion | undefined {
return this.delegate.getBoardVersion();
}

serialWrite(data: string): Promise<void> {
return this.delegate.serialWrite(data);
}
async clearDevice(): Promise<void> {
await this.delegate.clearDevice();
}
}
89 changes: 89 additions & 0 deletions src/device/mockUsb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
AfterRequestDevice,
BeforeRequestDevice,
BoardVersion,
ConnectionStatus,
ConnectionStatusEvent,
DeviceConnectionEventMap,
FlashDataSource,
FlashEvent,
FlashOptions,
MicrobitWebUSBConnection,
SerialConnectionEventMap,
TypedEventTarget,
} from "@microbit/microbit-connection";

/**
* A mock USB connection used during end-to-end testing.
*/
export class MockWebUSBConnection
extends TypedEventTarget<DeviceConnectionEventMap & SerialConnectionEventMap>
implements MicrobitWebUSBConnection
{
status: ConnectionStatus = ConnectionStatus.NO_AUTHORIZED_DEVICE;

private fakeDeviceId: number | undefined = 123;

constructor() {
super();
// Make globally available to allow e2e tests to configure interactions.
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
(window as any).mockUsb = this;
this.fakeDeviceId = Math.round(Math.random() * 1000);
}
async initialize(): Promise<void> {}
dispose(): void {}

mockDeviceId(deviceId: number | undefined) {
this.fakeDeviceId = deviceId;
}

private setStatus(newStatus: ConnectionStatus) {
this.status = newStatus;
this.dispatchTypedEvent("status", new ConnectionStatusEvent(newStatus));
}

async connect(): Promise<ConnectionStatus> {
this.dispatchTypedEvent("beforerequestdevice", new BeforeRequestDevice());
await new Promise((resolve) => setTimeout(resolve, 100));
this.dispatchTypedEvent("afterrequestdevice", new AfterRequestDevice());
await new Promise((resolve) => setTimeout(resolve, 100));
this.setStatus(ConnectionStatus.CONNECTED);
return this.status;
}

getDeviceId(): number | undefined {
return this.fakeDeviceId;
}

getBoardVersion(): BoardVersion | undefined {
return "V2";
}

async flash(
_dataSource: FlashDataSource,
options: FlashOptions
): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, 100));
options.progress(50, options.partial);
await new Promise((resolve) => setTimeout(resolve, 100));
options.progress(undefined, options.partial);
this.dispatchTypedEvent("flash", new FlashEvent());
}

async disconnect(): Promise<void> {}
async serialWrite(_data: string): Promise<void> {}

clearDevice(): void {
this.fakeDeviceId = undefined;
this.setStatus(ConnectionStatus.NO_AUTHORIZED_DEVICE);
}

setRequestDeviceExclusionFilters(
_exclusionFilters: USBDeviceFilter[]
): void {}
getDevice(): USBDevice | undefined {
return undefined;
}
async softwareReset(): Promise<void> {}
}
Loading