test: Further improve test coverage (#2422)
This commit is contained in:
committed by
dermotduffy
parent
050c2f7c1a
commit
9384785d37
@@ -10,8 +10,9 @@ import { ActionHandlerDetail, ActionHandlerOptions } from './ha/types.js';
|
|||||||
import { stopEventFromActivatingCardWideActions } from './utils/action.js';
|
import { stopEventFromActivatingCardWideActions } from './utils/action.js';
|
||||||
import { Timer } from './utils/timer.js';
|
import { Timer } from './utils/timer.js';
|
||||||
|
|
||||||
interface ActionHandlerInterface extends HTMLElement {
|
export interface ActionHandlerInterface extends HTMLElement {
|
||||||
holdTime: number;
|
holdTime: number;
|
||||||
|
connectedCallback(): void;
|
||||||
bind(element: Element, options): void;
|
bind(element: Element, options): void;
|
||||||
}
|
}
|
||||||
interface ActionHandlerElement extends HTMLElement {
|
interface ActionHandlerElement extends HTMLElement {
|
||||||
@@ -55,16 +56,8 @@ class ActionHandler extends HTMLElement implements ActionHandlerInterface {
|
|||||||
element.actionHandlerOptions = options;
|
element.actionHandlerOptions = options;
|
||||||
|
|
||||||
element.addEventListener('contextmenu', (ev: Event) => {
|
element.addEventListener('contextmenu', (ev: Event) => {
|
||||||
const e = ev || window.event;
|
ev.preventDefault();
|
||||||
if (e.preventDefault) {
|
ev.stopPropagation();
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
if (e.stopPropagation) {
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
e.cancelBubble = true;
|
|
||||||
e.returnValue = false;
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const start = (): void => {
|
const start = (): void => {
|
||||||
@@ -167,11 +160,7 @@ const actionHandlerBind = (
|
|||||||
element: ActionHandlerElement,
|
element: ActionHandlerElement,
|
||||||
options?: AdvancedCameraCardActionHandlerOptions,
|
options?: AdvancedCameraCardActionHandlerOptions,
|
||||||
): void => {
|
): void => {
|
||||||
const actionhandler: ActionHandler = getActionHandler();
|
getActionHandler().bind(element, options);
|
||||||
if (!actionhandler) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
actionhandler.bind(element, options);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionHandler = directive(
|
export const actionHandler = directive(
|
||||||
@@ -181,6 +170,7 @@ export const actionHandler = directive(
|
|||||||
return noChange;
|
return noChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// istanbul ignore next -- @preserve Required by Lit Directive API but never called (update() is used instead)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
render(_options?: AdvancedCameraCardActionHandlerOptions) {}
|
render(_options?: AdvancedCameraCardActionHandlerOptions) {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
import { html, render } from 'lit';
|
||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { ActionHandlerInterface, actionHandler } from '../src/action-handler-directive';
|
||||||
|
import { fireHASSEvent } from '../src/ha/fire-hass-event';
|
||||||
|
import { ActionHandlerDetail } from '../src/ha/types';
|
||||||
|
import { stopEventFromActivatingCardWideActions } from '../src/utils/action';
|
||||||
|
|
||||||
|
vi.mock('../src/ha/fire-hass-event.js');
|
||||||
|
vi.mock('../src/utils/action.js');
|
||||||
|
|
||||||
|
// @vitest-environment jsdom
|
||||||
|
const getActionHandler = (): ActionHandlerInterface => {
|
||||||
|
const existing = document.body.querySelector('action-handler-advanced-camera-card');
|
||||||
|
if (existing) {
|
||||||
|
return existing as ActionHandlerInterface;
|
||||||
|
}
|
||||||
|
const el = document.createElement('action-handler-advanced-camera-card');
|
||||||
|
document.body.appendChild(el);
|
||||||
|
return el as ActionHandlerInterface;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBoundElement = (options?: Record<string, unknown>): HTMLElement => {
|
||||||
|
const handler = getActionHandler();
|
||||||
|
const element = document.createElement('div');
|
||||||
|
handler.bind(element, options);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ActionHandler', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('connectedCallback', () => {
|
||||||
|
it('should stop hold timer on document mouse/touch events', () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const handler = getActionHandler();
|
||||||
|
handler.connectedCallback();
|
||||||
|
|
||||||
|
const element = createBoundElement({ hasHold: true });
|
||||||
|
|
||||||
|
// Start a hold via mousedown.
|
||||||
|
element.dispatchEvent(new MouseEvent('mousedown'));
|
||||||
|
|
||||||
|
// A document-level mouseup should cancel the hold timer.
|
||||||
|
document.dispatchEvent(new MouseEvent('mouseup'));
|
||||||
|
|
||||||
|
// Advance past hold time — hold should NOT have triggered.
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('click'));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('bind', () => {
|
||||||
|
it('should update options on re-bind without re-registering listeners', () => {
|
||||||
|
const handler = getActionHandler();
|
||||||
|
const element = document.createElement('div');
|
||||||
|
|
||||||
|
handler.bind(element, { hasHold: false });
|
||||||
|
handler.bind(element, { hasHold: true });
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(element as unknown as { actionHandlerOptions: unknown }).actionHandlerOptions,
|
||||||
|
).toEqual({
|
||||||
|
hasHold: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should suppress contextmenu default behavior', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
const ev = new MouseEvent('contextmenu', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
const preventDefault = vi.spyOn(ev, 'preventDefault');
|
||||||
|
const stopPropagation = vi.spyOn(ev, 'stopPropagation');
|
||||||
|
|
||||||
|
element.dispatchEvent(ev);
|
||||||
|
|
||||||
|
expect(preventDefault).toHaveBeenCalled();
|
||||||
|
expect(stopPropagation).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('tap', () => {
|
||||||
|
it('should fire tap on click', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
element.dispatchEvent(new MouseEvent('click'));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire start_tap on mousedown and end_tap on click', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('mousedown'));
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'start_tap' }),
|
||||||
|
);
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('click'));
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'end_tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not duplicate start_tap from touchstart then mousedown', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
|
||||||
|
element.dispatchEvent(new TouchEvent('touchstart'));
|
||||||
|
element.dispatchEvent(new MouseEvent('mousedown'));
|
||||||
|
|
||||||
|
const calls = vi.mocked(fireHASSEvent).mock.calls;
|
||||||
|
const startTapCalls = calls.filter(
|
||||||
|
([, , detail]) => (detail as ActionHandlerDetail)?.action === 'start_tap',
|
||||||
|
);
|
||||||
|
expect(startTapCalls).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire tap on Enter keyup', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
|
||||||
|
element.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fire tap on non-Enter keyup', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
|
||||||
|
element.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).not.toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hold', () => {
|
||||||
|
it('should fire hold after hold time', () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const element = createBoundElement({ hasHold: true });
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('mousedown'));
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
element.dispatchEvent(new MouseEvent('click'));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'hold' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire tap when released before hold time', () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const element = createBoundElement({ hasHold: true });
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('mousedown'));
|
||||||
|
vi.advanceTimersByTime(100);
|
||||||
|
element.dispatchEvent(new MouseEvent('click'));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'tap' }),
|
||||||
|
);
|
||||||
|
expect(fireHASSEvent).not.toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'hold' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('double click', () => {
|
||||||
|
it('should fire double_tap on rapid clicks', () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const element = createBoundElement({ hasDoubleClick: true });
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('click', { detail: 1 }));
|
||||||
|
element.dispatchEvent(new MouseEvent('click', { detail: 2 }));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'double_tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire tap after double click timeout', () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const element = createBoundElement({ hasDoubleClick: true });
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('click', { detail: 1 }));
|
||||||
|
vi.advanceTimersByTime(300);
|
||||||
|
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('touch events', () => {
|
||||||
|
it('should not fire tap on touchend without hold', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
|
||||||
|
element.dispatchEvent(new TouchEvent('touchstart'));
|
||||||
|
element.dispatchEvent(new TouchEvent('touchend'));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).not.toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire hold on touchend after hold time', () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const element = createBoundElement({ hasHold: true });
|
||||||
|
|
||||||
|
element.dispatchEvent(new TouchEvent('touchstart'));
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
element.dispatchEvent(new TouchEvent('touchend'));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'hold' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fire tap on touchcancel without hold', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
|
||||||
|
element.dispatchEvent(new TouchEvent('touchstart'));
|
||||||
|
element.dispatchEvent(new TouchEvent('touchcancel'));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).not.toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('propagation', () => {
|
||||||
|
it('should stop propagation by default', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
element.dispatchEvent(new MouseEvent('click'));
|
||||||
|
|
||||||
|
expect(stopEventFromActivatingCardWideActions).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow propagation when configured', () => {
|
||||||
|
const element = createBoundElement({ allowPropagation: true });
|
||||||
|
element.dispatchEvent(new MouseEvent('click'));
|
||||||
|
|
||||||
|
expect(stopEventFromActivatingCardWideActions).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mouseleave', () => {
|
||||||
|
it('should fire end_tap on mouseleave after mousedown', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('mousedown'));
|
||||||
|
vi.mocked(fireHASSEvent).mockClear();
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('mouseleave'));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'end_tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fire end_tap on mouseleave without prior mousedown', () => {
|
||||||
|
const element = createBoundElement();
|
||||||
|
|
||||||
|
element.dispatchEvent(new MouseEvent('mouseleave'));
|
||||||
|
|
||||||
|
expect(fireHASSEvent).not.toHaveBeenCalledWith(
|
||||||
|
element,
|
||||||
|
'action',
|
||||||
|
expect.objectContaining({ action: 'end_tap' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('actionHandler directive', () => {
|
||||||
|
it('should create action handler element and bind via Lit rendering', () => {
|
||||||
|
const existing = document.body.querySelector('action-handler-advanced-camera-card');
|
||||||
|
if (existing) {
|
||||||
|
existing.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
render(html`<div ${actionHandler()}></div>`, container);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
document.body.querySelector('action-handler-advanced-camera-card'),
|
||||||
|
).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reuse existing action handler element', () => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
render(html`<div ${actionHandler()}></div>`, container);
|
||||||
|
render(html`<div ${actionHandler()}></div>`, container);
|
||||||
|
|
||||||
|
const handlers = document.body.querySelectorAll(
|
||||||
|
'action-handler-advanced-camera-card',
|
||||||
|
);
|
||||||
|
expect(handlers).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
Vendored
+78
@@ -0,0 +1,78 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { CacheBase } from '../../src/cache/base';
|
||||||
|
|
||||||
|
describe('CacheBase', () => {
|
||||||
|
describe('has', () => {
|
||||||
|
it('should return true when key exists', () => {
|
||||||
|
const cache = new CacheBase(new Map([['a', 1]]));
|
||||||
|
expect(cache.has('a')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when key is absent', () => {
|
||||||
|
const cache = new CacheBase<string, number>(new Map());
|
||||||
|
expect(cache.has('a')).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get', () => {
|
||||||
|
it('should return value when key exists', () => {
|
||||||
|
const cache = new CacheBase(new Map([['a', 1]]));
|
||||||
|
expect(cache.get('a')).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when key is absent', () => {
|
||||||
|
const cache = new CacheBase<string, number>(new Map());
|
||||||
|
expect(cache.get('a')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set a value', () => {
|
||||||
|
const cache = new CacheBase<string, number>(new Map());
|
||||||
|
cache.set('a', 1);
|
||||||
|
expect(cache.get('a')).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a key', () => {
|
||||||
|
const cache = new CacheBase(new Map([['a', 1]]));
|
||||||
|
expect(cache.delete('a')).toBeTruthy();
|
||||||
|
expect(cache.has('a')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear all entries', () => {
|
||||||
|
const cache = new CacheBase(
|
||||||
|
new Map([
|
||||||
|
['a', 1],
|
||||||
|
['b', 2],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.clear();
|
||||||
|
|
||||||
|
expect(cache.has('a')).toBeFalsy();
|
||||||
|
expect(cache.has('b')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return matching entries', () => {
|
||||||
|
const cache = new CacheBase(
|
||||||
|
new Map([
|
||||||
|
['a', 1],
|
||||||
|
['b', 2],
|
||||||
|
['c', 3],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(cache.getMatches((v) => v >= 2)).toEqual([2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should iterate entries', () => {
|
||||||
|
const cache = new CacheBase(
|
||||||
|
new Map([
|
||||||
|
['a', 1],
|
||||||
|
['b', 2],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect([...cache.entries()]).toEqual([
|
||||||
|
['a', 1],
|
||||||
|
['b', 2],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { hasUnsupportedFilters } from '../src/query-source';
|
||||||
|
|
||||||
|
describe('hasUnsupportedFilters', () => {
|
||||||
|
it('should return false for empty query', () => {
|
||||||
|
expect(hasUnsupportedFilters({})).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when all filters are supported', () => {
|
||||||
|
const result = hasUnsupportedFilters(
|
||||||
|
{
|
||||||
|
favorite: true,
|
||||||
|
tags: new Set(['tag']),
|
||||||
|
what: new Set(['person']),
|
||||||
|
where: new Set(['yard']),
|
||||||
|
reviewed: false,
|
||||||
|
severity: new Set(['high' as const]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
favorite: true,
|
||||||
|
tags: true,
|
||||||
|
what: true,
|
||||||
|
where: true,
|
||||||
|
reviewed: true,
|
||||||
|
severity: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('should detect unsupported filter', () => {
|
||||||
|
it('favorite', () => {
|
||||||
|
expect(hasUnsupportedFilters({ favorite: true })).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tags', () => {
|
||||||
|
expect(hasUnsupportedFilters({ tags: new Set(['tag']) })).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('what', () => {
|
||||||
|
expect(hasUnsupportedFilters({ what: new Set(['person']) })).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('where', () => {
|
||||||
|
expect(hasUnsupportedFilters({ where: new Set(['yard']) })).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reviewed', () => {
|
||||||
|
expect(hasUnsupportedFilters({ reviewed: false })).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('severity', () => {
|
||||||
|
expect(
|
||||||
|
hasUnsupportedFilters({
|
||||||
|
severity: new Set(['high' as const]),
|
||||||
|
}),
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('should ignore empty sets', () => {
|
||||||
|
it('tags', () => {
|
||||||
|
expect(hasUnsupportedFilters({ tags: new Set() })).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('what', () => {
|
||||||
|
expect(hasUnsupportedFilters({ what: new Set() })).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('where', () => {
|
||||||
|
expect(hasUnsupportedFilters({ where: new Set() })).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('severity', () => {
|
||||||
|
expect(hasUnsupportedFilters({ severity: new Set() })).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+18
-37
@@ -1,49 +1,24 @@
|
|||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
// These globs are expected to have 100% coverage.
|
|
||||||
const FULL_COVERAGE = [
|
|
||||||
'src/camera-manager/**/*.ts',
|
|
||||||
'src/card-controller/**/*.ts',
|
|
||||||
'src/components-lib/**/*.ts',
|
|
||||||
'src/conditions/**/*.ts',
|
|
||||||
'src/config/**/*.ts',
|
|
||||||
'src/const.ts',
|
|
||||||
'src/ha/**/*.ts',
|
|
||||||
'src/localize/**/*.ts',
|
|
||||||
'src/types.ts',
|
|
||||||
'src/utils/**/*.ts',
|
|
||||||
'src/view/*.ts',
|
|
||||||
];
|
|
||||||
|
|
||||||
const EXCLUSIONS = [
|
const EXCLUSIONS = [
|
||||||
'.eslintrc.cjs',
|
'.eslintrc.cjs',
|
||||||
'docs/**',
|
'docs/**',
|
||||||
'src/components-lib/timeline/controller.ts',
|
|
||||||
'tests/**',
|
'tests/**',
|
||||||
|
|
||||||
|
// Web-components.
|
||||||
|
'src/card.ts',
|
||||||
|
'src/components/**/*.ts',
|
||||||
|
'src/editor.ts',
|
||||||
|
|
||||||
|
// Timeline controller (can be added later).
|
||||||
|
'src/components-lib/timeline/controller.ts',
|
||||||
|
|
||||||
|
// HA patches.
|
||||||
|
'src/patches/**/*.ts',
|
||||||
];
|
];
|
||||||
|
|
||||||
const INCLUSIONS = ['tests/**/*.test.ts'];
|
const INCLUSIONS = ['tests/**/*.test.ts'];
|
||||||
|
|
||||||
interface Threshold {
|
|
||||||
statements: number;
|
|
||||||
branches: number;
|
|
||||||
functions: number;
|
|
||||||
lines: number;
|
|
||||||
perFile: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullCoverage: Threshold = {
|
|
||||||
statements: 100,
|
|
||||||
branches: 100,
|
|
||||||
functions: 100,
|
|
||||||
lines: 100,
|
|
||||||
perFile: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateFullCoverageThresholds = (): Record<string, Threshold> => {
|
|
||||||
return FULL_COVERAGE.reduce((a, v) => ({ ...a, [v]: fullCoverage }), {});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ts-prune-ignore-next
|
// ts-prune-ignore-next
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
@@ -61,7 +36,13 @@ export default defineConfig({
|
|||||||
// Favor istanbul for coverage over v8 due to better accuracy.
|
// Favor istanbul for coverage over v8 due to better accuracy.
|
||||||
provider: 'istanbul',
|
provider: 'istanbul',
|
||||||
thresholds: {
|
thresholds: {
|
||||||
...calculateFullCoverageThresholds(),
|
perFile: true,
|
||||||
|
'src/**/*.ts': {
|
||||||
|
statements: 100,
|
||||||
|
branches: 100,
|
||||||
|
functions: 100,
|
||||||
|
lines: 100,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user