BREAKING CHANGE: Changes the behavior of the `not` condition when there is more than one condition present. Users who desire the previous behavior should wrap their conditions in an `and` first. - Closes: #2383
1431 lines
46 KiB
TypeScript
1431 lines
46 KiB
TypeScript
import { HassEntities } from 'home-assistant-js-websocket';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { MicrophoneState } from '../../src/card-controller/types';
|
|
import { ConditionsManager } from '../../src/conditions/conditions-manager';
|
|
import { ConditionStateManager } from '../../src/conditions/state-manager';
|
|
import { HomeAssistant } from '../../src/ha/types';
|
|
import {
|
|
createConfig,
|
|
createHASS,
|
|
createMediaLoadedInfo,
|
|
createStateEntity,
|
|
createUser,
|
|
} from '../test-utils';
|
|
|
|
// @vitest-environment jsdom
|
|
describe('ConditionsManager', () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('should evaluate conditions', () => {
|
|
describe('with a view condition', () => {
|
|
it('should match named view change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'view' as const, views: ['foo'] }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ view: 'foo' });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
|
|
it('should match any view change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'view' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ view: 'clips' });
|
|
expect(listener).toHaveBeenLastCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
view: {
|
|
to: 'clips',
|
|
},
|
|
},
|
|
});
|
|
|
|
stateManager.setState({ view: 'timeline' });
|
|
expect(listener).toHaveBeenLastCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
view: {
|
|
from: 'clips',
|
|
to: 'timeline',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(listener).toBeCalledTimes(2);
|
|
});
|
|
|
|
it('should not re-trigger without a real change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'view' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ view: 'clips' });
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
|
|
stateManager.setState({ view: 'clips' });
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
it('with fullscreen condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'fullscreen' as const, fullscreen: true }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ fullscreen: true });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ fullscreen: false });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('with expand condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'expand' as const, expand: true }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ expand: true });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ expand: false });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
describe('with camera condition', () => {
|
|
it('should match named camera change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'camera' as const, cameras: ['bar'] }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ camera: 'bar' });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ camera: 'will-not-match' });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('should match any camera change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'camera' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ camera: 'bar' });
|
|
expect(listener).toHaveBeenLastCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
camera: {
|
|
to: 'bar',
|
|
},
|
|
},
|
|
});
|
|
|
|
stateManager.setState({ camera: 'foo' });
|
|
expect(listener).toHaveBeenLastCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
camera: {
|
|
from: 'bar',
|
|
to: 'foo',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(listener).toBeCalledTimes(2);
|
|
});
|
|
|
|
it('should not re-trigger without a real change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'camera' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ camera: 'bar' });
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
|
|
stateManager.setState({ camera: 'bar' });
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('with stock HA conditions', () => {
|
|
describe('with state condition', () => {
|
|
it('neither positive nor negative', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'state' as const,
|
|
entity: 'binary_sensor.foo',
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'binary_sensor.foo': createStateEntity({ state: 'on' }),
|
|
}),
|
|
});
|
|
expect(listener).toBeCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
state: {
|
|
entity: 'binary_sensor.foo',
|
|
to: 'on',
|
|
},
|
|
},
|
|
});
|
|
expect(listener).toBeCalledTimes(1);
|
|
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'binary_sensor.foo': createStateEntity({ state: 'off' }),
|
|
}),
|
|
});
|
|
expect(listener).toBeCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
state: {
|
|
entity: 'binary_sensor.foo',
|
|
from: 'on',
|
|
to: 'off',
|
|
},
|
|
},
|
|
});
|
|
expect(listener).toBeCalledTimes(2);
|
|
});
|
|
|
|
describe('positive', () => {
|
|
it('single state', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'state' as const,
|
|
entity: 'binary_sensor.foo',
|
|
state: 'on',
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({ 'binary_sensor.foo': createStateEntity() }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'binary_sensor.foo': createStateEntity({ state: 'off' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('multiple states', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'state' as const,
|
|
entity: 'binary_sensor.foo',
|
|
state: ['active', 'on'],
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({ 'binary_sensor.foo': createStateEntity() }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'binary_sensor.foo': createStateEntity({ state: 'active' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'binary_sensor.foo': createStateEntity({ state: 'off' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe('negative', () => {
|
|
it('single state', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'state' as const,
|
|
entity: 'binary_sensor.foo',
|
|
state_not: 'on',
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({ 'binary_sensor.foo': createStateEntity() }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'binary_sensor.foo': createStateEntity({ state: 'off' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
it('multiple states', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'state' as const,
|
|
entity: 'binary_sensor.foo',
|
|
state_not: ['active', 'on'],
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({ 'binary_sensor.foo': createStateEntity() }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'binary_sensor.foo': createStateEntity({ state: 'active' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'binary_sensor.foo': createStateEntity({ state: 'off' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
|
|
it('implicit state condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
entity: 'binary_sensor.foo',
|
|
state: 'on',
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({ 'binary_sensor.foo': createStateEntity() }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'binary_sensor.foo': createStateEntity({ state: 'off' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('should match any state change when state and state_not omitted', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{ condition: 'state' as const, entity: 'switch.one' },
|
|
{ condition: 'state' as const, entity: 'switch.two' },
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'switch.one': createStateEntity({ state: 'on' }),
|
|
'switch.two': createStateEntity({ state: 'off' }),
|
|
}),
|
|
});
|
|
expect(listener).toHaveBeenLastCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
// Only the last matching state will be included in the data.
|
|
state: {
|
|
entity: 'switch.two',
|
|
to: 'off',
|
|
},
|
|
},
|
|
});
|
|
|
|
stateManager.setState({
|
|
hass: createHASS({
|
|
'switch.one': createStateEntity({ state: 'off' }),
|
|
'switch.two': createStateEntity({ state: 'on' }),
|
|
}),
|
|
});
|
|
|
|
expect(listener).toHaveBeenLastCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
// Only the last matching state will be included in the data.
|
|
state: {
|
|
entity: 'switch.two',
|
|
from: 'off',
|
|
to: 'on',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(listener).toBeCalledTimes(2);
|
|
});
|
|
|
|
it('should not re-trigger without a real change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'state' as const, entity: 'switch.one' }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
const hass = createHASS({
|
|
'switch.one': createStateEntity({ state: 'on' }),
|
|
});
|
|
stateManager.setState({
|
|
hass,
|
|
});
|
|
expect(listener).toBeCalledTimes(1);
|
|
expect(manager.getEvaluation()).toEqual({
|
|
result: true,
|
|
triggerData: {
|
|
// Only the last matching state will be included in the data.
|
|
state: {
|
|
entity: 'switch.one',
|
|
to: 'on',
|
|
},
|
|
},
|
|
});
|
|
|
|
stateManager.setState({ hass });
|
|
expect(listener).toBeCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('with numeric state condition', () => {
|
|
it('above', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'numeric_state' as const,
|
|
entity: 'sensor.foo',
|
|
above: 10,
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({ 'sensor.foo': createStateEntity({ state: '11' }) }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
hass: createHASS({ 'binary_sensor.foo': createStateEntity({ state: '9' }) }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('below', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'numeric_state' as const,
|
|
entity: 'sensor.foo',
|
|
below: 10,
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({ 'sensor.foo': createStateEntity({ state: '11' }) }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({ 'sensor.foo': createStateEntity({ state: '9' }) }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('with template condition', () => {
|
|
const createHASSForTemplateCondition = (states: HassEntities): HomeAssistant => {
|
|
const hass = createHASS(states);
|
|
vi.mocked(hass.connection.sendMessagePromise).mockResolvedValue([]);
|
|
return hass;
|
|
};
|
|
|
|
it('should evaluate true when template evalutes to true', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'template' as const,
|
|
value_template: '{{ is_state("sensor.foo", "on") }}',
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
|
|
stateManager.setState({
|
|
hass: createHASSForTemplateCondition({
|
|
'sensor.foo': createStateEntity({ state: 'on' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
|
|
stateManager.setState({
|
|
hass: createHASSForTemplateCondition({
|
|
'sensor.foo': createStateEntity({ state: 'off' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('should evaluate false when template evalutes to non-boolean', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'template' as const,
|
|
// This does not result in a boolean.
|
|
value_template: '{{ hass.states["light.office"].state }}',
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
stateManager.setState({
|
|
hass: createHASSForTemplateCondition({
|
|
'light.office': createStateEntity({ state: 'on' }),
|
|
}),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
it('should not call listeners for HA state changes without relevant condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'fullscreen' as const,
|
|
fullscreen: true,
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({
|
|
hass: createHASS({ 'sensor.foo': createStateEntity({ state: '11' }) }),
|
|
});
|
|
|
|
expect(listener).not.toBeCalled();
|
|
});
|
|
});
|
|
|
|
it('with user condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'user' as const,
|
|
users: ['user_1', 'user_2'],
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
hass: createHASS({}, createUser({ id: 'user_1' })),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
hass: createHASS({}, createUser({ id: 'user_WRONG' })),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('with media loaded condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'media_loaded' as const, media_loaded: true }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ mediaLoadedInfo: createMediaLoadedInfo() });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ mediaLoadedInfo: null });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
describe('with screen condition', () => {
|
|
it('on evaluation', () => {
|
|
vi.spyOn(window, 'matchMedia')
|
|
.mockReturnValueOnce({
|
|
addEventListener: vi.fn(),
|
|
} as unknown as MediaQueryList)
|
|
.mockReturnValueOnce({
|
|
matches: true,
|
|
} as unknown as MediaQueryList);
|
|
|
|
const manager = new ConditionsManager([
|
|
{ condition: 'screen' as const, media_query: 'whatever' },
|
|
]);
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
|
|
it('on trigger', () => {
|
|
const addEventListener = vi.fn();
|
|
const removeEventListener = vi.fn();
|
|
vi.spyOn(window, 'matchMedia')
|
|
.mockReturnValueOnce({
|
|
addEventListener: addEventListener,
|
|
removeEventListener: removeEventListener,
|
|
} as unknown as MediaQueryList)
|
|
.mockReturnValueOnce({
|
|
matches: false,
|
|
} as unknown as MediaQueryList)
|
|
.mockReturnValueOnce({
|
|
matches: true,
|
|
} as unknown as MediaQueryList);
|
|
|
|
const manager = new ConditionsManager([
|
|
{
|
|
condition: 'screen' as const,
|
|
media_query: 'media query goes here',
|
|
},
|
|
]);
|
|
|
|
expect(addEventListener).toHaveBeenCalledWith('change', expect.anything());
|
|
|
|
const callback = vi.fn();
|
|
manager.addListener(callback);
|
|
|
|
// Call the media query callback and use it to pretend a match happened. The
|
|
// callback is the 0th mock innvocation and the 1st argument.
|
|
addEventListener.mock.calls[0][1]();
|
|
|
|
// This should result in a callback to our state listener.
|
|
expect(callback).toBeCalledWith({ result: true, triggerData: {} });
|
|
|
|
// Destroy the manager and ensure the event listener is removed.
|
|
manager.destroy();
|
|
expect(removeEventListener).toBeCalled();
|
|
});
|
|
});
|
|
|
|
it('with display mode condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'display_mode' as const, display_mode: 'grid' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ displayMode: 'grid' });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ displayMode: 'single' });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('with triggered condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'triggered' as const, triggered: ['camera_1', 'camera_2'] }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ triggered: new Set(['camera_1']) });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
triggered: new Set(['camera_2', 'camera_1', 'camera_3']),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ triggered: new Set(['camera_3']) });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('with interaction condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'interaction' as const, interaction: true }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ interaction: true });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ interaction: false });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
describe('with microphone condition', () => {
|
|
const createMicrophoneState = (
|
|
state: Partial<MicrophoneState>,
|
|
): MicrophoneState => {
|
|
return {
|
|
connected: false,
|
|
muted: false,
|
|
forbidden: false,
|
|
...state,
|
|
};
|
|
};
|
|
it('empty', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'microphone' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
microphone: createMicrophoneState({ connected: true }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
microphone: createMicrophoneState({ connected: false }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ microphone: createMicrophoneState({ muted: true }) });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ microphone: createMicrophoneState({ muted: false }) });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
|
|
it('connected is true', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'microphone' as const, connected: true }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
microphone: createMicrophoneState({ connected: true }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
microphone: createMicrophoneState({ connected: false }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('connected is false', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'microphone' as const, connected: false }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
microphone: createMicrophoneState({ connected: true }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
microphone: createMicrophoneState({ connected: false }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
|
|
it('muted is true', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'microphone' as const, muted: true }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ microphone: createMicrophoneState({ muted: true }) });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ microphone: createMicrophoneState({ muted: false }) });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('muted is false', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'microphone' as const, muted: false }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ microphone: createMicrophoneState({ muted: true }) });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ microphone: createMicrophoneState({ muted: false }) });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
|
|
it('connected and muted', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'microphone' as const, muted: false, connected: true }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ microphone: createMicrophoneState({ muted: true }) });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ microphone: createMicrophoneState({ muted: false }) });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
microphone: createMicrophoneState({ connected: false, muted: false }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
microphone: createMicrophoneState({ connected: true, muted: false }),
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('with key condition', () => {
|
|
it('simple keypress', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'key' as const, key: 'a' }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
keys: {
|
|
a: { state: 'down', ctrl: false, shift: false, alt: false, meta: false },
|
|
},
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
keys: {
|
|
a: { state: 'up', ctrl: false, shift: false, alt: false, meta: false },
|
|
},
|
|
});
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('keypress with modifiers', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'key' as const,
|
|
key: 'a',
|
|
state: 'down' as const,
|
|
ctrl: true,
|
|
shift: true,
|
|
alt: true,
|
|
meta: true,
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
keys: {
|
|
a: { state: 'down', ctrl: false, shift: false, alt: false, meta: false },
|
|
},
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
keys: {
|
|
a: { state: 'down', ctrl: true, shift: true, alt: true, meta: false },
|
|
},
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
keys: {
|
|
a: { state: 'down', ctrl: true, shift: true, alt: true, meta: true },
|
|
},
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('with user agent condition', () => {
|
|
const userAgent =
|
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
|
|
|
|
it('should match exact user agent', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'user_agent' as const, user_agent: userAgent }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
userAgent: userAgent,
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
userAgent: 'Something else',
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('should match user agent regex', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'user_agent' as const, user_agent_re: 'Chrome/' }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
userAgent: userAgent,
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
userAgent: 'Something else',
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('should match companion app', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'user_agent' as const, companion: true }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
userAgent: 'Home Assistant/',
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
userAgent: userAgent,
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('should match multiple parameters', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'user_agent' as const,
|
|
companion: true,
|
|
user_agent: 'Home Assistant/',
|
|
user_agent_re: 'Home.Assistant',
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({
|
|
userAgent: 'Home Assistant/',
|
|
});
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({
|
|
userAgent: 'Something else',
|
|
});
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe('with config condition', () => {
|
|
const config_1 = createConfig({
|
|
// Default is:
|
|
//
|
|
// view: {
|
|
// default: live,
|
|
// },
|
|
});
|
|
const config_2 = createConfig({
|
|
view: {
|
|
default: 'clips',
|
|
},
|
|
});
|
|
const config_3 = createConfig({
|
|
view: {
|
|
default: 'clips',
|
|
default_cycle_camera: true,
|
|
},
|
|
});
|
|
const config_4 = createConfig({
|
|
view: {
|
|
default: 'clips',
|
|
default_cycle_camera: true,
|
|
dim: true,
|
|
},
|
|
});
|
|
|
|
it('should match any config change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'config' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ config: config_1 });
|
|
expect(listener).toHaveBeenLastCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
config: {
|
|
to: config_1,
|
|
},
|
|
},
|
|
});
|
|
|
|
stateManager.setState({ config: config_2 });
|
|
expect(listener).toHaveBeenLastCalledWith({
|
|
result: true,
|
|
triggerData: {
|
|
config: {
|
|
from: config_1,
|
|
to: config_2,
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(listener).toBeCalledTimes(2);
|
|
});
|
|
|
|
it('should match specific config change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'config' as const, paths: ['view.default'] }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ config: config_1 });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ config: config_2 });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ config: config_3 });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('should match multiple config change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'config' as const,
|
|
paths: ['view.default', 'view.default_cycle_camera'],
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ config: config_1 });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ config: config_2 });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ config: config_3 });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ config: config_4 });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('should not match unrelated changes', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'config' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ config: config_1 });
|
|
expect(listener).toBeCalledTimes(1);
|
|
|
|
// On the next state set, the condition won't match anymore (as the
|
|
// config is the same), the listener will still be called to indicate
|
|
// the condition evaluation has changed.
|
|
stateManager.setState({ expand: true });
|
|
expect(listener).toBeCalledTimes(2);
|
|
expect(listener).toHaveBeenLastCalledWith({ result: false });
|
|
|
|
// Future unrelated state changes won't call the listener.
|
|
stateManager.setState({ fullscreen: true });
|
|
expect(listener).toBeCalledTimes(2);
|
|
});
|
|
|
|
it('should not re-trigger without a real change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'config' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ config: config_1 });
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
|
|
stateManager.setState({ config: config_1 });
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
it('with initialized condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'initialized' as const }],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ initialized: true });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ initialized: false });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('with simple OR condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'or' as const,
|
|
conditions: [
|
|
{
|
|
condition: 'fullscreen' as const,
|
|
fullscreen: true,
|
|
},
|
|
{
|
|
condition: 'expand' as const,
|
|
expand: true,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ fullscreen: true });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ expand: true });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ fullscreen: false });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ expand: false });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('with triggered OR condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
|
|
// This is not a terribly realistic example, but chosen so that trigger
|
|
// data for both camera and view should be returned.
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'or' as const,
|
|
conditions: [
|
|
{ condition: 'camera' as const },
|
|
{ condition: 'view' as const },
|
|
],
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
|
|
stateManager.setState({ camera: 'camera-1' });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
expect(manager.getEvaluation().triggerData).toEqual({
|
|
camera: { to: 'camera-1' },
|
|
});
|
|
|
|
stateManager.setState({ view: 'view-1' });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
expect(manager.getEvaluation().triggerData).toEqual({
|
|
view: { to: 'view-1' },
|
|
});
|
|
|
|
stateManager.setState({ camera: 'camera-2', view: 'view-2' });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
expect(manager.getEvaluation().triggerData).toEqual({
|
|
camera: { to: 'camera-2', from: 'camera-1' },
|
|
|
|
// View data will not be here as the view condition is not evaluated,
|
|
// since the camera one will evaluate to true first.
|
|
});
|
|
});
|
|
|
|
it('with simple AND condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'and' as const,
|
|
conditions: [
|
|
{
|
|
condition: 'fullscreen' as const,
|
|
fullscreen: true,
|
|
},
|
|
{
|
|
condition: 'expand' as const,
|
|
expand: true,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ fullscreen: true });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ expand: true });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
stateManager.setState({ fullscreen: false });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
stateManager.setState({ expand: false });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('with triggered AND condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
|
|
// This is not a terribly realistic example, but chosen so that trigger
|
|
// data for both camera and view should be returned.
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'and' as const,
|
|
conditions: [
|
|
{ condition: 'camera' as const },
|
|
{ condition: 'view' as const },
|
|
],
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
|
|
stateManager.setState({ camera: 'camera-1' });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
|
|
stateManager.setState({ view: 'view-1' });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
|
|
stateManager.setState({ camera: 'camera-2', view: 'view-2' });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
expect(manager.getEvaluation().triggerData).toEqual({
|
|
camera: { from: 'camera-1', to: 'camera-2' },
|
|
view: { from: 'view-1', to: 'view-2' },
|
|
});
|
|
|
|
stateManager.setState({ view: 'view-3' });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
});
|
|
|
|
it('with not condition', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[
|
|
{
|
|
condition: 'not' as const,
|
|
conditions: [
|
|
{
|
|
condition: 'fullscreen' as const,
|
|
fullscreen: true,
|
|
},
|
|
{
|
|
condition: 'expand' as const,
|
|
expand: true,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
stateManager,
|
|
);
|
|
|
|
// Neither sub-condition is true initially, so `not` passes.
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
|
|
// fullscreen becomes true — any sub-condition being true means `not` fails.
|
|
stateManager.setState({ fullscreen: true });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
|
|
stateManager.setState({ expand: true });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
|
|
// fullscreen reverts, but expand is still true, so `not` still fails.
|
|
stateManager.setState({ fullscreen: false });
|
|
expect(manager.getEvaluation().result).toBeFalsy();
|
|
|
|
// Both sub-conditions are false again, so `not` passes.
|
|
stateManager.setState({ expand: false });
|
|
expect(manager.getEvaluation().result).toBeTruthy();
|
|
|
|
// `not` conditions never have trigger data (as nothing is triggering).
|
|
expect(manager.getEvaluation().triggerData).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe('should handle listeners correctly', () => {
|
|
it('should add listener', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'fullscreen' as const, fullscreen: true }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ fullscreen: true });
|
|
|
|
expect(listener).toBeCalledWith({ result: true, triggerData: {} });
|
|
expect(listener).toBeCalledTimes(1);
|
|
|
|
stateManager.setState({ fullscreen: false });
|
|
expect(listener).toBeCalledWith({ result: false });
|
|
expect(listener).toBeCalledTimes(2);
|
|
|
|
// Re-add the same listener (will still only be called once).
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ fullscreen: true });
|
|
|
|
expect(listener).toBeCalledWith({ result: true, triggerData: {} });
|
|
expect(listener).toBeCalledTimes(3);
|
|
});
|
|
|
|
it('should remove listener', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'fullscreen' as const, fullscreen: true }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
manager.removeListener(listener);
|
|
|
|
stateManager.setState({ fullscreen: true });
|
|
|
|
expect(listener).not.toBeCalled();
|
|
});
|
|
|
|
it('should remove listener on destroy', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'fullscreen' as const, fullscreen: true }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
manager.destroy();
|
|
|
|
stateManager.setState({ fullscreen: true });
|
|
|
|
expect(listener).not.toBeCalled();
|
|
});
|
|
|
|
it('with not call listeners when condition result does not change', () => {
|
|
const stateManager = new ConditionStateManager();
|
|
const manager = new ConditionsManager(
|
|
[{ condition: 'view' as const, views: ['foo'] }],
|
|
stateManager,
|
|
);
|
|
|
|
const listener = vi.fn();
|
|
manager.addListener(listener);
|
|
|
|
stateManager.setState({ view: 'foo' });
|
|
expect(listener).toBeCalledTimes(1);
|
|
|
|
stateManager.setState({ view: 'bar' });
|
|
expect(listener).toBeCalledTimes(2);
|
|
|
|
stateManager.setState({ view: 'bar' });
|
|
expect(listener).toBeCalledTimes(2);
|
|
|
|
stateManager.setState({ view: 'foo' });
|
|
expect(listener).toBeCalledTimes(3);
|
|
|
|
stateManager.setState({ view: 'foo' });
|
|
expect(listener).toBeCalledTimes(3);
|
|
});
|
|
});
|
|
});
|