feat: Add event-based automation triggers (#2537)

This commit is contained in:
Dermot Duffy
2026-06-30 17:45:13 -07:00
committed by dermotduffy
parent b701366762
commit a31816c168
109 changed files with 4607 additions and 1623 deletions
@@ -6,6 +6,7 @@ import { CallTrigger } from '../../../src/condition-trigger/triggers/triggers/ca
import { CameraTrigger } from '../../../src/condition-trigger/triggers/triggers/camera';
import { ConfigTrigger } from '../../../src/condition-trigger/triggers/triggers/config';
import { DisplayModeTrigger } from '../../../src/condition-trigger/triggers/triggers/display-mode';
import { EventTrigger } from '../../../src/condition-trigger/triggers/triggers/event';
import { ExpandTrigger } from '../../../src/condition-trigger/triggers/triggers/expand';
import { FullscreenTrigger } from '../../../src/condition-trigger/triggers/triggers/fullscreen';
import { InitializedTrigger } from '../../../src/condition-trigger/triggers/triggers/initialized';
@@ -24,6 +25,7 @@ import {
} from '../../../src/condition-trigger/triggers/triggers/types';
import { ViewTrigger } from '../../../src/condition-trigger/triggers/triggers/view';
import { Trigger } from '../../../src/config/schema/condition-trigger/triggers/types';
import { createHASSManager } from '../../test-utils';
type TriggerEvaluatorConstructor = new (...args: never[]) => TriggerEvaluator;
@@ -32,9 +34,11 @@ describe('createTriggerEvaluator', () => {
const context = (): TriggerEvaluatorContext => ({
stateManager: new ConditionStateManager(),
templateRenderer: new TemplateRenderer(),
hassManager: createHASSManager(),
});
it.each<[Trigger, TriggerEvaluatorConstructor]>([
[{ trigger: 'event', event_type: 'zha_event' }, EventTrigger],
[{ trigger: 'state', entity_id: 'binary_sensor.x' }, StateTrigger],
[{ trigger: 'numeric_state', entity_id: 'sensor.x', above: 5 }, NumericStateTrigger],
[{ trigger: 'template', value_template: '{{ true }}' }, TemplateTrigger],
@@ -2,7 +2,7 @@ import { describe, expect, it, Mock, vi } from 'vitest';
import { ConditionStateManager } from '../../../src/condition-trigger/conditions/state-manager';
import { TriggersManager } from '../../../src/condition-trigger/triggers/manager';
import { Trigger } from '../../../src/config/schema/condition-trigger/triggers/types';
import { createHASS, createStateEntity } from '../../test-utils';
import { createHASS, createHASSManager, createStateEntity } from '../../test-utils';
// @vitest-environment jsdom
describe('TriggersManager', () => {
@@ -14,7 +14,7 @@ describe('TriggersManager', () => {
listener: Mock;
} => {
const stateManager = new ConditionStateManager();
const manager = new TriggersManager(triggers, stateManager);
const manager = new TriggersManager(triggers, stateManager, createHASSManager());
const listener = vi.fn();
return { manager, stateManager, listener };
};
@@ -107,6 +107,7 @@ describe('TriggersManager', () => {
const manager = new TriggersManager(
[{ trigger: 'camera', cameras: ['front'], enabled }],
stateManager,
createHASSManager(),
);
const listener = vi.fn();
manager.addListener(listener);
@@ -169,6 +170,7 @@ describe('TriggersManager', () => {
const manager = new TriggersManager(
[{ trigger: 'camera', cameras: ['front', 'back'], enabled: ENABLED_TEMPLATE }],
stateManager,
createHASSManager(),
);
const listener = vi.fn();
manager.addListener(listener);
@@ -1,8 +1,8 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { CallTrigger } from '../../../../src/condition-trigger/triggers/triggers/call';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('CallTrigger', () => {
@@ -11,10 +11,9 @@ describe('CallTrigger', () => {
): { stateManager: ConditionStateManager; callback: Mock } => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
new CallTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
}).subscribe(callback);
new CallTrigger(trigger, createTriggerEvaluatorContext({ stateManager })).subscribe(
callback,
);
return { stateManager, callback };
};
@@ -1,9 +1,9 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { CameraTrigger } from '../../../../src/condition-trigger/triggers/triggers/camera';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createConfig } from '../../../test-utils';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('CameraTrigger', () => {
@@ -16,10 +16,10 @@ describe('CameraTrigger', () => {
} => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
const cameraTrigger = new CameraTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
});
const cameraTrigger = new CameraTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
);
return { cameraTrigger, stateManager, callback };
};
@@ -1,9 +1,9 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { ConfigTrigger } from '../../../../src/condition-trigger/triggers/triggers/config';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createConfig } from '../../../test-utils';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('ConfigTrigger', () => {
@@ -16,10 +16,10 @@ describe('ConfigTrigger', () => {
} => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
const configTrigger = new ConfigTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
});
const configTrigger = new ConfigTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
);
return { configTrigger, stateManager, callback };
};
@@ -1,8 +1,8 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { DisplayModeTrigger } from '../../../../src/condition-trigger/triggers/triggers/display-mode';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('DisplayModeTrigger', () => {
@@ -11,10 +11,10 @@ describe('DisplayModeTrigger', () => {
): { stateManager: ConditionStateManager; callback: Mock } => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
new DisplayModeTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
}).subscribe(callback);
new DisplayModeTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
).subscribe(callback);
return { stateManager, callback };
};
@@ -0,0 +1,177 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import {
EventSubscriptionRequest,
EventWatcherSubscriptionInterface,
} from '../../../../src/card-controller/hass/event-watcher';
import { EventTrigger } from '../../../../src/condition-trigger/triggers/triggers/event';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createHASSEvent, createHASSManager } from '../../../test-utils';
import { createTriggerEvaluatorContext } from './test-utils';
interface Harness {
trigger: EventTrigger;
eventWatcher: EventWatcherSubscriptionInterface;
callback: Mock;
}
const create = (config: TriggerOfType<'event'>): Harness => {
const eventWatcher = mock<EventWatcherSubscriptionInterface>();
const hassManager = createHASSManager({ eventWatcher });
const callback = vi.fn();
const trigger = new EventTrigger(
config,
createTriggerEvaluatorContext({ hassManager }),
);
return { trigger, eventWatcher, callback };
};
const getLastMatcher = (
eventWatcher: EventWatcherSubscriptionInterface,
n = 0,
): EventSubscriptionRequest['matcher'] =>
vi.mocked(eventWatcher.subscribe).mock.calls[n][0].matcher;
const callEventCallback = (
eventWatcher: EventWatcherSubscriptionInterface,
event: ReturnType<typeof createHASSEvent>,
n = 0,
): void => {
vi.mocked(eventWatcher.subscribe).mock.calls[n][0].callback(event);
};
describe('EventTrigger', () => {
it('should register one EventWatcher request per event_type', () => {
const { trigger, eventWatcher, callback } = create({
trigger: 'event',
event_type: 'zha_event',
});
trigger.subscribe(callback);
expect(eventWatcher.subscribe).toBeCalledTimes(1);
expect(vi.mocked(eventWatcher.subscribe).mock.calls[0][0].event_type).toBe(
'zha_event',
);
});
it('should dedupe duplicate event_types in list form', () => {
// A repeated entry would otherwise produce two requests and fire the
// callback twice for every matching event.
const { trigger, eventWatcher, callback } = create({
trigger: 'event',
event_type: ['zha_event', 'zha_event'],
});
trigger.subscribe(callback);
expect(eventWatcher.subscribe).toBeCalledTimes(1);
});
it('should expand list-form event_type into one request per type', () => {
const { trigger, eventWatcher, callback } = create({
trigger: 'event',
event_type: ['zha_event', 'deconz_event'],
});
trigger.subscribe(callback);
expect(eventWatcher.subscribe).toBeCalledTimes(2);
expect(vi.mocked(eventWatcher.subscribe).mock.calls[0][0].event_type).toBe(
'zha_event',
);
expect(vi.mocked(eventWatcher.subscribe).mock.calls[1][0].event_type).toBe(
'deconz_event',
);
});
it('should fire with the full HA event on dispatch', () => {
const { trigger, eventWatcher, callback } = create({
trigger: 'event',
event_type: 'zha_event',
});
trigger.subscribe(callback);
const event = createHASSEvent('zha_event', { command: 'press' });
callEventCallback(eventWatcher, event);
expect(callback).toBeCalledWith({ platform: 'event', event });
});
it('should omit the matcher when neither event_data nor context is set', () => {
const { trigger, eventWatcher, callback } = create({
trigger: 'event',
event_type: 'zha_event',
});
trigger.subscribe(callback);
expect(vi.mocked(eventWatcher.subscribe).mock.calls[0][0].matcher).toBeUndefined();
});
it('should attach an event_data matcher', () => {
const { trigger, eventWatcher, callback } = create({
trigger: 'event',
event_type: 'zha_event',
event_data: { command: 'press' },
});
trigger.subscribe(callback);
const matcher = getLastMatcher(eventWatcher);
expect(matcher?.(createHASSEvent('zha_event', { command: 'press' }))).toBe(true);
expect(matcher?.(createHASSEvent('zha_event', { command: 'release' }))).toBe(false);
});
it('should attach a context matcher', () => {
const { trigger, eventWatcher, callback } = create({
trigger: 'event',
event_type: 'zha_event',
context: { user_id: 'u-1' },
});
trigger.subscribe(callback);
const matcher = getLastMatcher(eventWatcher);
expect(
matcher?.(
createHASSEvent('zha_event', {}, { id: 'i', user_id: 'u-1', parent_id: null }),
),
).toBe(true);
expect(
matcher?.(
createHASSEvent('zha_event', {}, { id: 'i', user_id: 'u-2', parent_id: null }),
),
).toBe(false);
});
it('should AND event_data and context filters', () => {
const { trigger, eventWatcher, callback } = create({
trigger: 'event',
event_type: 'zha_event',
event_data: { command: 'press' },
context: { user_id: 'u-1' },
});
trigger.subscribe(callback);
const matcher = getLastMatcher(eventWatcher);
const matchingContext = { id: 'i', user_id: 'u-1', parent_id: null };
const nonMatchingContext = { id: 'i', user_id: 'u-2', parent_id: null };
expect(
matcher?.(createHASSEvent('zha_event', { command: 'press' }, matchingContext)),
).toBe(true);
expect(
matcher?.(createHASSEvent('zha_event', { command: 'press' }, nonMatchingContext)),
).toBe(false);
expect(
matcher?.(createHASSEvent('zha_event', { command: 'release' }, matchingContext)),
).toBe(false);
});
it('should unsubscribe every request on destroy', () => {
const { trigger, eventWatcher, callback } = create({
trigger: 'event',
event_type: ['zha_event', 'deconz_event'],
});
trigger.subscribe(callback);
trigger.destroy();
expect(eventWatcher.unsubscribe).toBeCalledTimes(2);
});
it('should be a no-op when destroyed without subscribing', () => {
const { trigger, eventWatcher } = create({
trigger: 'event',
event_type: 'zha_event',
});
trigger.destroy();
expect(eventWatcher.unsubscribe).not.toBeCalled();
});
});
@@ -1,8 +1,8 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { ExpandTrigger } from '../../../../src/condition-trigger/triggers/triggers/expand';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('ExpandTrigger', () => {
@@ -11,10 +11,10 @@ describe('ExpandTrigger', () => {
): { stateManager: ConditionStateManager; callback: Mock } => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
new ExpandTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
}).subscribe(callback);
new ExpandTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
).subscribe(callback);
return { stateManager, callback };
};
@@ -1,8 +1,8 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { FullscreenTrigger } from '../../../../src/condition-trigger/triggers/triggers/fullscreen';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('FullscreenTrigger', () => {
@@ -15,10 +15,10 @@ describe('FullscreenTrigger', () => {
} => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
const fullscreenTrigger = new FullscreenTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
});
const fullscreenTrigger = new FullscreenTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
);
return { fullscreenTrigger, stateManager, callback };
};
@@ -1,8 +1,8 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { InteractionTrigger } from '../../../../src/condition-trigger/triggers/triggers/interaction';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('InteractionTrigger', () => {
@@ -11,10 +11,10 @@ describe('InteractionTrigger', () => {
): { stateManager: ConditionStateManager; callback: Mock } => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
new InteractionTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
}).subscribe(callback);
new InteractionTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
).subscribe(callback);
return { stateManager, callback };
};
@@ -1,9 +1,9 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { MediaLoadedTrigger } from '../../../../src/condition-trigger/triggers/triggers/media-loaded';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createMediaLoadedInfo } from '../../../test-utils';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('MediaLoadedTrigger', () => {
@@ -12,10 +12,10 @@ describe('MediaLoadedTrigger', () => {
): { stateManager: ConditionStateManager; callback: Mock } => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
new MediaLoadedTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
}).subscribe(callback);
new MediaLoadedTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
).subscribe(callback);
return { stateManager, callback };
};
@@ -1,9 +1,9 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { MicrophoneState } from '../../../../src/card-controller/types';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { MicrophoneTrigger } from '../../../../src/condition-trigger/triggers/triggers/microphone';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('MicrophoneTrigger', () => {
@@ -19,10 +19,10 @@ describe('MicrophoneTrigger', () => {
): { stateManager: ConditionStateManager; callback: Mock } => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
new MicrophoneTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
}).subscribe(callback);
new MicrophoneTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
).subscribe(callback);
return { stateManager, callback };
};
@@ -1,10 +1,10 @@
import { HassEntities, HassEntity } from 'home-assistant-js-websocket';
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { NumericStateTrigger } from '../../../../src/condition-trigger/triggers/triggers/numeric-state';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createHASS, createStateEntity } from '../../../test-utils';
import { createTriggerEvaluatorContext } from './test-utils';
const SENSOR = 'sensor.temperature';
const SENSOR_TWO = 'sensor.humidity';
@@ -21,10 +21,10 @@ describe('NumericStateTrigger', () => {
} => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
const trigger = new NumericStateTrigger(config, {
stateManager,
templateRenderer: new TemplateRenderer(),
});
const trigger = new NumericStateTrigger(
config,
createTriggerEvaluatorContext({ stateManager }),
);
return { trigger, stateManager, callback };
};
@@ -1,10 +1,10 @@
import { HassEntities, HassEntity } from 'home-assistant-js-websocket';
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { StateTrigger } from '../../../../src/condition-trigger/triggers/triggers/state';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createHASS, createStateEntity } from '../../../test-utils';
import { createTriggerEvaluatorContext } from './test-utils';
const ENTITY = 'binary_sensor.door';
const ENTITY_TWO = 'binary_sensor.window';
@@ -20,10 +20,10 @@ describe('StateTrigger', () => {
} => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
const trigger = new StateTrigger(config, {
stateManager,
templateRenderer: new TemplateRenderer(),
});
const trigger = new StateTrigger(
config,
createTriggerEvaluatorContext({ stateManager }),
);
return { trigger, stateManager, callback };
};
@@ -1,9 +1,9 @@
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { TemplateTrigger } from '../../../../src/condition-trigger/triggers/triggers/template';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createHASS, createStateEntity } from '../../../test-utils';
import { createTriggerEvaluatorContext } from './test-utils';
const ENTITY_ONE = 'sensor.foo';
const ENTITY_TWO = 'sensor.bar';
@@ -21,10 +21,10 @@ describe('TemplateTrigger', () => {
} => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
const trigger = new TemplateTrigger(config, {
stateManager,
templateRenderer: new TemplateRenderer(),
});
const trigger = new TemplateTrigger(
config,
createTriggerEvaluatorContext({ stateManager }),
);
return { trigger, stateManager, callback };
};
@@ -0,0 +1,13 @@
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { TriggerEvaluatorContext } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createHASSManager } from '../../../test-utils';
export const createTriggerEvaluatorContext = (
context?: Partial<TriggerEvaluatorContext>,
): TriggerEvaluatorContext => ({
stateManager: new ConditionStateManager(),
templateRenderer: new TemplateRenderer(),
hassManager: createHASSManager(),
...context,
});
@@ -1,8 +1,8 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { TriggeredTrigger } from '../../../../src/condition-trigger/triggers/triggers/triggered';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createTriggerEvaluatorContext } from './test-utils';
// @vitest-environment jsdom
describe('TriggeredTrigger', () => {
@@ -11,10 +11,10 @@ describe('TriggeredTrigger', () => {
): { stateManager: ConditionStateManager; callback: Mock } => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
new TriggeredTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
}).subscribe(callback);
new TriggeredTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
).subscribe(callback);
return { stateManager, callback };
};
@@ -1,7 +1,7 @@
import { describe, expect, it, Mock, vi } from 'vitest';
import { TemplateRenderer } from '../../../../src/card-controller/templates';
import { ConditionStateManager } from '../../../../src/condition-trigger/conditions/state-manager';
import { TriggerOfType } from '../../../../src/condition-trigger/triggers/triggers/types';
import { createTriggerEvaluatorContext } from './test-utils';
import { ViewTrigger } from '../../../../src/condition-trigger/triggers/triggers/view';
// @vitest-environment jsdom
@@ -15,10 +15,10 @@ describe('ViewTrigger', () => {
} => {
const stateManager = new ConditionStateManager();
const callback = vi.fn();
const viewTrigger = new ViewTrigger(trigger, {
stateManager,
templateRenderer: new TemplateRenderer(),
});
const viewTrigger = new ViewTrigger(
trigger,
createTriggerEvaluatorContext({ stateManager }),
);
return { viewTrigger, stateManager, callback };
};