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:
David Lechner
2021-07-01 11:58:04 -05:00
parent 99166abb96
commit 7a5aa75894
2 changed files with 164 additions and 6 deletions
+141
View File
@@ -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
View File
@@ -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;
} }