fix: Stop a released PTZ key ending another held key's movement (#2675)
- Closes: #2668
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import type { ViewContext } from 'view';
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import { PTZDigitalAction } from '../../../../src/card-controller/actions/actions/ptz-digital';
|
||||
import type { Action } from '../../../../src/card-controller/actions/types';
|
||||
import type { CardController } from '../../../../src/card-controller/controller';
|
||||
import type {
|
||||
PartialZoomSettings,
|
||||
@@ -494,7 +496,7 @@ describe('should handle ptz digital action', () => {
|
||||
expect(api.getViewManager().setViewWithModifiers).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should stop movement started by two concurrent starts', async () => {
|
||||
it('should only move for the latest of two concurrent starts', async () => {
|
||||
const api = createCardAPI();
|
||||
const context = {};
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
@@ -512,6 +514,17 @@ describe('should handle ptz digital action', () => {
|
||||
createStartAction('up').execute(api),
|
||||
]);
|
||||
|
||||
// The 'up' start replaces the 'left' start before the 'left' start has
|
||||
// taken its first step, so only the 'up' start moves.
|
||||
expect(api.getViewManager().setViewWithModifiers).toHaveBeenCalledTimes(1);
|
||||
expect(getRequestedZoom(api)).toEqual({
|
||||
...defaultSettings,
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 45,
|
||||
},
|
||||
});
|
||||
|
||||
const stopAction = new PTZDigitalAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz_digital',
|
||||
@@ -519,12 +532,104 @@ describe('should handle ptz digital action', () => {
|
||||
});
|
||||
await stopAction.execute(api);
|
||||
|
||||
// One step per start, and none after the stop.
|
||||
expect(api.getViewManager().setViewWithModifiers).toHaveBeenCalledTimes(2);
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewWithModifiers).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should continue movement when a stop for a different movement arrives', async () => {
|
||||
const api = createCardAPI();
|
||||
const context = {};
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
|
||||
const leftAction = new PTZDigitalAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz_digital',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'start',
|
||||
});
|
||||
await leftAction.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithModifiers).toHaveBeenCalledTimes(1);
|
||||
expect(getRequestedZoom(api)).toEqual({
|
||||
...defaultSettings,
|
||||
pan: {
|
||||
x: 45,
|
||||
y: 50,
|
||||
},
|
||||
});
|
||||
|
||||
// A stop for a movement that is not in progress leaves the 'left'
|
||||
// movement running.
|
||||
const stopUpAction = new PTZDigitalAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz_digital',
|
||||
ptz_action: 'up',
|
||||
ptz_phase: 'stop',
|
||||
});
|
||||
await stopUpAction.execute(api);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewWithModifiers).toHaveBeenCalledTimes(2);
|
||||
|
||||
const stopLeftAction = new PTZDigitalAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz_digital',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'stop',
|
||||
});
|
||||
await stopLeftAction.execute(api);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewWithModifiers).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not stop an in-progress action that is not a digital PTZ action', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
|
||||
const incumbent = mock<Action>();
|
||||
const context = { ptzDigital: { camera: { inProgressAction: incumbent } } };
|
||||
|
||||
const stopAction = new PTZDigitalAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz_digital',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'stop',
|
||||
});
|
||||
await stopAction.execute(api);
|
||||
|
||||
expect(incumbent.stop).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not repeat steps when stopped during the first step', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
|
||||
const action = new PTZDigitalAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz_digital',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'start',
|
||||
},
|
||||
);
|
||||
|
||||
// The stop arrives while the first step is being taken.
|
||||
vi.mocked(api.getViewManager().setViewWithModifiers).mockImplementationOnce(() => {
|
||||
action.stop();
|
||||
});
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithModifiers).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewWithModifiers).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should continue movement when a start is concurrent with a stop', async () => {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import { Capabilities } from '../../../../src/camera-manager/capabilities';
|
||||
import { PTZAction } from '../../../../src/card-controller/actions/actions/ptz';
|
||||
import type { Action } from '../../../../src/card-controller/actions/types';
|
||||
import { PTZMovementType } from '../../../../src/types';
|
||||
import { createCameraManager, createStore } from '../../../camera-manager/test-utils';
|
||||
import { createCameraConfig } from '../../../config/test-utils';
|
||||
@@ -612,7 +614,7 @@ describe('should handle ptz action', () => {
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should stop movement started by two concurrent starts', async () => {
|
||||
it('should only move for the latest of two concurrent starts', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
@@ -644,6 +646,106 @@ describe('should handle ptz action', () => {
|
||||
createStartAction('up').execute(api),
|
||||
]);
|
||||
|
||||
// The 'up' start replaces the 'left' start before the 'left' start has
|
||||
// made its first move, so only the 'up' start moves.
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(1);
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
|
||||
'camera.office',
|
||||
'up',
|
||||
{ preset: undefined },
|
||||
);
|
||||
|
||||
const stopAction = new PTZAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz',
|
||||
ptz_action: 'up',
|
||||
ptz_phase: 'stop',
|
||||
});
|
||||
await stopAction.execute(api);
|
||||
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should continue movement when a stop for a different movement arrives', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: [PTZMovementType.Relative],
|
||||
up: [PTZMovementType.Relative],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const context = {};
|
||||
await new PTZAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'start',
|
||||
}).execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(1);
|
||||
|
||||
// A stop for a movement that is not in progress leaves the 'left'
|
||||
// movement running.
|
||||
await new PTZAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz',
|
||||
ptz_action: 'up',
|
||||
ptz_phase: 'stop',
|
||||
}).execute(api);
|
||||
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(2);
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenLastCalledWith(
|
||||
'camera.office',
|
||||
'left',
|
||||
{ preset: undefined },
|
||||
);
|
||||
|
||||
await new PTZAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'stop',
|
||||
}).execute(api);
|
||||
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not stop an in-progress action that is not a PTZ action', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: [PTZMovementType.Relative],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const incumbent = mock<Action>();
|
||||
const context = { ptz: { 'camera.office': { inProgressAction: incumbent } } };
|
||||
|
||||
const stopAction = new PTZAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'ptz',
|
||||
@@ -652,12 +754,7 @@ describe('should handle ptz action', () => {
|
||||
});
|
||||
await stopAction.execute(api);
|
||||
|
||||
// One move per start, and none after the stop.
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(2);
|
||||
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(2);
|
||||
expect(incumbent.stop).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should continue movement when a start is concurrent with a stop', async () => {
|
||||
|
||||
Reference in New Issue
Block a user