fix: Confirmations should apply to all actions (#1959)

This also refactors how generic HA actions are handled, and improves
typing of actions.


[skip ci]
This commit is contained in:
Dermot Duffy
2025-03-15 14:32:48 -07:00
committed by GitHub
parent a79ffa6edc
commit 75ae7720d3
90 changed files with 1332 additions and 854 deletions
@@ -1,4 +1,13 @@
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from 'vitest';
import { mock } from 'vitest-mock-extended';
import {
ActionsManager,
@@ -8,13 +17,7 @@ import {
import { TemplateRenderer } from '../../../src/card-controller/templates';
import { AdvancedCameraCardView } from '../../../src/config/types';
import { createLogAction } from '../../../src/utils/action';
import {
createAction,
createCardAPI,
createConfig,
createHASS,
createView,
} from '../../test-utils';
import { createCardAPI, createConfig, createHASS, createView } from '../../test-utils';
describe('ActionsManager', () => {
describe('getMergedActions', () => {
@@ -138,7 +141,7 @@ describe('ActionsManager', () => {
vi.restoreAllMocks();
});
it('should handle interaction', () => {
it('should handle interaction', async () => {
const api = createCardAPI();
const element = document.createElement('div');
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
@@ -158,7 +161,7 @@ describe('ActionsManager', () => {
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
manager.handleInteractionEvent(
await manager.handleInteractionEvent(
new CustomEvent<Interaction>('event', { detail: { action: 'tap' } }),
);
expect(consoleSpy).toBeCalled();
@@ -203,7 +206,7 @@ describe('ActionsManager', () => {
vi.restoreAllMocks();
});
it('should handle event', () => {
it('should handle event', async () => {
const action = createLogAction('Hello, world!');
const event = new CustomEvent('ll-custom', {
detail: action,
@@ -213,15 +216,15 @@ describe('ActionsManager', () => {
const manager = new ActionsManager(api);
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
manager.handleCustomActionEvent(event);
await manager.handleCustomActionEvent(event);
expect(consoleSpy).toBeCalled();
});
it('should not handle event without detail', () => {
it('should not handle event without detail', async () => {
const manager = new ActionsManager(createCardAPI());
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
manager.handleCustomActionEvent(new Event('ll-custom'));
await manager.handleCustomActionEvent(new Event('ll-custom'));
expect(consoleSpy).not.toBeCalled();
});
});
@@ -250,6 +253,77 @@ describe('ActionsManager', () => {
await manager.executeActions(createLogAction('Hello, world!'));
expect(consoleSpy).toBeCalled();
});
it('should execute actions', async () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
await manager.executeActions(createLogAction('Hello, world!'));
expect(consoleSpy).toBeCalled();
});
it('should render templates', async () => {
const action = createLogAction('{{ acc.camera }}');
const templateRenderer = mock<TemplateRenderer>();
templateRenderer.renderRecursively.mockReturnValue(action);
const api = createCardAPI();
const hass = createHASS();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const conditionState = {
camera: 'camera',
};
vi.mocked(api.getConditionStateManager().getState).mockReturnValue(conditionState);
const manager = new ActionsManager(api, templateRenderer);
const config = { entity: 'light.office' };
const triggerData = { view: { from: 'previous-view', to: 'view' } };
await manager.executeActions(action, {
config,
triggerData,
});
expect(templateRenderer.renderRecursively).toBeCalledWith(hass, action, {
conditionState,
triggerData,
});
});
describe('should forward haptics', () => {
afterEach(() => {
vi.unstubAllGlobals();
});
it('should forward success haptic', async () => {
const handler = vi.fn();
window.addEventListener('haptic', handler);
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeActions({ action: 'none' });
expect(handler).toBeCalledWith(expect.objectContaining({ detail: 'success' }));
});
it('should forward warning haptic', async () => {
const handler = vi.fn();
window.addEventListener('haptic', handler);
const api = createCardAPI();
const manager = new ActionsManager(api);
vi.stubGlobal('confirm', vi.fn().mockReturnValue(false));
await manager.executeActions({ action: 'none', confirmation: true });
expect(handler).toBeCalledWith(expect.objectContaining({ detail: 'warning' }));
});
});
});
describe('uninitialize', () => {
@@ -266,18 +340,18 @@ describe('ActionsManager', () => {
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
const promise = manager.executeActions([
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
createAction({
{
action: 'fire-dom-event',
advanced_camera_card_action: 'sleep',
duration: {
m: 1,
},
})!,
},
createLogAction('Hello, world!'),
]);
// Stop inflight actions.
manager.uninitialize();
await manager.uninitialize();
// Advance timers (causes the sleep to end).
vi.runOnlyPendingTimers();
@@ -288,34 +362,4 @@ describe('ActionsManager', () => {
expect(consoleSpy).not.toBeCalled();
});
});
it('should render templates', async () => {
const action = createLogAction('{{ acc.camera }}');
const templateRenderer = mock<TemplateRenderer>();
templateRenderer.renderRecursively.mockReturnValue(action);
const api = createCardAPI();
const hass = createHASS();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const conditionState = {
camera: 'camera',
};
vi.mocked(api.getConditionStateManager().getState).mockReturnValue(conditionState);
const manager = new ActionsManager(api, templateRenderer);
const config = { camera_image: 'camera-image' };
const triggerData = { view: { from: 'previous-view', to: 'view' } };
await manager.executeActions(action, {
config,
triggerData,
});
expect(templateRenderer.renderRecursively).toBeCalledWith(hass, action, {
conditionState,
triggerData,
});
});
});
@@ -1,19 +1,172 @@
import { it } from 'vitest';
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { BaseAction } from '../../../../src/card-controller/actions/actions/base';
import { createCardAPI } from '../../../test-utils';
import { createViewAction } from '../../../../src/utils/action';
import { createCardAPI, createHASS, createUser } from '../../../test-utils';
it('should construct', async () => {
const api = createCardAPI();
const action = new BaseAction(
{},
{
action: 'fire-dom-event',
},
);
describe('should handle base action', () => {
beforeEach(() => {
vi.clearAllMocks();
});
await action.execute(api);
await action.stop();
beforeAll(() => {
vi.stubGlobal('confirm', vi.fn());
});
// These methods have no observable effect on the base class, so this test is
// currently only providing coverage and proof of no exceptions!
afterAll(() => {
vi.unstubAllGlobals();
});
it('should construct', async () => {
const api = createCardAPI();
const action = new BaseAction(
{},
{
action: 'fire-dom-event',
},
);
await action.execute(api);
await action.stop();
// These methods have no observable effect on the base class, so this test is
// currently only providing coverage and proof of no exceptions!
});
it('should not confirm when not necessary', async () => {
const api = createCardAPI();
const action = new BaseAction(
{},
{
action: 'fire-dom-event',
},
);
await action.execute(api);
expect(confirm).not.toBeCalled();
});
it('should continue execution when confirmed', async () => {
const api = createCardAPI();
const action = new BaseAction(
{},
{
action: 'fire-dom-event',
confirmation: true,
},
);
vi.mocked(confirm).mockReturnValue(true);
await action.execute(api);
expect(confirm).toBeCalled();
});
it('should abort execution when not confirmed', async () => {
const api = createCardAPI();
const action = new BaseAction(
{},
{
action: 'fire-dom-event',
confirmation: true,
},
);
vi.mocked(confirm).mockReturnValue(false);
expect(async () => await action.execute(api)).rejects.toThrowError(/Aborted action/);
});
it('should not confirm when exempted', async () => {
const api = createCardAPI();
const hass = createHASS({}, createUser({ id: 'user-id' }));
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const action = new BaseAction(
{},
{
action: 'fire-dom-event',
confirmation: {
exemptions: [
{
user: 'user-id',
},
],
},
},
);
await action.execute(api);
expect(confirm).not.toBeCalled();
});
describe('should show correct confirmation text', () => {
it('should show action name in confirmation text', async () => {
const api = createCardAPI();
const hass = createHASS({}, createUser({ id: 'user-id' }));
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const action = new BaseAction(
{},
{
action: 'more-info',
confirmation: true,
},
);
vi.mocked(confirm).mockReturnValue(true);
await action.execute(api);
expect(confirm).toBeCalledWith(
'Are you sure you want to perform this action: more-info',
);
});
it('should show advanced camera card action name in confirmation text', async () => {
const api = createCardAPI();
const hass = createHASS({}, createUser({ id: 'user-id' }));
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const action = new BaseAction(
{},
{
...createViewAction('clips'),
confirmation: true,
},
);
vi.mocked(confirm).mockReturnValue(true);
await action.execute(api);
expect(confirm).toBeCalledWith(
'Are you sure you want to perform this action: clips',
);
});
it('should show configured confirmation text', async () => {
const api = createCardAPI();
const hass = createHASS({}, createUser({ id: 'user-id' }));
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const action = new BaseAction(
{},
{
action: 'more-info',
confirmation: {
text: 'Test text',
},
},
);
vi.mocked(confirm).mockReturnValue(true);
await action.execute(api);
expect(confirm).toBeCalledWith('Test text');
});
});
});
@@ -0,0 +1,48 @@
import { describe, expect, it, vi } from 'vitest';
import { CallServiceAction } from '../../../../src/card-controller/actions/actions/call-service';
import { createCardAPI, createHASS } from '../../../test-utils';
describe('CallServiceAction', () => {
it('should call service', async () => {
const api = createCardAPI();
const hass = createHASS();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const action = new CallServiceAction(
{},
{
action: 'call-service',
service: 'light.turn_on',
data: { brightness_pct: 80 },
target: { entity_id: 'light.office' },
},
);
await action.execute(api);
expect(hass.callService).toBeCalledWith(
'light',
'turn_on',
{
brightness_pct: 80,
},
{ entity_id: 'light.office' },
);
});
it('should not call service without hass', async () => {
const api = createCardAPI();
const action = new CallServiceAction(
{},
{
action: 'call-service',
service: 'light.turn_on',
data: { brightness_pct: 80 },
target: { entity_id: 'light.office' },
},
);
await action.execute(api);
// No observable effect.
});
});
@@ -0,0 +1,37 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { CustomAction } from '../../../../src/card-controller/actions/actions/custom';
import { createCardAPI } from '../../../test-utils';
// @vitest-environment jsdom
describe('CustomAction', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('should open the URL in a new window', async () => {
const handler = vi.fn();
const element = document.createElement('div');
element.addEventListener('ll-custom', handler);
const api = createCardAPI();
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
const action = new CustomAction(
{},
{
action: 'fire-dom-event' as const,
foo: 'bar',
1: 2,
},
{},
);
await action.execute(api);
expect(handler).toBeCalledWith(
expect.objectContaining({
detail: { action: 'fire-dom-event', foo: 'bar', 1: 2 },
}),
);
});
});
@@ -1,46 +0,0 @@
import { describe, expect, it, vi } from 'vitest';
import { createCardAPI, createHASS, createLitElement } from '../../../test-utils.js';
import { GenericAction } from '../../../../src/card-controller/actions/actions/generic.js';
import { handleActionConfig } from '../../../../src/ha/handle-action.js';
vi.mock('../../../../src/ha/handle-action.js');
describe('should handle generic action', () => {
it('without hass', async () => {
const api = createCardAPI();
const action = new GenericAction(
{},
{
action: 'fire-dom-event',
},
);
await action.execute(api);
expect(handleActionConfig).not.toBeCalled();
});
// @vitest-environment jsdom
it('with hass', async () => {
const api = createCardAPI();
const hass = createHASS();
const element = createLitElement();
vi.mocked(api.getCardElementManager()).getElement.mockReturnValue(element);
vi.mocked(api.getHASSManager()).getHASS.mockReturnValue(hass);
const action = new GenericAction(
{},
{
action: 'fire-dom-event',
},
);
await action.execute(api);
expect(handleActionConfig).toBeCalledWith(
element,
hass,
{},
{ action: 'fire-dom-event' },
);
});
});
@@ -0,0 +1,80 @@
import { describe, expect, it, vi } from 'vitest';
import { MoreInfoAction } from '../../../../src/card-controller/actions/actions/more-info';
import { createCardAPI } from '../../../test-utils';
// @vitest-environment jsdom
describe('should handle more-info action', () => {
it('should handle more-info with entity in action', async () => {
const handler = vi.fn();
const element = document.createElement('div');
element.addEventListener('hass-more-info', handler);
const api = createCardAPI();
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
const action = new MoreInfoAction(
{},
{
action: 'more-info',
entity: 'light.office',
},
{},
);
await action.execute(api);
expect(handler).toBeCalledWith(
expect.objectContaining({
detail: { entityId: 'light.office' },
}),
);
});
it('should handle more-info with entity in auxilliary config', async () => {
const handler = vi.fn();
const element = document.createElement('div');
element.addEventListener('hass-more-info', handler);
const api = createCardAPI();
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
const action = new MoreInfoAction(
{},
{
action: 'more-info',
},
{
entity: 'light.office',
},
);
await action.execute(api);
expect(handler).toBeCalledWith(
expect.objectContaining({
detail: { entityId: 'light.office' },
}),
);
});
it('should take no action with any entity', async () => {
const handler = vi.fn();
const element = document.createElement('div');
element.addEventListener('hass-more-info', handler);
const api = createCardAPI();
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
const action = new MoreInfoAction(
{},
{
action: 'more-info',
},
{},
);
await action.execute(api);
expect(handler).not.toBeCalled();
});
});
@@ -0,0 +1,57 @@
import { describe, expect, it, vi } from 'vitest';
import { NavigateAction } from '../../../../src/card-controller/actions/actions/navigate';
import { createCardAPI } from '../../../test-utils';
// @vitest-environment jsdom
describe('should handle navigate action', () => {
it('should handle navigate action', async () => {
const handler = vi.fn();
window.addEventListener('location-changed', handler);
const action = new NavigateAction(
{},
{
action: 'navigate',
navigation_path: '/path',
},
{},
);
const historyLength = history.length;
await action.execute(createCardAPI());
expect(history.length).toBe(historyLength + 1);
expect(handler).toBeCalledWith(
expect.objectContaining({
detail: { replace: false },
}),
);
});
it('should handle navigate action that replaces', async () => {
const handler = vi.fn();
window.addEventListener('location-changed', handler);
const action = new NavigateAction(
{},
{
action: 'navigate',
navigation_path: '/path',
navigation_replace: true,
},
{},
);
const historyLength = history.length;
await action.execute(createCardAPI());
expect(history.length).toBe(historyLength);
expect(handler).toBeCalledWith(
expect.objectContaining({
detail: { replace: true },
}),
);
});
});
@@ -0,0 +1,17 @@
import { it } from 'vitest';
import { NoneAction } from '../../../../src/card-controller/actions/actions/none';
import { createCardAPI } from '../../../test-utils';
it('should handle none action', async () => {
const api = createCardAPI();
const action = new NoneAction(
{},
{
action: 'none' as const,
},
);
await action.execute(api);
// No observable side effects.
});
@@ -0,0 +1,48 @@
import { describe, expect, it, vi } from 'vitest';
import { createCardAPI, createHASS } from '../../../test-utils';
import { PerformActionAction } from '../../../../src/card-controller/actions/actions/perform-action';
describe('PerformActionAction', () => {
it('should perform action', async () => {
const api = createCardAPI();
const hass = createHASS();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const action = new PerformActionAction(
{},
{
action: 'perform-action',
perform_action: 'light.turn_on',
data: { brightness_pct: 80 },
target: { entity_id: 'light.office' },
},
);
await action.execute(api);
expect(hass.callService).toBeCalledWith(
'light',
'turn_on',
{
brightness_pct: 80,
},
{ entity_id: 'light.office' },
);
});
it('should not perform action without hass', async () => {
const api = createCardAPI();
const action = new PerformActionAction(
{},
{
action: 'perform-action',
perform_action: 'light.turn_on',
data: { brightness_pct: 80 },
target: { entity_id: 'light.office' },
},
);
await action.execute(api);
// No observable effect.
});
});
@@ -0,0 +1,81 @@
import { describe, expect, it, vi } from 'vitest';
import { ToggleAction } from '../../../../src/card-controller/actions/actions/toggle';
import { createCardAPI, createHASS, createStateEntity } from '../../../test-utils';
describe('ToggleAction', () => {
describe('should toggle entities', () => {
it.each([
['light.office' as const, 'off' as const, 'light' as const, 'turn_on' as const],
['light.office' as const, 'on' as const, 'light' as const, 'turn_off' as const],
[
'cover.door' as const,
'closed' as const,
'cover' as const,
'open_cover' as const,
],
['cover.door' as const, 'open' as const, 'cover' as const, 'close_cover' as const],
['lock.door' as const, 'locked' as const, 'lock' as const, 'unlock' as const],
['lock.door' as const, 'unlocked' as const, 'lock' as const, 'lock' as const],
[
'group.foo' as const,
'off' as const,
'homeassistant' as const,
'turn_on' as const,
],
[
'group.foo' as const,
'on' as const,
'homeassistant' as const,
'turn_off' as const,
],
])(
'%s %s',
async (
entityID: string,
state: string,
expectedServiceDomain: string,
expectedService: string,
) => {
const api = createCardAPI();
const hass = createHASS({
[entityID]: createStateEntity({ entity_id: entityID, state: state }),
});
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const action = new ToggleAction({}, { action: 'toggle' }, { entity: entityID });
await action.execute(api);
expect(hass.callService).toBeCalledWith(expectedServiceDomain, expectedService, {
entity_id: entityID,
});
},
);
});
it('should do nothing without an entity ID', async () => {
const api = createCardAPI();
const hass = createHASS();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const action = new ToggleAction({}, { action: 'toggle' }, {});
await action.execute(api);
expect(hass.callService).not.toBeCalled();
});
it('should do nothing without an entity state', async () => {
const api = createCardAPI();
const hass = createHASS();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const action = new ToggleAction(
{},
{ action: 'toggle' },
{ entity: 'light.NOT_FOUND' },
);
await action.execute(api);
expect(hass.callService).not.toBeCalled();
});
});
@@ -0,0 +1,25 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { URLAction } from '../../../../src/card-controller/actions/actions/url';
import { createCardAPI } from '../../../test-utils';
// @vitest-environment jsdom
describe('URLAction', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('should open the URL in a new window', async () => {
const urlAction = new URLAction(
{},
{
action: 'url',
url_path: 'https://example.com',
},
);
const windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
await urlAction.execute(createCardAPI());
expect(windowOpenSpy).toHaveBeenCalledWith('https://example.com');
});
});
+27 -20
View File
@@ -1,12 +1,13 @@
import { describe, expect, it, vi } from 'vitest';
import { CallServiceAction } from '../../../src/card-controller/actions/actions/call-service';
import { CameraSelectAction } from '../../../src/card-controller/actions/actions/camera-select';
import { CameraUIAction } from '../../../src/card-controller/actions/actions/camera-ui';
import { CustomAction } from '../../../src/card-controller/actions/actions/custom';
import { DefaultAction } from '../../../src/card-controller/actions/actions/default';
import { DisplayModeSelectAction } from '../../../src/card-controller/actions/actions/display-mode-select';
import { DownloadAction } from '../../../src/card-controller/actions/actions/download';
import { ExpandAction } from '../../../src/card-controller/actions/actions/expand';
import { FullscreenAction } from '../../../src/card-controller/actions/actions/fullscreen';
import { GenericAction } from '../../../src/card-controller/actions/actions/generic';
import { InternalCallbackAction } from '../../../src/card-controller/actions/actions/internal-callback';
import { LogAction } from '../../../src/card-controller/actions/actions/log';
import { MediaPlayerAction } from '../../../src/card-controller/actions/actions/media-player';
@@ -15,8 +16,12 @@ import { MicrophoneConnectAction } from '../../../src/card-controller/actions/ac
import { MicrophoneDisconnectAction } from '../../../src/card-controller/actions/actions/microphone-disconnect';
import { MicrophoneMuteAction } from '../../../src/card-controller/actions/actions/microphone-mute';
import { MicrophoneUnmuteAction } from '../../../src/card-controller/actions/actions/microphone-unmute';
import { MoreInfoAction } from '../../../src/card-controller/actions/actions/more-info';
import { MuteAction } from '../../../src/card-controller/actions/actions/mute';
import { NavigateAction } from '../../../src/card-controller/actions/actions/navigate';
import { NoneAction } from '../../../src/card-controller/actions/actions/none';
import { PauseAction } from '../../../src/card-controller/actions/actions/pause';
import { PerformActionAction } from '../../../src/card-controller/actions/actions/perform-action';
import { PlayAction } from '../../../src/card-controller/actions/actions/play';
import { PTZAction } from '../../../src/card-controller/actions/actions/ptz';
import { PTZControlsAction } from '../../../src/card-controller/actions/actions/ptz-controls';
@@ -28,13 +33,12 @@ import { StatusBarAction } from '../../../src/card-controller/actions/actions/st
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';
import { ViewAction } from '../../../src/card-controller/actions/actions/view';
import { ActionFactory } from '../../../src/card-controller/actions/factory';
import {
AdvancedCameraCardCustomAction,
INTERNAL_CALLBACK_ACTION,
} from '../../../src/config/types';
import { ActionConfig, INTERNAL_CALLBACK_ACTION } from '../../../src/config/types';
// @vitest-environment jsdom
describe('ActionFactory', () => {
@@ -55,23 +59,26 @@ describe('ActionFactory', () => {
).toBeNull();
});
describe('generic', () => {
it('non advanced camera card action', () => {
describe('stock actions', () => {
it.each([
[{ action: 'more-info' as const }, MoreInfoAction],
[{ action: 'toggle' as const }, ToggleAction],
[{ action: 'navigate' as const, navigation_path: '/foo' }, NavigateAction],
[{ action: 'url' as const, url_path: 'https://card.camera' }, URLAction],
[
{ action: 'perform-action' as const, perform_action: 'action' },
PerformActionAction,
],
[{ action: 'call-service' as const, service: 'service' }, CallServiceAction],
[{ action: 'none' as const }, NoneAction],
[{ action: 'fire-dom-event' as const }, CustomAction],
])('action: $action', (action: ActionConfig, classObject: object) => {
const factory = new ActionFactory();
expect(factory.createAction({}, { action: 'fire-dom-event' })).toBeInstanceOf(
GenericAction,
);
});
it('non fire-dom-event', () => {
const factory = new ActionFactory();
expect(factory.createAction({}, { action: 'more-info' })).toBeInstanceOf(
GenericAction,
);
expect(factory.createAction({}, action)).toBeInstanceOf(classObject);
});
});
describe('actions', () => {
describe('custom actions', () => {
it.each([
[{ advanced_camera_card_action: 'camera_select' as const }, CameraSelectAction],
[{ advanced_camera_card_action: 'camera_ui' as const }, CameraUIAction],
@@ -178,10 +185,10 @@ describe('ActionFactory', () => {
],
])(
'advanced_camera_card_action: $advanced_camera_card_action',
(action: Partial<AdvancedCameraCardCustomAction>, classObject: object) => {
(action: Partial<ActionConfig>, classObject: object) => {
const factory = new ActionFactory();
expect(
factory.createAction({}, { action: 'fire-dom-event', ...action }),
factory.createAction({}, { ...action, action: 'fire-dom-event' }),
).toBeInstanceOf(classObject);
},
);