refactor: Unify substream actions into substream_{on,off} (#2497)
## Summary
- Collapse `live_substream_{on,off,select}` into a unified
`substream_{on,off}` pair, symmetric with `call_{start,end}`.
- Rename the `camera` field on the former `live_substream_select` to
`stream` (it always was a stream ID).
- Add optional `camera` field to both new actions for targeting a
non-selected base camera.
- YAML configs are migrated automatically; URL bookmarks must be updated
by hand.
## Migration
### Cycling between camera and substream (toggle button)
Before:
```yaml
tap_action:
action: custom:advanced-camera-card-action
advanced_camera_card_action: live_substream_on
```
After:
```yaml
tap_action:
action: custom:advanced-camera-card-action
advanced_camera_card_action: substream_on
```
### Selecting a specific substream
Before:
```yaml
tap_action:
action: custom:advanced-camera-card-action
advanced_camera_card_action: live_substream_select
camera: camera.front_door_hd
```
After:
```yaml
tap_action:
action: custom:advanced-camera-card-action
advanced_camera_card_action: substream_on
stream: camera.front_door_hd
```
### Turning the substream off
Before:
```yaml
tap_action:
action: custom:advanced-camera-card-action
advanced_camera_card_action: live_substream_off
```
After:
```yaml
tap_action:
action: custom:advanced-camera-card-action
advanced_camera_card_action: substream_off
```
### URL querystrings (manual update required)
| Before | After |
| --- | --- |
|
`?advanced-camera-card-action.live_substream_select=camera.front_door_hd`
| `?advanced-camera-card-action.substream_on=camera.front_door_hd` |
This commit is contained in:
committed by
dermotduffy
parent
15e335a647
commit
f18e4cd4b8
@@ -1,21 +1,58 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { SubstreamOffAction } from '../../../../src/card-controller/actions/actions/substream-off';
|
||||
import { SubstreamViewModifier } from '../../../../src/card-controller/view/modifiers/substream';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
import { applyViewModifiers } from '../../../../src/card-controller/view/modifiers';
|
||||
import { createSubstreamOffAction } from '../../../../src/utils/action';
|
||||
import { View } from '../../../../src/view/view';
|
||||
import { createCardAPI, createView } from '../../../test-utils';
|
||||
|
||||
it('should handle live_substream_off action', async () => {
|
||||
// Runs the off-action for `view` and applies the modifier it produces.
|
||||
const applySubstreamOff = async (
|
||||
view: View,
|
||||
options?: { camera?: string },
|
||||
): Promise<void> => {
|
||||
const api = createCardAPI();
|
||||
const action = new SubstreamOffAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'live_substream_off',
|
||||
},
|
||||
);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
|
||||
await action.execute(api);
|
||||
await new SubstreamOffAction({}, createSubstreamOffAction(options)).execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
const params = vi.mocked(api.getViewManager().setViewByParameters).mock.calls[0]?.[0];
|
||||
applyViewModifiers(view, params?.modifiers);
|
||||
};
|
||||
|
||||
describe('SubstreamOffAction', () => {
|
||||
it('should clear the selected camera override', async () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera.office',
|
||||
context: {
|
||||
live: { overrides: new Map([['camera.office', 'camera.kitchen']]) },
|
||||
},
|
||||
});
|
||||
|
||||
await applySubstreamOff(view);
|
||||
|
||||
expect(view.context?.live?.overrides?.get('camera.office')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should clear an explicit camera override', async () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera.driveway',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([
|
||||
['camera.office', 'camera.kitchen'],
|
||||
['camera.driveway', 'camera.driveway_hd'],
|
||||
]),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await applySubstreamOff(view, { camera: 'camera.office' });
|
||||
|
||||
expect(view.context?.live?.overrides?.get('camera.office')).toBeUndefined();
|
||||
expect(view.context?.live?.overrides?.get('camera.driveway')).toBe(
|
||||
'camera.driveway_hd',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
import { CameraManagerStore } from '../../../../src/camera-manager/store';
|
||||
import { SubstreamOnAction } from '../../../../src/card-controller/actions/actions/substream-on';
|
||||
import { applyViewModifiers } from '../../../../src/card-controller/view/modifiers';
|
||||
import { createSubstreamOnAction } from '../../../../src/utils/action';
|
||||
import { getStreamCameraID } from '../../../../src/view/substream';
|
||||
import { View } from '../../../../src/view/view';
|
||||
import {
|
||||
@@ -13,15 +14,6 @@ import {
|
||||
createView,
|
||||
} from '../../../test-utils';
|
||||
|
||||
const createAction = (): SubstreamOnAction =>
|
||||
new SubstreamOnAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'live_substream_on',
|
||||
},
|
||||
);
|
||||
|
||||
// A store where `camera.office` has one substream dependency, `camera.kitchen`.
|
||||
const createStoreWithSubstreams = (): CameraManagerStore =>
|
||||
createStore([
|
||||
@@ -37,64 +29,148 @@ const createStoreWithSubstreams = (): CameraManagerStore =>
|
||||
]);
|
||||
|
||||
// Runs the on-action for `view`, applies the modifier it produces (via the real
|
||||
// `applyViewModifiers`), and returns the resulting engaged stream.
|
||||
const getStreamAfterSubstreamOn = async (
|
||||
// `applyViewModifiers`), and returns the engaged stream for the override key
|
||||
// the modifier wrote (`camera` if set, otherwise the selected camera).
|
||||
const applySubstreamOn = async (
|
||||
view: View,
|
||||
store: CameraManagerStore = createStoreWithSubstreams(),
|
||||
options?: {
|
||||
store?: CameraManagerStore;
|
||||
camera?: string;
|
||||
stream?: string;
|
||||
},
|
||||
): Promise<string | null> => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(
|
||||
createCameraManager(options?.store ?? createStoreWithSubstreams()),
|
||||
);
|
||||
|
||||
await createAction().execute(api);
|
||||
await new SubstreamOnAction(
|
||||
{},
|
||||
createSubstreamOnAction({
|
||||
camera: options?.camera,
|
||||
stream: options?.stream,
|
||||
}),
|
||||
).execute(api);
|
||||
|
||||
const params = vi.mocked(api.getViewManager().setViewByParameters).mock.calls[0]?.[0];
|
||||
applyViewModifiers(view, params?.modifiers);
|
||||
return getStreamCameraID(view);
|
||||
return getStreamCameraID(view, options?.camera);
|
||||
};
|
||||
|
||||
describe('SubstreamOnAction', () => {
|
||||
it('should advance to the next dependency', async () => {
|
||||
expect(
|
||||
await getStreamAfterSubstreamOn(
|
||||
createView({ view: 'live', camera: 'camera.office' }),
|
||||
),
|
||||
).toBe('camera.kitchen');
|
||||
});
|
||||
|
||||
it('should wrap back to the parent camera', async () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera.office',
|
||||
context: {
|
||||
live: { overrides: new Map([['camera.office', 'camera.kitchen']]) },
|
||||
},
|
||||
describe('cycling (no `stream` parameter)', () => {
|
||||
it('should advance to the next dependency', async () => {
|
||||
expect(
|
||||
await applySubstreamOn(createView({ view: 'live', camera: 'camera.office' })),
|
||||
).toBe('camera.kitchen');
|
||||
});
|
||||
|
||||
expect(await getStreamAfterSubstreamOn(view)).toBe('camera.office');
|
||||
});
|
||||
it('should wrap back to the parent camera', async () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera.office',
|
||||
context: {
|
||||
live: { overrides: new Map([['camera.office', 'camera.kitchen']]) },
|
||||
},
|
||||
});
|
||||
|
||||
it('should treat a malformed override as the start of the cycle', async () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera.office',
|
||||
context: {
|
||||
live: { overrides: new Map([['camera.office', 'NOT_A_REAL_CAMERA']]) },
|
||||
},
|
||||
expect(await applySubstreamOn(view)).toBe('camera.office');
|
||||
});
|
||||
|
||||
expect(await getStreamAfterSubstreamOn(view)).toBe('camera.office');
|
||||
it('should treat a malformed override as the start of the cycle', async () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera.office',
|
||||
context: {
|
||||
live: { overrides: new Map([['camera.office', 'NOT_A_REAL_CAMERA']]) },
|
||||
},
|
||||
});
|
||||
|
||||
expect(await applySubstreamOn(view)).toBe('camera.office');
|
||||
});
|
||||
|
||||
it('should engage no substream when there are no usable dependencies', async () => {
|
||||
const view = createView({ view: 'live', camera: 'camera.office' });
|
||||
|
||||
expect(await applySubstreamOn(view, { store: createStore() })).toBe(
|
||||
'camera.office',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should engage no substream when there are no usable dependencies', async () => {
|
||||
const view = createView({ view: 'live', camera: 'camera.office' });
|
||||
describe('with an explicit `stream`', () => {
|
||||
it('should engage that stream on the selected camera', async () => {
|
||||
expect(
|
||||
await applySubstreamOn(createView({ view: 'live', camera: 'camera.office' }), {
|
||||
stream: 'camera.kitchen',
|
||||
}),
|
||||
).toBe('camera.kitchen');
|
||||
});
|
||||
|
||||
expect(await getStreamAfterSubstreamOn(view, createStore())).toBe('camera.office');
|
||||
it('should treat `stream` equal to the camera as no substream', async () => {
|
||||
// Already cycled to a substream; passing the parent camera as `stream`
|
||||
// should be equivalent to off.
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera.office',
|
||||
context: {
|
||||
live: { overrides: new Map([['camera.office', 'camera.kitchen']]) },
|
||||
},
|
||||
});
|
||||
|
||||
expect(await applySubstreamOn(view, { stream: 'camera.office' })).toBe(
|
||||
'camera.office',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should engage no substream when the view has no camera', async () => {
|
||||
describe('with an explicit `camera`', () => {
|
||||
it('should target that camera instead of the selected one', async () => {
|
||||
const view = createView({ view: 'live', camera: 'camera.driveway' });
|
||||
|
||||
expect(
|
||||
await applySubstreamOn(view, {
|
||||
camera: 'camera.office',
|
||||
stream: 'camera.kitchen',
|
||||
}),
|
||||
).toBe('camera.kitchen');
|
||||
});
|
||||
|
||||
it('should cycle dependencies of the explicit camera', async () => {
|
||||
const view = createView({ view: 'live', camera: 'camera.driveway' });
|
||||
|
||||
expect(await applySubstreamOn(view, { camera: 'camera.office' })).toBe(
|
||||
'camera.kitchen',
|
||||
);
|
||||
});
|
||||
|
||||
it('should engage no substream when the explicit camera has no dependencies', async () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera.driveway',
|
||||
context: {
|
||||
live: { overrides: new Map([['camera.no_deps', 'camera.stale']]) },
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
await applySubstreamOn(view, {
|
||||
camera: 'camera.no_deps',
|
||||
store: createStore([
|
||||
{
|
||||
cameraID: 'camera.no_deps',
|
||||
capabilities: createCapabilities({ substream: true }),
|
||||
},
|
||||
]),
|
||||
}),
|
||||
).toBe('camera.no_deps');
|
||||
});
|
||||
});
|
||||
|
||||
it('should engage no substream when the view has no camera and the action has no camera', async () => {
|
||||
expect(
|
||||
await getStreamAfterSubstreamOn(createView({ view: 'live', camera: null })),
|
||||
await applySubstreamOn(createView({ view: 'live', camera: null })),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
@@ -102,7 +178,7 @@ describe('SubstreamOnAction', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(null);
|
||||
|
||||
await createAction().execute(api);
|
||||
await new SubstreamOnAction({}, createSubstreamOnAction()).execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { SubstreamSelectAction } from '../../../../src/card-controller/actions/actions/substream-select';
|
||||
import { SubstreamViewModifier } from '../../../../src/card-controller/view/modifiers/substream';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle live_substream_select action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new SubstreamSelectAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'live_substream_select',
|
||||
camera: 'substream',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
});
|
||||
});
|
||||
@@ -40,7 +40,6 @@ import { SleepAction } from '../../../src/card-controller/actions/actions/sleep'
|
||||
import { StatusBarAction } from '../../../src/card-controller/actions/actions/status-bar';
|
||||
import { SubstreamOffAction } from '../../../src/card-controller/actions/actions/substream-off';
|
||||
import { SubstreamOnAction } from '../../../src/card-controller/actions/actions/substream-on';
|
||||
import { SubstreamSelectAction } from '../../../src/card-controller/actions/actions/substream-select';
|
||||
import { ToggleAction } from '../../../src/card-controller/actions/actions/toggle';
|
||||
import { UnmuteAction } from '../../../src/card-controller/actions/actions/unmute';
|
||||
import { URLAction } from '../../../src/card-controller/actions/actions/url';
|
||||
@@ -111,18 +110,8 @@ describe('ActionFactory', () => {
|
||||
[{ advanced_camera_card_action: 'fullscreen' as const }, FullscreenAction],
|
||||
[{ advanced_camera_card_action: 'info' as const }, InfoAction],
|
||||
[{ advanced_camera_card_action: 'image' as const }, ViewAction],
|
||||
[
|
||||
{ advanced_camera_card_action: 'live_substream_off' as const },
|
||||
SubstreamOffAction,
|
||||
],
|
||||
[{ advanced_camera_card_action: 'live_substream_on' as const }, SubstreamOnAction],
|
||||
[
|
||||
{
|
||||
advanced_camera_card_action: 'live_substream_select' as const,
|
||||
camera: 'camera.office',
|
||||
},
|
||||
SubstreamSelectAction,
|
||||
],
|
||||
[{ advanced_camera_card_action: 'substream_off' as const }, SubstreamOffAction],
|
||||
[{ advanced_camera_card_action: 'substream_on' as const }, SubstreamOnAction],
|
||||
[{ advanced_camera_card_action: 'live' as const }, ViewAction],
|
||||
[
|
||||
{
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
createGeneralAction,
|
||||
createLogAction,
|
||||
createMediaPlayerAction,
|
||||
createSubstreamOffAction,
|
||||
createSubstreamOnAction,
|
||||
createViewAction,
|
||||
} from '../../../src/utils/action';
|
||||
import { createCardAPI, createConfig } from '../../test-utils';
|
||||
@@ -77,10 +79,10 @@ describe('LockManager', () => {
|
||||
|
||||
for (const action of [
|
||||
createViewAction('clips'),
|
||||
createCameraAction('camera_select', 'cam-1'),
|
||||
createCameraAction('live_substream_select', 'cam-1'),
|
||||
createGeneralAction('live_substream_on'),
|
||||
createGeneralAction('live_substream_off'),
|
||||
createCameraAction('cam-1'),
|
||||
createSubstreamOnAction({ stream: 'cam-1' }),
|
||||
createSubstreamOnAction(),
|
||||
createSubstreamOffAction(),
|
||||
createGeneralAction('default'),
|
||||
createGeneralAction('pause'),
|
||||
createGeneralAction('reload'),
|
||||
|
||||
@@ -140,10 +140,8 @@ describe('QueryStringManager', () => {
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should execute live_substream_select action', async () => {
|
||||
setQueryString(
|
||||
'?advanced-camera-card-action.id.live_substream_select=camera.office_hd',
|
||||
);
|
||||
it('should execute substream_on with a stream value as a view modifier', async () => {
|
||||
setQueryString('?advanced-camera-card-action.id.substream_on=camera.office_hd');
|
||||
const api = createCardAPI();
|
||||
setCardID(api, 'id');
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
@@ -154,7 +152,7 @@ describe('QueryStringManager', () => {
|
||||
expect(manager.hasViewRelatedActionsToRun()).toBeFalsy();
|
||||
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
modifiers: [new SubstreamViewModifier({ stream: 'camera.office_hd' })],
|
||||
params: {},
|
||||
});
|
||||
|
||||
@@ -162,24 +160,77 @@ describe('QueryStringManager', () => {
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('should ignore action without value', () => {
|
||||
it.each([['camera_select' as const], ['live_substream_select' as const]])(
|
||||
'%s',
|
||||
async (action: string) => {
|
||||
setQueryString(`?advanced-camera-card-action.id.${action}=`);
|
||||
const api = createCardAPI();
|
||||
setCardID(api, 'id');
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
it('should dispatch substream_on without a value as a non-view action', async () => {
|
||||
setQueryString('?advanced-camera-card-action.id.substream_on=');
|
||||
const api = createCardAPI();
|
||||
setCardID(api, 'id');
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
expect(manager.hasViewRelatedActionsToRun()).toBeFalsy();
|
||||
await manager.executeIfNecessary();
|
||||
expect(manager.hasViewRelatedActionsToRun()).toBeFalsy();
|
||||
await manager.executeIfNecessary();
|
||||
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
},
|
||||
expect(api.getActionsManager().executeActions).toBeCalledWith({
|
||||
actions: [
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
card_id: 'id',
|
||||
advanced_camera_card_action: 'substream_on',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should execute substream_off as a view modifier that clears the override', async () => {
|
||||
setQueryString('?advanced-camera-card-action.id.substream_off=');
|
||||
const api = createCardAPI();
|
||||
setCardID(api, 'id');
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
expect(manager.hasViewRelatedActionsToRun()).toBeTruthy();
|
||||
await manager.executeIfNecessary();
|
||||
expect(manager.hasViewRelatedActionsToRun()).toBeFalsy();
|
||||
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
|
||||
modifiers: [new SubstreamViewModifier()],
|
||||
params: {},
|
||||
});
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should warn on the legacy live_substream_select URL form', async () => {
|
||||
const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
setQueryString(
|
||||
'?advanced-camera-card-action.id.live_substream_select=camera.office_hd',
|
||||
);
|
||||
const api = createCardAPI();
|
||||
setCardID(api, 'id');
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
expect(manager.hasViewRelatedActionsToRun()).toBeFalsy();
|
||||
await manager.executeIfNecessary();
|
||||
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
|
||||
expect(consoleSpy).toBeCalledWith(expect.stringContaining('live_substream_select'));
|
||||
});
|
||||
|
||||
it('should ignore camera_select without a value', async () => {
|
||||
setQueryString('?advanced-camera-card-action.id.camera_select=');
|
||||
const api = createCardAPI();
|
||||
setCardID(api, 'id');
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
expect(manager.hasViewRelatedActionsToRun()).toBeFalsy();
|
||||
await manager.executeIfNecessary();
|
||||
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should handle unknown action', async () => {
|
||||
@@ -235,7 +286,7 @@ describe('QueryStringManager', () => {
|
||||
it('should handle view and default with camera and substream specified', async () => {
|
||||
setQueryString(
|
||||
'?advanced-camera-card-action.id.clips=' +
|
||||
'&advanced-camera-card-action.id.live_substream_select=camera.kitchen_hd' +
|
||||
'&advanced-camera-card-action.id.substream_on=camera.kitchen_hd' +
|
||||
'&advanced-camera-card-action.id.default=' +
|
||||
'&advanced-camera-card-action.id.camera_select=camera.kitchen',
|
||||
);
|
||||
@@ -250,7 +301,7 @@ describe('QueryStringManager', () => {
|
||||
params: {
|
||||
camera: 'camera.kitchen',
|
||||
},
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
modifiers: [new SubstreamViewModifier({ stream: 'camera.kitchen_hd' })],
|
||||
});
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
|
||||
});
|
||||
@@ -276,9 +327,7 @@ describe('QueryStringManager', () => {
|
||||
});
|
||||
|
||||
it('should only execute when needed', async () => {
|
||||
setQueryString(
|
||||
'?advanced-camera-card-action.id.live_substream_select=camera.office_hd',
|
||||
);
|
||||
setQueryString('?advanced-camera-card-action.id.substream_on=camera.office_hd');
|
||||
const api = createCardAPI();
|
||||
setCardID(api, 'id');
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
|
||||
@@ -6,7 +6,7 @@ describe('SubstreamViewModifier', () => {
|
||||
it('should write the override for the selected camera', () => {
|
||||
const view = createView({ camera: 'camera' });
|
||||
|
||||
new SubstreamViewModifier('substream').modify(view);
|
||||
new SubstreamViewModifier({ stream: 'substream' }).modify(view);
|
||||
|
||||
expect(view.context?.live?.overrides?.get('camera')).toBe('substream');
|
||||
});
|
||||
@@ -28,7 +28,10 @@ describe('SubstreamViewModifier', () => {
|
||||
context: { live: { overrides: new Map([['camera', 'substream']]) } },
|
||||
});
|
||||
|
||||
new SubstreamViewModifier('other-substream', 'other-camera').modify(view);
|
||||
new SubstreamViewModifier({
|
||||
stream: 'other-substream',
|
||||
camera: 'other-camera',
|
||||
}).modify(view);
|
||||
|
||||
expect(view.context?.live?.overrides?.get('other-camera')).toBe('other-substream');
|
||||
expect(view.context?.live?.overrides?.get('camera')).toBe('substream');
|
||||
@@ -40,15 +43,26 @@ describe('SubstreamViewModifier', () => {
|
||||
context: { live: { overrides: new Map([['other-camera', 'other-substream']]) } },
|
||||
});
|
||||
|
||||
new SubstreamViewModifier(undefined, 'other-camera').modify(view);
|
||||
new SubstreamViewModifier({ camera: 'other-camera' }).modify(view);
|
||||
|
||||
expect(view.context?.live?.overrides?.get('other-camera')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should clear the override when the stream equals the camera itself', () => {
|
||||
const view = createView({
|
||||
camera: 'camera',
|
||||
context: { live: { overrides: new Map([['camera', 'substream']]) } },
|
||||
});
|
||||
|
||||
new SubstreamViewModifier({ stream: 'camera' }).modify(view);
|
||||
|
||||
expect(view.context?.live?.overrides?.get('camera')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should no-op for a view without a camera', () => {
|
||||
const view = createView({ camera: null });
|
||||
|
||||
new SubstreamViewModifier('substream').modify(view);
|
||||
new SubstreamViewModifier({ stream: 'substream' }).modify(view);
|
||||
|
||||
expect(view.context).toBeNull();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user