Add support for more advanced forms of overriding

This commit is contained in:
Dermot Duffy
2024-04-13 18:23:11 -07:00
parent 0eca5d780c
commit 323925101d
21 changed files with 855 additions and 285 deletions
+150 -23
View File
@@ -1,39 +1,166 @@
# `overrides`
Various parts of card configuration may [conditionally](conditions.md) be
overridden (e.g. to hide the menu in fullscreen mode).
The card configuration may [conditionally](conditions.md) be overridden (e.g. to
hide the menu in fullscreen mode).
```yaml
overrides:
- conditions:
[condition]
overrides:
[override]
[...]
```
Not all configuration parameters are overriddable, some because it doesn't make
sense for that parameter to vary, and many because of the extra complexity of
supporting overriding given the lack of compelling usecases ([please request new
overridable parameters
here!](https://github.com/dermotduffy/frigate-hass-card/issues/new/choose)).
!> Whilst all configuration parameters are theoretically overridable, in some instances a configuration variable may only be consulted on startup or changing its value may negatively impact behavior -- override results may vary!
Each entry under the top-level `overrides` configuration block should be a list
item, that has both of the following parameters set:
The top-level `overrides` configuration block expects a list, with each list
item containing `conditions` and at least one of `merge`, `delete` or `set` specified.
| Option | Default | Description |
| - | - | - |
| `conditions` | | A list of [conditions](conditions.md) that must evaluate to `true` in order for the overrides to be applied. |
| `overrides` | | Configuration overrides to be applied. Any configuration parameter matching [Overrideable parameters](overrides.md?id=overrideable-parameters) can be overridden. |
| `delete` | | An array of configuration paths to delete. See below. |
| `merge` | | A dictionary of configuration paths to merge. See below. |
| `set` | | A dictionary of configuration paths to set. See below. |
## Overrideable parameters
## Configuration Paths
| Configuration Key | Overrideable |
| - | - |
| [`cameras.*`](cameras/README.md) | :white_check_mark: |
| [`cameras_global.*`](cameras/README.md) | :white_check_mark: |
| [`dimensions.*`](dimensions.md) | :white_check_mark: |
| [`image.*`](image.md) | :white_check_mark: |
| [`live.controls.*`](live.md?id=controls), [`live.display.*`](live.md?id=display), [`live.microphone.*`](live.md?id=microphone), [`live.show_image_during_load`](live.md), [`live.zoomable`](live.md) | :white_check_mark: |
| [`menu.*`](menu.md) | :white_check_mark: |
| [`view.*`](view.md) | :white_check_mark: |
| *(Everything else)* | :heavy_multiplication_x: |
The `delete`, `merge` and `set` parameters take configuration paths. Paths are dot-separated references to particular configuration parameters. To refer to list elements use `[n]` notation.
For example the path `cameras[1].dimensions.aspect_ratio` refers to the `aspect_ratio` parameter below:
```yaml
cameras:
- camera_entity: camera.other
- camera_entity: camera.relevant
dimensions:
aspect_ratio: '16:9'
```
## `delete`
An array of configuration paths to delete.
```yaml
overrides:
- conditions:
[condition]
delete:
- [path_1]
- [path_2]
```
### Examples
Delete the 2nd camera:
```yaml
overrides:
- conditions:
[condition]
delete:
'cameras[2]'
```
Delete the menu style parameter, thus falling back to the default:
```yaml
overrides:
- conditions:
[condition]
delete:
'menu.style'
```
## `merge`
Specifies an object to recursively merge into existing configuration.
| Option | Default | Description |
| - | - | - |
| [configuration path] | | Arbitrary configuration object to merge. Must be an object (i.e. not a literal value). |
### Examples
Hide the menu when a given condition is met:
```yaml
overrides:
- conditions:
[condition]
merge:
menu: {
style: 'hidden'
}
```
Enable thumbnails below the `live` feed:
```yaml
overrides:
- conditions:
[condition]
merge:
'live.controls.thumbnails': {
mode: 'below'
}
```
Also enables thumbnails below the `live` feed, but without using the dot-separated notation:
```yaml
overrides:
- conditions:
[condition]
merge:
live: {
controls: {
thumbnails: {
mode: 'below'
}
}
}
```
## `set`
Specifies a value to set in the configuration. This differs from `merge` in that the existing value is entirely replaced.
| Option | Default | Description |
| - | - | - |
| [configuration path] | | Arbitrary configuration value / object / list to set. |
### Examples
Set the entire menu configuration to defaults with the exception of the `style` which is set to `overlay`.
```yaml
overrides:
- conditions:
[condition]
set:
menu: {
style: 'overlay'
}
```
Set the menu style but without touching the other `menu` parameters:
```yaml
overrides:
- conditions:
[condition]
set:
'menu.style': 'overlay'
```
That is equivalent to merging the following:
```yaml
overrides:
- conditions:
[condition]
merge:
menu: {
style: 'overlay'
}
```
+25 -7
View File
@@ -424,7 +424,7 @@ overrides:
- condition: state
entity: light.office_main_lights
state: 'on'
overrides:
merge:
menu:
position: bottom
```
@@ -451,7 +451,7 @@ overrides:
- condition: state
entity: binary_sensor.alarm_armed
state: 'off'
overrides:
merge:
view:
default: image
```
@@ -471,7 +471,7 @@ overrides:
fullscreen: true
- condition: display_mode
display_mode: grid
overrides:
merge:
live:
display:
grid_columns: 5
@@ -497,7 +497,7 @@ overrides:
- conditions:
- condition: expand
expand: true
overrides:
merge:
menu:
style: overlay
```
@@ -520,11 +520,29 @@ overrides:
- conditions:
- condition: fullscreen
fullscreen: true
overrides:
merge:
menu:
style: none
```
### Remove a camera when an entity state changes
This example removes a camera from the card when an entity is disabled (e.g. a switch controlling power to the camera).
```yaml
type: custom:frigate-card
cameras:
- camera_entity: camera.office
- camera_entity: camera.kitchen
overrides:
- conditions:
- condition: state
entity: switch.kitchen_camera_power
state: off
delete:
- 'cameras[1]'
```
## PTZ control
The card supports using PTZ controls to conveniently control pan, tilt and zoom for cameras. This example shows the PTZ controls on the `live` view. Note that if your camera engine supports it (e.g. `frigate`) this will just work out of the box with no configuration at all.
@@ -568,7 +586,7 @@ overrides:
- conditions:
- condition: screen
media_query: '(orientation: landscape)'
overrides:
merge:
menu:
position: left
```
@@ -584,7 +602,7 @@ overrides:
- conditions:
- condition: screen
media_query: '(max-width: 300px)'
overrides:
merge:
menu:
style: none
live:
+2 -2
View File
@@ -61,7 +61,7 @@
"@types/masonry-layout": "^4.2.5",
"@typescript-eslint/eslint-plugin": "^5.36.2",
"@typescript-eslint/parser": "^5.36.2",
"@vitest/coverage-istanbul": "^1.3.1",
"@vitest/coverage-istanbul": "^1.5.0",
"docsify-cli": "^4.4.4",
"eslint": "^8.23.0",
"eslint-config-airbnb-base": "^15.0.0",
@@ -81,7 +81,7 @@
"sass": "^1.54.9",
"ts-prune": "^0.10.3",
"typescript": "^4.9.5",
"vitest": "^1.3.1",
"vitest": "^1.5.0",
"vitest-mock-extended": "^1.3.1"
},
"scripts": {
+1 -1
View File
@@ -145,7 +145,7 @@ export class CameraManager {
// order, to ensure that the defaults in the cameras global config do not
// override the values specified in the per-camera config.
const cameras = config.cameras.map((camera) =>
recursivelyMergeObjectsNotArrays(cloneDeep(config?.cameras_global), camera),
recursivelyMergeObjectsNotArrays({}, cloneDeep(config?.cameras_global), camera),
);
try {
+60 -31
View File
@@ -1,14 +1,21 @@
import { CurrentUser } from '@dermotduffy/custom-card-helpers';
import { HassEntities } from 'home-assistant-js-websocket';
import merge from 'lodash-es/merge';
import { copyConfig } from '../config/management';
import { ZodSchema } from 'zod';
import {
copyConfig,
deleteConfigValue,
getConfigValue,
setConfigValue,
} from '../config/management';
import {
FrigateCardCondition,
frigateConditionalSchema,
OverrideConfigurationKey,
RawFrigateCardConfig,
ViewDisplayMode,
frigateConditionalSchema,
Overrides,
} from '../config/types';
import { desparsifyArrays } from '../utils/basic';
import { CardConditionAPI } from './types';
interface MicrophoneConditionState {
@@ -73,44 +80,66 @@ export function evaluateConditionViaEvent(
return evaluateEvent.evaluation ?? false;
}
type RawOverrides = {
conditions: FrigateCardCondition[];
overrides: RawFrigateCardConfig;
}[];
export function getOverriddenConfig(
manager: Readonly<ConditionsManager>,
config: Readonly<RawFrigateCardConfig>,
configOverrides?: Readonly<RawOverrides>,
stateOverrides?: Partial<ConditionState>,
options?: {
configOverrides?: Readonly<Overrides>;
stateOverrides?: Partial<ConditionState>;
schema?: ZodSchema;
logOnParseError?: boolean;
},
): RawFrigateCardConfig {
const output = copyConfig(config);
let output = copyConfig(config);
let overridden = false;
if (configOverrides) {
for (const override of configOverrides) {
if (manager.evaluateConditions(override.conditions, stateOverrides)) {
merge(output, override.overrides);
if (options?.configOverrides) {
for (const override of options.configOverrides) {
if (manager.evaluateConditions(override.conditions, options?.stateOverrides)) {
override.delete?.forEach((deletionKey) => {
deleteConfigValue(output, deletionKey);
});
Object.keys(override.set ?? {}).forEach((setKey) => {
setConfigValue(output, setKey, override.set?.[setKey]);
});
Object.keys(override.merge ?? {}).forEach((mergeKey) => {
setConfigValue(
output,
mergeKey,
merge({}, getConfigValue(output, mergeKey), override.merge?.[mergeKey]),
);
});
overridden = true;
}
}
}
// Attempt to return the same configuration object if it has not been
// overridden (to reduce re-renders for a configuration that has not changed).
return overridden ? output : config;
}
export function getOverridesByKey(
key: OverrideConfigurationKey,
overrides?: Readonly<RawOverrides>,
): RawOverrides {
return (
overrides
?.filter((o) => key in o.overrides)
.map((o) => ({
conditions: o.conditions,
overrides: o.overrides[key] as RawFrigateCardConfig,
})) ?? []
);
if (!overridden) {
// Attempt to return the same configuration object if it has not been
// overridden (to reduce re-renders for a configuration that has not changed).
return config;
}
if (options?.configOverrides?.some((override) => override.delete?.length)) {
// If anything was deleted during this override, empty undefined slots may
// be left in arrays where values were unset. Desparsify them.
output = desparsifyArrays(output);
}
if (options?.schema) {
const parseResult = options.schema.safeParse(output);
if (options.logOnParseError && !parseResult.success) {
console.warn(
`Cannot parse overridden configuration`,
output,
parseResult.error.message,
);
}
return parseResult.success ? parseResult.data : config;
}
return output;
}
// A tiny wrapper interface to allow the same manager to be passed around
+6 -6
View File
@@ -4,7 +4,7 @@ import {
CardWideConfig,
FrigateCardConfig,
frigateCardConfigSchema,
RawFrigateCardConfig
RawFrigateCardConfig,
} from '../config/types';
import { localize } from '../localize/localize';
import { setProfiles } from '../config/profiles';
@@ -104,11 +104,11 @@ export class ConfigManager {
if (!this._config) {
return;
}
const overriddenConfig = getOverriddenConfig(
conditionsManager,
this._config,
this._config.overrides,
) as FrigateCardConfig;
const overriddenConfig = getOverriddenConfig(conditionsManager, this._config, {
configOverrides: this._config.overrides,
schema: frigateCardConfigSchema,
logOnParseError: !!this.getCardWideConfig()?.debug?.logging,
}) as FrigateCardConfig;
// Save on Lit re-rendering costs by only updating the configuration if it
// actually changes.
+18 -13
View File
@@ -26,8 +26,9 @@ import {
CardWideConfig,
frigateCardConfigDefaults,
LiveConfig,
LiveOverrides,
liveConfigAbsoluteRootSchema,
LiveProvider,
Overrides,
TransitionEffect,
} from '../../config/types.js';
import { localize } from '../../localize/localize.js';
@@ -80,7 +81,7 @@ export class FrigateCardLive extends LitElement {
public overriddenLiveConfig?: LiveConfig;
@property({ attribute: false, hasChanged: contentsChanged })
public liveOverrides?: LiveOverrides;
public overrides?: Overrides;
@property({ attribute: false })
public cameraManager?: CameraManager;
@@ -151,7 +152,7 @@ export class FrigateCardLive extends LitElement {
.overriddenLiveConfig=${this.overriddenLiveConfig}
.inBackground=${this._controller.isInBackground()}
.conditionsManagerEpoch=${this.conditionsManagerEpoch}
.liveOverrides=${this.liveOverrides}
.overrides=${this.overrides}
.cardWideConfig=${this.cardWideConfig}
.cameraManager=${this.cameraManager}
.microphoneManager=${this.microphoneManager}
@@ -182,7 +183,7 @@ export class FrigateCardLiveGrid extends LitElement {
public overriddenLiveConfig?: LiveConfig;
@property({ attribute: false, hasChanged: contentsChanged })
public liveOverrides?: LiveOverrides;
public overrides?: Overrides;
@property({ attribute: false })
public conditionsManagerEpoch?: ConditionsManagerEpoch;
@@ -211,7 +212,7 @@ export class FrigateCardLiveGrid extends LitElement {
.nonOverriddenLiveConfig=${this.nonOverriddenLiveConfig}
.overriddenLiveConfig=${this.overriddenLiveConfig}
.conditionsManagerEpoch=${this.conditionsManagerEpoch}
.liveOverrides=${this.liveOverrides}
.overrides=${this.overrides}
.cardWideConfig=${this.cardWideConfig}
.cameraManager=${this.cameraManager}
.microphoneManager=${this.microphoneManager}
@@ -290,7 +291,7 @@ export class FrigateCardLiveCarousel extends LitElement {
public overriddenLiveConfig?: LiveConfig;
@property({ attribute: false, hasChanged: contentsChanged })
public liveOverrides?: LiveOverrides;
public overrides?: Overrides;
@property({ attribute: false })
public conditionsManagerEpoch?: ConditionsManagerEpoch;
@@ -471,19 +472,23 @@ export class FrigateCardLiveCarousel extends LitElement {
// (in the carousel for example) is not necessarily the live camera *this*
// <frigate-card-live-provider> is rendering right now, so we provide a
// stateOverride to evaluate the condition in that context.
const config = getOverriddenConfig(
const liveConfig = getOverriddenConfig(
this.conditionsManagerEpoch.manager,
this.nonOverriddenLiveConfig,
this.liveOverrides,
{ camera: cameraID },
) as LiveConfig;
{ live: this.nonOverriddenLiveConfig },
{
configOverrides: this.overrides,
stateOverrides: { camera: cameraID },
schema: liveConfigAbsoluteRootSchema,
logOnParseError: !!this.cardWideConfig?.debug?.logging,
},
).live as LiveConfig;
const cameraMetadata = this.cameraManager.getCameraMetadata(cameraID);
return html`
<div class="embla__slide">
<frigate-card-live-provider
?load=${!config.lazy_load}
?load=${!liveConfig.lazy_load}
.microphoneStream=${this.view?.camera === cameraID
? this.microphoneManager?.getStream()
: undefined}
@@ -493,7 +498,7 @@ export class FrigateCardLiveCarousel extends LitElement {
() => this.cameraManager?.getCameraEndpoints(cameraID) ?? undefined,
)}
.label=${cameraMetadata?.title ?? ''}
.liveConfig=${config}
.liveConfig=${liveConfig}
.hass=${this.hass}
.cardWideConfig=${this.cardWideConfig}
>
+2 -12
View File
@@ -9,10 +9,7 @@ import {
import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { CameraManager } from '../camera-manager/manager.js';
import {
ConditionsManagerEpoch,
getOverridesByKey,
} from '../card-controller/conditions-manager.js';
import { ConditionsManagerEpoch } from '../card-controller/conditions-manager.js';
import { ReadonlyMicrophoneManager } from '../card-controller/microphone-manager.js';
import {
CardWideConfig,
@@ -233,10 +230,7 @@ export class FrigateCardViews extends LitElement {
.nonOverriddenLiveConfig=${this.nonOverriddenConfig.live}
.overriddenLiveConfig=${this.overriddenConfig.live}
.conditionsManagerEpoch=${this.conditionsManagerEpoch}
.liveOverrides=${getOverridesByKey(
'live',
this.overriddenConfig.overrides,
)}
.overrides=${this.overriddenConfig.overrides}
.cameraManager=${this.cameraManager}
.cardWideConfig=${this.cardWideConfig}
.microphoneManager=${this.microphoneManager}
@@ -248,10 +242,6 @@ export class FrigateCardViews extends LitElement {
: ``
}
</frigate-card-surround>`;
// .fetchMediaType=${this.view?.is('live') ? this.overriddenConfig.live.controls.thumbnails.media_type : undefined}
// .fetchEventsMediaType=${this.view?.is('live') ? this.overriddenConfig.live.controls.thumbnails.events_media_type : undefined}
}
static get styles(): CSSResultGroup {
+1 -1
View File
@@ -42,7 +42,6 @@ import { arrayify } from '../utils/basic';
* @param keys The key to the property to set.
* @param value The value to set.
*/
export const setConfigValue = (
obj: RawFrigateCardConfig,
keys: string | (string | number)[],
@@ -689,4 +688,5 @@ const UPGRADES = [
// Delete the value if it's set to the default.
transform: (val) => (val === 'low' ? ['low-performance'] : null),
}),
upgradeArrayOfObjects(CONF_OVERRIDES, upgradeMoveTo('overrides', 'merge')),
];
+38 -51
View File
@@ -1008,8 +1008,24 @@ const livethumbnailsControlSchema = thumbnailsControlSchema.extend({
),
});
const liveOverridableConfigSchema = z
const liveConfigSchema = z
.object({
auto_pause: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.array()
.default(liveConfigDefault.auto_pause),
auto_play: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.array()
.default(liveConfigDefault.auto_play),
auto_mute: z
.enum(MEDIA_MUTE_CONDITIONS)
.array()
.default(liveConfigDefault.auto_mute),
auto_unmute: z
.enum(MEDIA_UNMUTE_CONDITIONS)
.array()
.default(liveConfigDefault.auto_unmute),
controls: z
.object({
builtin: z.boolean().default(liveConfigDefault.controls.builtin),
@@ -1032,55 +1048,36 @@ const liveOverridableConfigSchema = z
title: titleControlConfigSchema.optional(),
})
.default(liveConfigDefault.controls),
show_image_during_load: z
.boolean()
.default(liveConfigDefault.show_image_during_load),
microphone: microphoneConfigSchema.default(liveConfigDefault.microphone),
zoomable: z.boolean().default(liveConfigDefault.zoomable),
display: viewDisplaySchema,
})
.merge(actionsSchema);
const liveConfigSchema = liveOverridableConfigSchema
.extend({
auto_play: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.array()
.default(liveConfigDefault.auto_play),
auto_pause: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.array()
.default(liveConfigDefault.auto_pause),
auto_mute: z
.enum(MEDIA_MUTE_CONDITIONS)
.array()
.default(liveConfigDefault.auto_mute),
auto_unmute: z
.enum(MEDIA_UNMUTE_CONDITIONS)
.array()
.default(liveConfigDefault.auto_unmute),
preload: z.boolean().default(liveConfigDefault.preload),
draggable: z.boolean().default(liveConfigDefault.draggable),
lazy_load: z.boolean().default(liveConfigDefault.lazy_load),
lazy_unload: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.array()
.default(liveConfigDefault.lazy_unload),
draggable: z.boolean().default(liveConfigDefault.draggable),
microphone: microphoneConfigSchema.default(liveConfigDefault.microphone),
preload: z.boolean().default(liveConfigDefault.preload),
show_image_during_load: z
.boolean()
.default(liveConfigDefault.show_image_during_load),
transition_effect: transitionEffectConfigSchema.default(
liveConfigDefault.transition_effect,
),
zoomable: z.boolean().default(liveConfigDefault.zoomable),
})
.merge(actionsSchema)
.default(liveConfigDefault);
export type LiveConfig = z.infer<typeof liveConfigSchema>;
const liveOverridesSchema = z
.object({
conditions: frigateCardConditionSchema.array(),
overrides: liveOverridableConfigSchema,
})
.array()
.optional();
export type LiveOverrides = z.infer<typeof liveOverridesSchema>;
// This schema is used when the live config needs to be overridden (see
// `live.ts`). Overrides will always be "relative" to the config root, so this
// schema maintains that 'depth' from the root but without the other
// requirements that frigateCardConfigSchema has. Without this, overrides
// calculated in `live.ts` would fail since cameras/type are not provided (as
// these are mandatory parameters in the full config).
export const liveConfigAbsoluteRootSchema = z.object({
live: liveConfigSchema,
});
// *************************************************************************
// Cast Configuration
@@ -1580,26 +1577,16 @@ export const dimensionsConfigSchema = z
// Override Configuration
// *************************************************************************
// Strip all defaults from the override schemas, to ensure values are only what
// the user has specified.
const overrideConfigurationSchema = z.object({
cameras: deepRemoveDefaults(camerasConfigSchema).optional(),
cameras_global: deepRemoveDefaults(cameraConfigSchema).optional(),
live: deepRemoveDefaults(liveOverridableConfigSchema).optional(),
menu: deepRemoveDefaults(menuConfigSchema).optional(),
image: deepRemoveDefaults(imageConfigSchema).optional(),
view: deepRemoveDefaults(viewConfigSchema).optional(),
dimensions: deepRemoveDefaults(dimensionsConfigSchema).optional(),
});
export type OverrideConfigurationKey = keyof z.infer<typeof overrideConfigurationSchema>;
const overridesSchema = z
.object({
conditions: frigateCardConditionSchema.array(),
overrides: overrideConfigurationSchema,
merge: z.object({}).passthrough().optional(),
set: z.object({}).passthrough().optional(),
delete: z.string().array().optional(),
})
.array()
.optional();
export type Overrides = z.infer<typeof overridesSchema>;
// *************************************************************************
// Automation Configuration
+20 -2
View File
@@ -243,8 +243,8 @@ export const getChildrenFromElement = (parent: HTMLElement): HTMLElement[] => {
return children.filter(isHTMLElement);
};
export const recursivelyMergeObjectsNotArrays = <T>(src1: T, src2: T): T => {
return mergeWith({}, src1, src2, (_a, b) => (Array.isArray(b) ? b : undefined));
export const recursivelyMergeObjectsNotArrays = <T>(target: T, src1: T, src2: T): T => {
return mergeWith(target, src1, src2, (_a, b) => (Array.isArray(b) ? b : undefined));
};
export const aspectRatioToString = (options?: {
@@ -268,3 +268,21 @@ export const aspectRatioToStyle = (options?: {
'aspect-ratio': aspectRatioToString(options),
};
};
/**
* Remove empty slots from nested arrays.
*/
export const desparsifyArrays = <T>(data: T): T => {
if (Array.isArray(data)) {
return <T>(
data.filter((item) => item !== undefined).map((item) => desparsifyArrays(item))
);
} else if (typeof data === 'object' && data !== null) {
const result: Record<string | number | symbol, unknown> = {};
for (const key in data) {
result[key] = desparsifyArrays(data[key]);
}
return <T>result;
}
return data;
};
+4 -1
View File
@@ -28,7 +28,10 @@ export function deepRemoveDefaults<T extends z.ZodTypeAny>(schema: T): any {
}
if (schema instanceof z.ZodArray) {
return z.ZodArray.create(deepRemoveDefaults(schema.element));
return z.ZodArray.create(deepRemoveDefaults(schema.element))
.min(schema._def.minLength?.value, schema._def.minLength?.message)
.max(schema._def.maxLength?.value, schema._def.maxLength?.message)
.length(schema._def.exactLength?.value, schema._def.exactLength?.message);
}
if (schema instanceof z.ZodOptional) {
+2 -2
View File
@@ -457,11 +457,11 @@ describe('CameraManager', async () => {
},
);
it('without cameras', async () => {
it('without camera', async () => {
const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
const manager = createCameraManager(api, mock<CameraManagerEngine>(), []);
const manager = createCameraManager(api, mock<CameraManagerEngine>());
expect(manager.generateDefaultEventQueries('not_a_camera')).toBeNull();
});
+336 -63
View File
@@ -1,10 +1,10 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { z } from 'zod';
import {
ConditionsEvaluateRequestEvent,
ConditionsManager,
evaluateConditionViaEvent,
getOverriddenConfig,
getOverridesByKey,
} from '../../src/card-controller/conditions-manager';
import { FrigateCardCondition } from '../../src/config/types';
import {
@@ -77,77 +77,350 @@ describe('getOverriddenConfig', () => {
style: 'none',
},
};
const overrides = [
{
overrides: {
menu: {
style: 'above',
},
},
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
},
];
it('should not override config', () => {
const manager = new ConditionsManager(createCardAPI());
expect(getOverriddenConfig(manager, config, overrides)).toBe(config);
});
it('should override config', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(getOverriddenConfig(manager, config, overrides)).toEqual({
menu: {
style: 'above',
},
});
});
it('should do nothing without overrides', () => {
it('should not override without overrides', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(getOverriddenConfig(manager, config)).toBe(config);
});
});
describe('getOverridesByKey', () => {
const conditions = [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
];
const override = {
menu: {
style: 'above',
},
};
const overrides = [
{
overrides: override,
conditions: conditions,
},
];
it('should get overrides', () => {
expect(getOverridesByKey('menu', overrides)).toEqual([
{ conditions: conditions, overrides: { style: 'above' } },
]);
it('should not override when condition does not match', () => {
const manager = new ConditionsManager(createCardAPI());
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
merge: {
menu: {
style: 'hidden',
},
},
delete: ['menu.style'],
set: {
'menu.style': 'overlay',
},
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
},
],
}),
).toBe(config);
});
it('should get no overrides', () => {
expect(getOverridesByKey('live', overrides)).toEqual([]);
describe('should merge', () => {
it('with path', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
merge: {
'live.controls.thumbnails': {
mode: 'none',
},
},
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
},
],
}),
).toEqual({
menu: {
style: 'none',
},
live: {
controls: {
thumbnails: {
mode: 'none',
},
},
},
});
});
it('without path', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
merge: {
menu: {
style: 'hidden',
},
},
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
},
],
}),
).toEqual({
menu: {
style: 'hidden',
},
});
});
it('with invalid merge', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
merge: 6 as unknown as Record<string, unknown>,
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
},
],
}),
).toEqual({
menu: {
style: 'none',
},
});
});
});
it('should get no overrides when undefined', () => {
expect(getOverridesByKey('live')).toEqual([]);
describe('should set', () => {
it('leaf node', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
set: {
'menu.style': 'hidden',
},
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
},
],
}),
).toEqual({
menu: {
style: 'hidden',
},
});
});
it('root node', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
set: {
menu: {
style: 'hidden',
},
},
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
},
],
}),
).toEqual({
menu: {
style: 'hidden',
},
});
});
});
describe('should delete', () => {
it('leaf node', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
delete: ['menu.style' as const],
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
},
],
}),
).toEqual({
menu: {},
});
});
it('root node', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
delete: ['menu' as const],
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
},
],
}),
).toEqual({});
});
// it('with empty value and object', () => {
// const manager = new ConditionsManager(createCardAPI());
// manager.setState({ fullscreen: true });
// expect(
// getOverriddenConfig(manager, config, {
// configOverrides: [
// {
// delete: [''],
// conditions: [
// {
// condition: 'fullscreen' as const,
// fullscreen: true,
// },
// ],
// },
// ],
// emptyKeyReplaces: true,
// }),
// ).toEqual({});
// });
});
describe('should validate schema', () => {
const testSchema = z.object({
menu: z.object({
style: z.enum(['none', 'hidden']),
}),
});
it('passing', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
set: {
'menu.style': 'hidden',
},
},
],
schema: testSchema,
}),
).toEqual({
menu: {
style: 'hidden',
},
});
});
it('failing', () => {
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
set: {
'menu.style': 'NOT_A_STYLE',
},
},
],
schema: testSchema,
}),
).toEqual(config);
});
it('failing and logging', () => {
const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
const manager = new ConditionsManager(createCardAPI());
manager.setState({ fullscreen: true });
expect(
getOverriddenConfig(manager, config, {
configOverrides: [
{
conditions: [
{
condition: 'fullscreen' as const,
fullscreen: true,
},
],
set: {
'menu.style': 'NOT_A_STYLE',
},
},
],
schema: testSchema,
logOnParseError: true,
}),
).toEqual(config);
expect(consoleSpy).toBeCalledWith(
'Cannot parse overridden configuration',
expect.anything(),
expect.anything(),
);
});
});
});
@@ -194,7 +467,7 @@ describe('ConditionsManager', () => {
return createConfig({
overrides: [
{
overrides: {},
merge: {},
conditions: conditions,
},
],
@@ -455,7 +728,7 @@ describe('ConditionsManager', () => {
describe('with screen condition', () => {
const mediaQueryConfig = {
type: 'custom:frigate-card',
cameras: [],
cameras: [{}],
elements: [
{
type: 'custom:frigate-card-conditional',
+50 -7
View File
@@ -420,7 +420,7 @@ describe('should handle version specific upgrades', () => {
media_loaded: true,
},
],
overrides: {
merge: {
view: {
default: 'clips',
},
@@ -489,7 +489,7 @@ describe('should handle version specific upgrades', () => {
overrides: [
{
conditions: {},
overrides: {
merge: {
menu: {
buttons: {
camera_ui: {
@@ -1872,7 +1872,7 @@ describe('should handle version specific upgrades', () => {
views: ['clips', 'snapshots'],
},
],
overrides: {
merge: {
view: {
default: 'clips',
},
@@ -1987,7 +1987,7 @@ describe('should handle version specific upgrades', () => {
cameras: ['camera_1', 'camera_2'],
},
],
overrides: {
merge: {
view: {
default: 'clips',
},
@@ -2107,7 +2107,7 @@ describe('should handle version specific upgrades', () => {
[condition]: true,
},
],
overrides: {
merge: {
view: {
default: 'clips',
},
@@ -2255,7 +2255,7 @@ describe('should handle version specific upgrades', () => {
state_not: 'off',
},
],
overrides: {
merge: {
view: {
default: 'clips',
},
@@ -2387,7 +2387,7 @@ describe('should handle version specific upgrades', () => {
media_query: 'query',
},
],
overrides: {
merge: {
view: {
default: 'clips',
},
@@ -2497,5 +2497,48 @@ describe('should handle version specific upgrades', () => {
});
});
});
it('from overrides to merge', () => {
const config = {
type: 'custom:frigate-card',
cameras: [],
overrides: [
{
conditions: [
{
condition: 'view',
view: ['clips'],
},
],
overrides: {
menu: {
style: 'hidden',
},
},
},
],
};
expect(upgradeConfig(config)).toBeTruthy();
expect(config).toEqual({
type: 'custom:frigate-card',
cameras: [],
overrides: [
{
conditions: [
{
condition: 'view',
view: ['clips'],
},
],
merge: {
menu: {
style: 'hidden',
},
},
},
],
});
});
});
});
+1 -1
View File
@@ -25,7 +25,7 @@ describe('setProfiles', () => {
it('should handle profiles', () => {
const input = {
type: 'frigate-hass-card',
cameras: [],
cameras: [{}],
live: {
controls: {
timeline: {
+2 -2
View File
@@ -12,7 +12,7 @@ import { createConfig } from '../test-utils';
describe('config defaults', () => {
it('should be as expected', () => {
expect(createConfig()).toEqual({
cameras: [],
cameras: [{}],
cameras_global: {
dependencies: {
all_cameras: false,
@@ -453,7 +453,7 @@ describe('should handle custom frigate elements', () => {
it('should not require title controls to specify all options', () => {
expect(
createConfig({
cameras: [],
cameras: [{}],
live: {
controls: {
title: {
+1 -1
View File
@@ -67,7 +67,7 @@ export const createCondition = (
export const createConfig = (config?: RawFrigateCardConfig): FrigateCardConfig => {
return frigateCardConfigSchema.parse({
type: 'frigate-hass-card',
cameras: [],
cameras: [{}],
...config,
});
};
+52 -2
View File
@@ -7,6 +7,7 @@ import {
aspectRatioToStyle,
contentsChanged,
dayToDate,
desparsifyArrays,
dispatchFrigateCardEvent,
errorToConsole,
formatDate,
@@ -158,6 +159,7 @@ describe('runWhenIdleIfSupported', () => {
});
it('should run directly when not supported', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).requestIdleCallback = undefined;
const func = vi.fn();
runWhenIdleIfSupported(func);
@@ -166,7 +168,7 @@ describe('runWhenIdleIfSupported', () => {
it('should run idle when supported', () => {
const requestIdle = vi.fn();
(window as any).requestIdleCallback = requestIdle;
window.requestIdleCallback = requestIdle;
const func = vi.fn();
runWhenIdleIfSupported(func);
expect(requestIdle).toBeCalledWith(func, {});
@@ -174,7 +176,7 @@ describe('runWhenIdleIfSupported', () => {
it('should run idle with timeout when supported', () => {
const requestIdle = vi.fn();
(window as any).requestIdleCallback = requestIdle;
window.requestIdleCallback = requestIdle;
const func = vi.fn();
runWhenIdleIfSupported(func, 10);
expect(requestIdle).toBeCalledWith(func, { timeout: 10 });
@@ -298,6 +300,7 @@ describe('recursivelyMergeObjectsNotArrays', () => {
it('should recursively merge objects but replace arrays', () => {
expect(
recursivelyMergeObjectsNotArrays(
{},
{
a: {
b: {
@@ -356,3 +359,50 @@ describe('aspectRatioToStyle', () => {
expect(aspectRatioToStyle({ ratio: [4] })).toEqual({ 'aspect-ratio': 'auto' });
});
});
describe('desparsifyArrays', () => {
it('number', () => {
expect(desparsifyArrays(1)).toBe(1);
});
it('string', () => {
expect(desparsifyArrays('foo')).toBe('foo');
});
describe('array', () => {
it('simple', () => {
expect(desparsifyArrays([1, 2, undefined, 3])).toEqual([1, 2, 3]);
});
it('nested', () => {
expect(
desparsifyArrays([
1,
2,
undefined,
{
subArray: [undefined, 3],
},
4,
]),
).toEqual([1, 2, { subArray: [3] }, 4]);
});
});
describe('object', () => {
it('simple', () => {
expect(
desparsifyArrays({ foo: [1, 2, undefined, 3], bar: [undefined, 4] }),
).toEqual({
foo: [1, 2, 3],
bar: [4],
});
});
it('nested', () => {
expect(
desparsifyArrays({ foo: { bar: [1, undefined, 2], empty: [undefined] } }),
).toEqual({
foo: {
bar: [1, 2],
empty: [],
},
});
});
});
});
+17
View File
@@ -49,6 +49,23 @@ describe('deepRemoveDefaults', () => {
const result = deepRemoveDefaults(schema).parse({ string: 'moo' });
expect(result.string).toBe('moo');
});
describe('should still enforce array length', () => {
it('min', () => {
const schema = z.number().array().min(1);
const result = deepRemoveDefaults(schema).safeParse([]);
expect(result.success).toBeFalsy();
});
it('max', () => {
const schema = z.number().array().max(1);
const result = deepRemoveDefaults(schema).safeParse([1, 2]);
expect(result.success).toBeFalsy();
});
it('exact', () => {
const schema = z.number().array().length(1);
const result = deepRemoveDefaults(schema).safeParse([]);
expect(result.success).toBeFalsy();
});
});
});
describe('getParseErrorKeys', () => {
+67 -57
View File
@@ -800,6 +800,16 @@ __metadata:
languageName: node
linkType: hard
"@jridgewell/trace-mapping@npm:^0.3.23":
version: 0.3.25
resolution: "@jridgewell/trace-mapping@npm:0.3.25"
dependencies:
"@jridgewell/resolve-uri": "npm:^3.1.0"
"@jridgewell/sourcemap-codec": "npm:^1.4.14"
checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4
languageName: node
linkType: hard
"@lit-labs/scoped-registry-mixin@npm:^1.0.3":
version: 1.0.3
resolution: "@lit-labs/scoped-registry-mixin@npm:1.0.3"
@@ -1467,76 +1477,76 @@ __metadata:
languageName: node
linkType: hard
"@vitest/coverage-istanbul@npm:^1.3.1":
version: 1.3.1
resolution: "@vitest/coverage-istanbul@npm:1.3.1"
"@vitest/coverage-istanbul@npm:^1.5.0":
version: 1.5.0
resolution: "@vitest/coverage-istanbul@npm:1.5.0"
dependencies:
debug: "npm:^4.3.4"
istanbul-lib-coverage: "npm:^3.2.2"
istanbul-lib-instrument: "npm:^6.0.1"
istanbul-lib-report: "npm:^3.0.1"
istanbul-lib-source-maps: "npm:^4.0.1"
istanbul-lib-source-maps: "npm:^5.0.4"
istanbul-reports: "npm:^3.1.6"
magicast: "npm:^0.3.3"
picocolors: "npm:^1.0.0"
test-exclude: "npm:^6.0.0"
peerDependencies:
vitest: 1.3.1
checksum: 10c0/40efacde51e5998bde8032dd45d64fe214674f1efb97c094c4ae9ef1893afc4e0ef97f3fa4cc4923561b67b1196c27a877a0fa5bc71a049b8e48e3ef1f5c48db
vitest: 1.5.0
checksum: 10c0/12abbaf8925c797e1fafef05c8b7d554c9fc61d13eb70c3f8532ade72db305a3cb1fec9bb7642763de27471e3f3da3d684d6533aa954756c5edf2707d2fc25cd
languageName: node
linkType: hard
"@vitest/expect@npm:1.3.1":
version: 1.3.1
resolution: "@vitest/expect@npm:1.3.1"
"@vitest/expect@npm:1.5.0":
version: 1.5.0
resolution: "@vitest/expect@npm:1.5.0"
dependencies:
"@vitest/spy": "npm:1.3.1"
"@vitest/utils": "npm:1.3.1"
"@vitest/spy": "npm:1.5.0"
"@vitest/utils": "npm:1.5.0"
chai: "npm:^4.3.10"
checksum: 10c0/ea66a1e912d896a481a27631b68089b885af7e8ed62ba8aaa119c37a9beafe6c094fd672775a20e6e23460af66e294f9ca259e6e0562708d1b7724eaaf53c7bb
checksum: 10c0/12138caf0831a9bcdf475750fdff27588f03f11ee5101124f9d39951bbcdec39aa6a19a1a30f3ab0ca17374ddfc7399b861e2fcb1548bba302943e8e88512a9c
languageName: node
linkType: hard
"@vitest/runner@npm:1.3.1":
version: 1.3.1
resolution: "@vitest/runner@npm:1.3.1"
"@vitest/runner@npm:1.5.0":
version: 1.5.0
resolution: "@vitest/runner@npm:1.5.0"
dependencies:
"@vitest/utils": "npm:1.3.1"
"@vitest/utils": "npm:1.5.0"
p-limit: "npm:^5.0.0"
pathe: "npm:^1.1.1"
checksum: 10c0/d732de2368d2bc32cbc27f0bbc5477f6e36088ddfb873c036935a45b1b252ebc529b932cf5cd944eed9b692243acebef828f6d3218583cb8a6817a8270712050
checksum: 10c0/a7b693e4121b6159a77f6cbffc03c228cd18c3734e27a968fe26579777f6d7a4d55a02fba396d564df996a91881ee76de80ee5a526d27a4b508f44a9af62d95a
languageName: node
linkType: hard
"@vitest/snapshot@npm:1.3.1":
version: 1.3.1
resolution: "@vitest/snapshot@npm:1.3.1"
"@vitest/snapshot@npm:1.5.0":
version: 1.5.0
resolution: "@vitest/snapshot@npm:1.5.0"
dependencies:
magic-string: "npm:^0.30.5"
pathe: "npm:^1.1.1"
pretty-format: "npm:^29.7.0"
checksum: 10c0/cad0844270852c6d53c1ca6b7ca279034880d2140837ff245d5bd2376f4356cc924929c58dc69bcf9fad83ba934d4a06000c908971cc24b5d7a9ec2656b72d29
checksum: 10c0/d897070b3a7c008eb58d44ca0dc324c69dfc1f787335e59fb930ae2e8d02a148e6b3c13497a588083a0654bdc430281fdd84276ead4888230314a2984dd9f93d
languageName: node
linkType: hard
"@vitest/spy@npm:1.3.1":
version: 1.3.1
resolution: "@vitest/spy@npm:1.3.1"
"@vitest/spy@npm:1.5.0":
version: 1.5.0
resolution: "@vitest/spy@npm:1.5.0"
dependencies:
tinyspy: "npm:^2.2.0"
checksum: 10c0/efc42f679d2a51fc6583ca3136ccd47581cb27c923ed3cb0500f5dee9aac99b681bfdd400c16ef108f2e0761daa642bc190816a6411931a2aba99ebf8b213dd4
checksum: 10c0/0206f1e8431f543474dc6d252b553227d0f286b27226b987b63babb18865e8320e6c3d822c67782aae68555728a57ecdeb7c4e16283dbad49791608a691f26d1
languageName: node
linkType: hard
"@vitest/utils@npm:1.3.1":
version: 1.3.1
resolution: "@vitest/utils@npm:1.3.1"
"@vitest/utils@npm:1.5.0":
version: 1.5.0
resolution: "@vitest/utils@npm:1.5.0"
dependencies:
diff-sequences: "npm:^29.6.3"
estree-walker: "npm:^3.0.3"
loupe: "npm:^2.3.7"
pretty-format: "npm:^29.7.0"
checksum: 10c0/d604c8ad3b1aee30d4dcd889098f591407bfe18547ff96485b1d1ed54eff58219c756a9544a7fbd4e37886863abacd7a89a76334cb3ea7f84c3d496bb757db23
checksum: 10c0/b9d779ea5c1a5759df4f59e3ba3f3a94816d6c600afe7a2d14b963ea114ce1acedbfe678cbfacb0a20d33cabcce890a08b2ce3fd52e3465f2e0969cc39f7686b
languageName: node
linkType: hard
@@ -3855,7 +3865,7 @@ __metadata:
"@types/masonry-layout": "npm:^4.2.5"
"@typescript-eslint/eslint-plugin": "npm:^5.36.2"
"@typescript-eslint/parser": "npm:^5.36.2"
"@vitest/coverage-istanbul": "npm:^1.3.1"
"@vitest/coverage-istanbul": "npm:^1.5.0"
component-emitter: "npm:^1.3.0"
crypto: "npm:^1.0.1"
date-fns: "npm:^2.29.2"
@@ -3895,7 +3905,7 @@ __metadata:
vis-data: "npm:^7.1.9"
vis-timeline: "npm:^7.7.3"
vis-util: "npm:^5.0.2"
vitest: "npm:^1.3.1"
vitest: "npm:^1.5.0"
vitest-mock-extended: "npm:^1.3.1"
web-dialog: "npm:^0.0.11"
xss: "npm:^1.0.14"
@@ -4938,14 +4948,14 @@ __metadata:
languageName: node
linkType: hard
"istanbul-lib-source-maps@npm:^4.0.1":
version: 4.0.1
resolution: "istanbul-lib-source-maps@npm:4.0.1"
"istanbul-lib-source-maps@npm:^5.0.4":
version: 5.0.4
resolution: "istanbul-lib-source-maps@npm:5.0.4"
dependencies:
"@jridgewell/trace-mapping": "npm:^0.3.23"
debug: "npm:^4.1.1"
istanbul-lib-coverage: "npm:^3.0.0"
source-map: "npm:^0.6.1"
checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66
checksum: 10c0/48b48294590675005ba439888a53157fc71a99d78321428f3ce5f64e28cdfb6bc6eb45871333f448437118ef56a0ef371f4958163e2c2d066d3a703415a71b2e
languageName: node
linkType: hard
@@ -8011,10 +8021,10 @@ __metadata:
languageName: node
linkType: hard
"tinypool@npm:^0.8.2":
version: 0.8.2
resolution: "tinypool@npm:0.8.2"
checksum: 10c0/8998626614172fc37c394e9a14e701dc437727fc6525488a4d4fd42044a4b2b59d6f076d750cbf5c699f79c58dd4e40599ab09e2f1ae0df4b23516b98c9c3055
"tinypool@npm:^0.8.3":
version: 0.8.3
resolution: "tinypool@npm:0.8.3"
checksum: 10c0/c219d0cfb69de8e3cf17403034a508d773f2fccaad79a13cdbad68600c4fb10186ad814d2320bcaa8f6e774fff5666d2a3d3b241dc8a7ad9d970ee63fe620a32
languageName: node
linkType: hard
@@ -8487,9 +8497,9 @@ __metadata:
languageName: node
linkType: hard
"vite-node@npm:1.3.1":
version: 1.3.1
resolution: "vite-node@npm:1.3.1"
"vite-node@npm:1.5.0":
version: 1.5.0
resolution: "vite-node@npm:1.5.0"
dependencies:
cac: "npm:^6.7.14"
debug: "npm:^4.3.4"
@@ -8498,7 +8508,7 @@ __metadata:
vite: "npm:^5.0.0"
bin:
vite-node: vite-node.mjs
checksum: 10c0/b50665ef224f3527f856ab88a0cfabab36dd6e2dd1e3edca8f8f25d5d33754e1050495472c2c82147d0dcf7c5280971dae2f37a531c10f3941d8d3344e34ce0b
checksum: 10c0/85f9e8616f3612c71193a2bbdfb00f610685108a65eaa7ac535655ca1bd65dda5436ee577465436f715966f34e4b25b54140bbd584fe2c95ea0726acf4ecd351
languageName: node
linkType: hard
@@ -8554,15 +8564,15 @@ __metadata:
languageName: node
linkType: hard
"vitest@npm:^1.3.1":
version: 1.3.1
resolution: "vitest@npm:1.3.1"
"vitest@npm:^1.5.0":
version: 1.5.0
resolution: "vitest@npm:1.5.0"
dependencies:
"@vitest/expect": "npm:1.3.1"
"@vitest/runner": "npm:1.3.1"
"@vitest/snapshot": "npm:1.3.1"
"@vitest/spy": "npm:1.3.1"
"@vitest/utils": "npm:1.3.1"
"@vitest/expect": "npm:1.5.0"
"@vitest/runner": "npm:1.5.0"
"@vitest/snapshot": "npm:1.5.0"
"@vitest/spy": "npm:1.5.0"
"@vitest/utils": "npm:1.5.0"
acorn-walk: "npm:^8.3.2"
chai: "npm:^4.3.10"
debug: "npm:^4.3.4"
@@ -8574,15 +8584,15 @@ __metadata:
std-env: "npm:^3.5.0"
strip-literal: "npm:^2.0.0"
tinybench: "npm:^2.5.1"
tinypool: "npm:^0.8.2"
tinypool: "npm:^0.8.3"
vite: "npm:^5.0.0"
vite-node: "npm:1.3.1"
vite-node: "npm:1.5.0"
why-is-node-running: "npm:^2.2.2"
peerDependencies:
"@edge-runtime/vm": "*"
"@types/node": ^18.0.0 || >=20.0.0
"@vitest/browser": 1.3.1
"@vitest/ui": 1.3.1
"@vitest/browser": 1.5.0
"@vitest/ui": 1.5.0
happy-dom: "*"
jsdom: "*"
peerDependenciesMeta:
@@ -8600,7 +8610,7 @@ __metadata:
optional: true
bin:
vitest: vitest.mjs
checksum: 10c0/66d312a3dc12e67bba22d31332d939e89cd17d38531893c7b13b8826704564031c1dde795df2799b855660572c19a595301e920710c7775d072ee6332502efc5
checksum: 10c0/87f6666cccd52678a0e1dbf32e7492e4003580a168114221e4c2d404d53097d886a8be4c5c379e347d0e749affa0ea5416e4e3a0356aeca228a77ee54891ca3c
languageName: node
linkType: hard