mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 09:36:27 +00:00
hub: fix bad hub runtime state when disconnected
This fixes a bad hub runtime state when the hub disconnects during a download. This caused the run button in the UI to be enabled when the hub was disconnected. Issue: https://github.com/pybricks/support/issues/378
This commit is contained in:
@@ -0,0 +1,141 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2021 The Pybricks Authors
|
||||||
|
|
||||||
|
import { Action } from '../actions';
|
||||||
|
import { statusReportEvent } from '../ble-pybricks-service/actions';
|
||||||
|
import { Status, statusToFlag } from '../ble-pybricks-service/protocol';
|
||||||
|
import { didDisconnect } from '../ble/actions';
|
||||||
|
import {
|
||||||
|
didFailToFinishDownload,
|
||||||
|
didFinishDownload,
|
||||||
|
didProgressDownload,
|
||||||
|
didStartDownload,
|
||||||
|
} from './actions';
|
||||||
|
import reducers, { HubRuntimeState } from './reducers';
|
||||||
|
|
||||||
|
type State = ReturnType<typeof reducers>;
|
||||||
|
|
||||||
|
test('initial state', () => {
|
||||||
|
expect(reducers(undefined, {} as Action)).toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"downloadProgress": null,
|
||||||
|
"runtime": "hub.runtime.disconnected",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('runtime', () => {
|
||||||
|
test.each(Object.values(HubRuntimeState))('didDisconnect', (startingState) => {
|
||||||
|
// all states are overridden by disconnect
|
||||||
|
expect(
|
||||||
|
reducers({ runtime: startingState } as State, didDisconnect()).runtime,
|
||||||
|
).toBe(HubRuntimeState.Disconnected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('didStartDownload', () => {
|
||||||
|
// download ignored if disconnected
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Disconnected } as State,
|
||||||
|
didStartDownload(),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Disconnected);
|
||||||
|
expect(
|
||||||
|
reducers({ runtime: HubRuntimeState.Idle } as State, didStartDownload())
|
||||||
|
.runtime,
|
||||||
|
).toBe(HubRuntimeState.Loading);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('didProgressDownload', () => {
|
||||||
|
// download ignored if disconnected
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Disconnected } as State,
|
||||||
|
didProgressDownload(0),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Disconnected);
|
||||||
|
|
||||||
|
// this shouldn't have any effect
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Loading } as State,
|
||||||
|
didProgressDownload(0),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Loading);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('didFinishDownload', () => {
|
||||||
|
// download ignored if disconnected
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Disconnected } as State,
|
||||||
|
didFinishDownload(),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Disconnected);
|
||||||
|
|
||||||
|
// normal operation
|
||||||
|
expect(
|
||||||
|
reducers({ runtime: HubRuntimeState.Loading } as State, didFinishDownload())
|
||||||
|
.runtime,
|
||||||
|
).toBe(HubRuntimeState.Loaded);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('didFinishDownload', () => {
|
||||||
|
// download ignored if disconnected
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Disconnected } as State,
|
||||||
|
didFailToFinishDownload(),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Disconnected);
|
||||||
|
|
||||||
|
// normal operation for error path
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Loading } as State,
|
||||||
|
didFailToFinishDownload(),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Idle);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('statusReportEvent', () => {
|
||||||
|
// don't ever expect this to happen in practice since we can't receive
|
||||||
|
// updates while disconnected
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Disconnected } as State,
|
||||||
|
statusReportEvent(statusToFlag(Status.UserProgramRunning)),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Disconnected);
|
||||||
|
|
||||||
|
// status update ignored while download not finished
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Loading } as State,
|
||||||
|
statusReportEvent(statusToFlag(Status.UserProgramRunning)),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Loading);
|
||||||
|
|
||||||
|
// normal operation - user program started
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Loaded } as State,
|
||||||
|
statusReportEvent(statusToFlag(Status.UserProgramRunning)),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Running);
|
||||||
|
|
||||||
|
// really short program run finished before receiving download finished
|
||||||
|
expect(
|
||||||
|
reducers({ runtime: HubRuntimeState.Loaded } as State, statusReportEvent(0))
|
||||||
|
.runtime,
|
||||||
|
).toBe(HubRuntimeState.Idle);
|
||||||
|
|
||||||
|
// normal operation - user program stopped
|
||||||
|
expect(
|
||||||
|
reducers(
|
||||||
|
{ runtime: HubRuntimeState.Running } as State,
|
||||||
|
statusReportEvent(0),
|
||||||
|
).runtime,
|
||||||
|
).toBe(HubRuntimeState.Idle);
|
||||||
|
});
|
||||||
|
});
|
||||||
+23
-6
@@ -46,21 +46,38 @@ const runtime: Reducer<HubRuntimeState, Action> = (
|
|||||||
case BleDeviceActionType.DidDisconnect:
|
case BleDeviceActionType.DidDisconnect:
|
||||||
return HubRuntimeState.Disconnected;
|
return HubRuntimeState.Disconnected;
|
||||||
case HubActionType.DidStartDownload:
|
case HubActionType.DidStartDownload:
|
||||||
|
// disconnected overrides download
|
||||||
|
if (state === HubRuntimeState.Disconnected) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
return HubRuntimeState.Loading;
|
return HubRuntimeState.Loading;
|
||||||
case HubActionType.DidFinishDownload:
|
case HubActionType.DidFinishDownload:
|
||||||
|
// disconnected overrides download
|
||||||
|
if (state === HubRuntimeState.Disconnected) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
return HubRuntimeState.Loaded;
|
return HubRuntimeState.Loaded;
|
||||||
case HubActionType.DidFailToFinishDownload:
|
case HubActionType.DidFailToFinishDownload:
|
||||||
|
// disconnected overrides download
|
||||||
|
if (state === HubRuntimeState.Disconnected) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
return HubRuntimeState.Idle;
|
return HubRuntimeState.Idle;
|
||||||
case BlePybricksServiceEventActionType.StatusReport:
|
case BlePybricksServiceEventActionType.StatusReport:
|
||||||
// The loading state is determined solely by the IDE, so we can't
|
// The loading state is determined solely by the IDE, so we can't
|
||||||
// let the hub status interfere with it.
|
// let the hub status interfere with it.
|
||||||
if (state !== HubRuntimeState.Loading) {
|
if (
|
||||||
if (action.statusFlags & statusToFlag(Status.UserProgramRunning)) {
|
state === HubRuntimeState.Disconnected ||
|
||||||
return HubRuntimeState.Running;
|
state === HubRuntimeState.Loading
|
||||||
}
|
) {
|
||||||
return HubRuntimeState.Idle;
|
return state;
|
||||||
}
|
}
|
||||||
return state;
|
|
||||||
|
if (action.statusFlags & statusToFlag(Status.UserProgramRunning)) {
|
||||||
|
return HubRuntimeState.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HubRuntimeState.Idle;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user