feat: Use Vitest for testing (#25284)

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
This commit is contained in:
NerivecandKoen Kanters authored and GitHub committed 2024-12-22 20:19:45 +01:00
1 parent 5d059b7bcb
commit 06f44533af
51 files changed
+2606 -4771

No files matched your search

+7 -3
View File
@@ -131,10 +131,11 @@ export class Controller {
try {
this.sdNotify = process.env.NOTIFY_SOCKET ? await import('sd-notify') : undefined;
logger.debug('sd-notify loaded');
/* v8 ignore start */
} catch {
// istanbul ignore next
logger.debug('sd-notify is not installed');
}
/* v8 ignore stop */
// Start zigbee
try {
@@ -145,10 +146,13 @@ export class Controller {
logger.error('Check https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start.html for possible solutions');
logger.error('Exiting...');
logger.error((error as Error).stack!);
/* istanbul ignore if */
/* v8 ignore start */
if ((error as Error).message.includes('USB adapter discovery error (No valid USB adapter found)')) {
logger.error('If this happens after updating to Zigbee2MQTT 2.0.0, see https://github.com/Koenkk/zigbee2mqtt/discussions/24364');
}
/* v8 ignore stop */
return await this.exit(1);
}
@@ -292,6 +296,7 @@ export class Controller {
softwareBuildID: entity.zh.softwareBuildID,
// Manufacturer name can contain \u0000, remove this.
// https://github.com/home-assistant/core/issues/85691
/* v8 ignore next */
manufacturerName: entity.zh.manufacturerName?.split('\u0000')[0],
};
}
@@ -360,7 +365,6 @@ export class Controller {
try {
await extension[method]?.();
} catch (error) {
/* istanbul ignore next */
logger.error(`Failed to call '${extension.constructor.name}' '${method}' (${(error as Error).stack})`);
}
}
+2 -2
View File
@@ -42,7 +42,7 @@ export default class Availability extends Extension {
private isAvailable(entity: Device | Group): boolean {
if (entity.isDevice()) {
return Date.now() - (entity.zh.lastSeen ?? /* istanbul ignore next */ 0) < this.getTimeout(entity);
return Date.now() - (entity.zh.lastSeen ?? /* v8 ignore next */ 0) < this.getTimeout(entity);
} else {
const membersDevices = entity.membersDevices();
return membersDevices.length === 0 || membersDevices.some((d) => this.availabilityCache[d.ieeeAddr]);
@@ -164,7 +164,7 @@ export default class Availability extends Extension {
private async publishAvailability(entity: Device | Group, logLastSeen: boolean, forcePublish = false, skipGroups = false): Promise<void> {
if (logLastSeen && entity.isDevice()) {
const ago = Date.now() - (entity.zh.lastSeen ?? /* istanbul ignore next */ 0);
const ago = Date.now() - (entity.zh.lastSeen ?? /* v8 ignore next */ 0);
if (this.isActiveDevice(entity)) {
logger.debug(`Active device '${entity.name}' was last seen '${(ago / utils.minutes(1)).toFixed(2)}' minutes ago.`);
-7
View File
@@ -170,12 +170,10 @@ const POLL_ON_MESSAGE: Readonly<PollOnMessage> = [
const supportedAttrs = await getColorCapabilities(endpoint);
const readAttrs: string[] = [];
/* istanbul ignore else */
if (supportedAttrs.colorXY) {
readAttrs.push('currentX', 'currentY');
}
/* istanbul ignore else */
if (supportedAttrs.colorTemperature) {
readAttrs.push('colorTemperature');
}
@@ -390,7 +388,6 @@ export default class Bind extends Extension {
failed: failedClusters,
};
/* istanbul ignore else */
if (successfulClusters.length !== 0) {
if (type === 'bind') {
await this.setupReporting(
@@ -466,7 +463,6 @@ export default class Bind extends Extension {
const coordinatorEndpoint = this.zigbee.firstCoordinatorEndpoint();
for (const bind of binds) {
/* istanbul ignore else */
if (bind.cluster.name in REPORT_CLUSTERS) {
for (const endpoint of this.getSetupReportingEndpoints(bind, coordinatorEndpoint)) {
const entity = `${this.zigbee.resolveEntity(endpoint.getDevice())!.name}/${endpoint.ID}`;
@@ -477,7 +473,6 @@ export default class Bind extends Extension {
const items = [];
for (const c of REPORT_CLUSTERS[bind.cluster.name as ClusterName]!) {
/* istanbul ignore else */
if (!c.condition || (await c.condition(endpoint))) {
const i = {...c};
delete i.condition;
@@ -524,7 +519,6 @@ export default class Bind extends Extension {
}
for (const b of endpoint.binds) {
/* istanbul ignore else */
if (b.target === coordinator && !requiredClusters.includes(b.cluster.name) && b.cluster.name in REPORT_CLUSTERS) {
boundClusters.push(b.cluster.name);
}
@@ -537,7 +531,6 @@ export default class Bind extends Extension {
const items = [];
for (const item of REPORT_CLUSTERS[cluster as ClusterName]!) {
/* istanbul ignore else */
if (!item.condition || (await item.condition(endpoint))) {
const i = {...item};
delete i.condition;
+1 -1
View File
@@ -804,7 +804,7 @@ export default class Bridge extends Extension {
let icon = device.options.icon ?? definitionIcon;
if (icon) {
/* istanbul ignore next */
/* v8 ignore next */
icon = icon.replace('${zigbeeModel}', utils.sanitizeImageParameter(device.zh.modelID ?? ''));
icon = icon.replace('${model}', utils.sanitizeImageParameter(device.definition.model));
}
-1
View File
@@ -43,7 +43,6 @@ abstract class Extension {
/**
* Is called once the extension has to start
*/
/* istanbul ignore next */
async start(): Promise<void> {}
/**
-1
View File
@@ -64,7 +64,6 @@ export default abstract class ExternalJSExtension<M> extends Extension {
}
for (const fileName of fs.readdirSync(this.basePath)) {
/* istanbul ignore else */
if (fileName.endsWith('.js')) {
yield {name: fileName, code: this.getFileCode(fileName)};
}
+6 -7
View File
@@ -1,6 +1,8 @@
import type {IncomingMessage, Server, ServerResponse} from 'node:http';
import type {Socket} from 'node:net';
import type {RequestHandler} from 'express-static-gzip';
import assert from 'node:assert';
import {existsSync, readFileSync} from 'node:fs';
import {createServer} from 'node:http';
@@ -9,7 +11,7 @@ import {posix} from 'node:path';
import {parse} from 'node:url';
import bind from 'bind-decorator';
import expressStaticGzip, {RequestHandler} from 'express-static-gzip';
import expressStaticGzip from 'express-static-gzip';
import finalhandler from 'finalhandler';
import stringify from 'json-stable-stringify-without-jsonify';
import WebSocket from 'ws';
@@ -71,18 +73,19 @@ export default class Frontend extends Extension {
}
override async start(): Promise<void> {
/* istanbul ignore next */
const options = {
enableBrotli: true,
// TODO: https://github.com/Koenkk/zigbee2mqtt/issues/24654 - enable compressed index serving when express-static-gzip is fixed.
index: false,
serveStatic: {
index: 'index.html',
/* v8 ignore start */
setHeaders: (res: ServerResponse, path: string): void => {
if (path.endsWith('index.html')) {
res.setHeader('Cache-Control', 'no-store');
}
},
/* v8 ignore stop */
},
};
this.fileServer = expressStaticGzip(frontend.getPath(), options);
@@ -172,7 +175,6 @@ export default class Frontend extends Extension {
});
for (const [topic, payload] of Object.entries(this.mqtt.retainedMessages)) {
/* istanbul ignore else */
if (topic.startsWith(`${this.mqttBaseTopic}/`)) {
ws.send(
stringify({
@@ -188,9 +190,8 @@ export default class Frontend extends Extension {
const payload = this.state.get(device);
const lastSeen = settings.get().advanced.last_seen;
/* istanbul ignore if */
if (lastSeen !== 'disable') {
payload.last_seen = utils.formatDate(device.zh.lastSeen ?? 0, lastSeen);
payload.last_seen = utils.formatDate(device.zh.lastSeen ?? /* v8 ignore next */ 0, lastSeen);
}
if (device.zh.linkquality !== undefined) {
@@ -202,14 +203,12 @@ export default class Frontend extends Extension {
}
@bind private onMQTTPublishMessage(data: eventdata.MQTTMessagePublished): void {
/* istanbul ignore else */
if (data.topic.startsWith(`${this.mqttBaseTopic}/`)) {
// Send topic without base_topic
const topic = data.topic.substring(this.mqttBaseTopic.length + 1);
const payload = utils.parseJSON(data.payload, data.payload);
for (const client of this.wss.clients) {
/* istanbul ignore else */
if (client.readyState === WebSocket.OPEN) {
client.send(stringify({topic, payload}));
}
-1
View File
@@ -89,7 +89,6 @@ export default class Groups extends Extension {
if (entity instanceof Device) {
const endpoint = entity.endpoint(endpointName);
/* istanbul ignore else */
if (endpoint) {
for (const group of groups) {
if (
+17 -23
View File
@@ -360,8 +360,7 @@ class Bridge {
constructor(ieeeAdress: string, version: zh.CoordinatorVersion, discovery: DiscoveryEntry[]) {
this.coordinatorIeeeAddress = ieeeAdress;
this.coordinatorType = version.type;
/* istanbul ignore next */
this.coordinatorFirmwareVersion = version.meta.revision ? `${version.meta.revision}` : '';
this.coordinatorFirmwareVersion = version.meta.revision ? `${version.meta.revision}` : /* v8 ignore next */ '';
this.discoveryEntries = discovery;
this.options = {
@@ -718,11 +717,8 @@ export default class HomeAssistant extends Extension {
},
};
// istanbul ignore else
if (tempCalibration.value_min != null) discoveryEntry.discovery_payload.min = tempCalibration.value_min;
// istanbul ignore else
if (tempCalibration.value_max != null) discoveryEntry.discovery_payload.max = tempCalibration.value_max;
// istanbul ignore else
if (tempCalibration.value_step != null) {
discoveryEntry.discovery_payload.step = tempCalibration.value_step;
}
@@ -733,10 +729,10 @@ export default class HomeAssistant extends Extension {
if (piHeatingDemand) {
const discoveryEntry: DiscoveryEntry = {
type: 'sensor',
object_id: endpoint ? /* istanbul ignore next */ `${piHeatingDemand.name}_${endpoint}` : `${piHeatingDemand.name}`,
object_id: endpoint ? /* v8 ignore next */ `${piHeatingDemand.name}_${endpoint}` : `${piHeatingDemand.name}`,
mockProperties: [{property: piHeatingDemand.property, value: null}],
discovery_payload: {
name: endpoint ? /* istanbul ignore next */ `${piHeatingDemand.label} ${endpoint}` : piHeatingDemand.label,
name: endpoint ? /* v8 ignore next */ `${piHeatingDemand.label} ${endpoint}` : piHeatingDemand.label,
value_template: `{{ value_json.${piHeatingDemand.property} }}`,
...(piHeatingDemand.unit && {unit_of_measurement: piHeatingDemand.unit}),
entity_category: 'diagnostic',
@@ -816,7 +812,6 @@ export default class HomeAssistant extends Extension {
const closingState = motorState.values.find((s) => COVER_CLOSING_LOOKUP.includes(s.toString().toLowerCase()));
const stoppedState = motorState.values.find((s) => COVER_STOPPED_LOOKUP.includes(s.toString().toLowerCase()));
// istanbul ignore else
if (openingState && closingState && stoppedState) {
discoveryEntry.discovery_payload.state_opening = openingState;
discoveryEntry.discovery_payload.state_closing = closingState;
@@ -836,10 +831,11 @@ export default class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.state_stopped = 'STOP';
}
// istanbul ignore if
/* v8 ignore start */
if (!position && !tilt) {
discoveryEntry.discovery_payload.optimistic = true;
}
/* v8 ignore stop */
if (position) {
discoveryEntry.discovery_payload = {
@@ -879,7 +875,7 @@ export default class HomeAssistant extends Extension {
};
const speed = (firstExpose as zhc.Fan).features.filter(isEnumExpose).find((e) => e.name === 'mode');
// istanbul ignore else
if (speed) {
// A fan entity in Home Assistant 2021.3 and above may have a speed,
// controlled by a percentage from 1 to 100, and/or non-speed presets.
@@ -943,7 +939,7 @@ export default class HomeAssistant extends Extension {
mockProperties: [{property: firstExpose.property, value: null}],
object_id: endpoint ? `switch_${firstExpose.name}_${endpoint}` : `switch_${firstExpose.name}`,
discovery_payload: {
name: endpoint ? `${firstExpose.label} ${endpoint}` : firstExpose.label,
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
value_template:
typeof firstExpose.value_on === 'boolean'
? `{% if value_json.${firstExpose.property} %}true{% else %}false{% endif %}`
@@ -964,7 +960,7 @@ export default class HomeAssistant extends Extension {
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
name: endpoint ? `${firstExpose.label} ${endpoint}` : firstExpose.label,
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
value_template: `{{ value_json.${firstExpose.property} }}`,
payload_on: firstExpose.value_on,
payload_off: firstExpose.value_off,
@@ -1006,9 +1002,7 @@ export default class HomeAssistant extends Extension {
delete discoveryEntry.discovery_payload.device_class;
}
// istanbul ignore else
if (firstExpose.value_min != null) discoveryEntry.discovery_payload.min = firstExpose.value_min;
// istanbul ignore else
if (firstExpose.value_max != null) discoveryEntry.discovery_payload.max = firstExpose.value_max;
discoveryEntries.push(discoveryEntry);
@@ -1076,7 +1070,7 @@ export default class HomeAssistant extends Extension {
object_id: firstExpose.property,
mockProperties: [],
discovery_payload: {
name: endpoint ? /* istanbul ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
state_topic: true,
event_types: this.prepareActionEventTypes(firstExpose.values),
value_template: this.actionValueTemplate,
@@ -1100,7 +1094,7 @@ export default class HomeAssistant extends Extension {
object_id: firstExpose.property,
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
name: endpoint ? /* istanbul ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
state_topic: false,
command_topic_prefix: endpoint,
command_topic: true,
@@ -1137,7 +1131,6 @@ export default class HomeAssistant extends Extension {
/**
* Otherwise expose as SENSOR entity.
*/
/* istanbul ignore else */
if (firstExpose.access & ACCESS_STATE) {
discoveryEntries.push({
type: 'sensor',
@@ -1189,9 +1182,10 @@ export default class HomeAssistant extends Extension {
}
break;
}
/* istanbul ignore next */
/* v8 ignore start */
default:
throw new Error(`Unsupported exposes type: '${firstExpose.type}'`);
/* v8 ignore stop */
}
// Exposes with category 'config' or 'diagnostic' are always added to the respective category.
@@ -1249,14 +1243,16 @@ export default class HomeAssistant extends Extension {
* zigbee2mqtt/mydevice/l1.
*/
const entity = this.zigbee.resolveEntity(data.entity.name)!;
if (entity.isDevice()) {
for (const topic in this.getDiscovered(entity).messages) {
const topicMatch = topic.match(this.discoveryRegexWoTopic);
// istanbul ignore if
/* v8 ignore start */
if (!topicMatch) {
continue;
}
/* v8 ignore stop */
const objectID = topicMatch[3];
const lightMatch = /^light_(.*)/.exec(objectID);
@@ -1333,7 +1329,8 @@ export default class HomeAssistant extends Extension {
private getConfigs(entity: Device | Group | Bridge): DiscoveryEntry[] {
const isDevice = entity.isDevice();
const isGroup = entity.isGroup();
/* istanbul ignore next */
/* v8 ignore next */
if (!entity || (isDevice && !entity.definition)) return [];
let configs: DiscoveryEntry[] = [];
@@ -1390,7 +1387,6 @@ export default class HomeAssistant extends Extension {
},
};
/* istanbul ignore else */
if (settings.get().advanced.last_seen.startsWith('ISO_8601')) {
config.discovery_payload.device_class = 'timestamp';
}
@@ -1491,7 +1487,6 @@ export default class HomeAssistant extends Extension {
if (payload.state_topic === undefined || payload.state_topic) {
payload.state_topic = stateTopic;
} else {
/* istanbul ignore else */
if (payload.state_topic !== undefined) {
delete payload.state_topic;
}
@@ -1744,7 +1739,6 @@ export default class HomeAssistant extends Extension {
// Device was flagged to be excluded from homeassistant discovery
clear = clear || Boolean(entity && entity.options.homeassistant !== undefined && !entity.options.homeassistant);
/* istanbul ignore else */
if (clear) {
logger.debug(`Clearing outdated Home Assistant config '${data.topic}'`);
await this.mqtt.publish(topic, '', {retain: true, qos: 1}, this.discoveryTopic, false, false);
-3
View File
@@ -217,7 +217,6 @@ export default class NetworkMap extends Extension {
logger.debug((error as Error).stack!);
}
/* istanbul ignore else */
if (includeRoutes) {
try {
const result = await requestWithRetry<zh.RoutingTable>(async () => await device.zh.routingTable());
@@ -283,7 +282,6 @@ export default class NetworkMap extends Extension {
if (neighbor.ieeeAddr === '0x0000000000000000') {
const neighborDevice = this.zigbee.deviceByNetworkAddress(neighbor.networkAddress);
/* istanbul ignore else */
if (neighborDevice) {
neighbor.ieeeAddr = neighborDevice.ieeeAddr;
}
@@ -305,7 +303,6 @@ export default class NetworkMap extends Extension {
const routingTable = routingTables.get(device);
/* istanbul ignore else */
if (routingTable) {
for (const entry of routingTable.table) {
if (entry.status === 'ACTIVE' && entry.nextHop === neighbor.networkAddress) {
-1
View File
@@ -199,7 +199,6 @@ export default class Publish extends Extension {
}
if (usedConverters[endpointOrGroupID] === undefined) usedConverters[endpointOrGroupID] = [];
/* istanbul ignore next */
// Match any key if the toZigbee converter defines no key.
const converter = converters.find(
(c) =>
+4 -2
View File
@@ -102,7 +102,7 @@ export default class Receive extends Extension {
}
@bind async onDeviceMessage(data: eventdata.DeviceMessage): Promise<void> {
/* istanbul ignore next */
/* v8 ignore next */
if (!data.device) return;
if (!data.device.definition || data.device.zh.interviewing) {
@@ -171,10 +171,12 @@ export default class Receive extends Extension {
if (converted) {
payload = {...payload, ...converted};
}
} catch (error) /* istanbul ignore next */ {
/* v8 ignore start */
} catch (error) {
logger.error(`Exception while calling fromZigbee converter: ${(error as Error).message}}`);
logger.debug((error as Error).stack!);
}
/* v8 ignore stop */
}
if (!utils.objectIsEmpty(payload)) {
+2 -4
View File
@@ -39,7 +39,6 @@ export default class Device {
exposes(): zhc.Expose[] {
assert(this.definition, 'Cannot retreive exposes before definition is resolved');
/* istanbul ignore if */
if (typeof this.definition.exposes == 'function') {
const options: KeyValue = this.options;
return this.definition.exposes(this.zh, options);
@@ -81,7 +80,6 @@ export default class Device {
return undefined;
}
} else {
/* istanbul ignore next */
if (key !== 'default') {
return undefined;
}
@@ -104,7 +102,7 @@ export default class Device {
}
}
/* istanbul ignore next */
/* v8 ignore next */
return epName === 'default' ? undefined : epName;
}
@@ -123,7 +121,7 @@ export default class Device {
isDevice(): this is Device {
return true;
}
/* istanbul ignore next */
isGroup(): this is Group {
return false;
}
-1
View File
@@ -34,7 +34,6 @@ export default class Group {
const definitions: zhc.Definition[] = [];
for (const member of this.membersDevices()) {
/* istanbul ignore else */
if (member.definition) {
definitions.push(member.definition);
}
-2
View File
@@ -191,7 +191,6 @@ export default class MQTT {
this.eventBus.emitMQTTMessagePublished({topic, payload, options: {...defaultOptions, ...options}});
if (!this.isConnected()) {
/* istanbul ignore else */
if (!skipLog) {
logger.error(`Not connected to MQTT server!`);
logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
@@ -213,7 +212,6 @@ export default class MQTT {
try {
await this.client.publishAsync(topic, payload, actualOptions);
} catch (error) {
/* istanbul ignore else */
if (!skipLog) {
logger.error(`MQTT server error: ${(error as Error).message}`);
logger.error(`Could not send message: topic: '${topic}', payload: '${payload}`);
+14 -13
View File
@@ -59,11 +59,9 @@ class Logger {
// winston.config.syslog.levels sets 'warning' as 'red'
format: winston.format.combine(
winston.format.colorize({colors: {debug: 'blue', info: 'green', warning: 'yellow', error: 'red'}}),
winston.format.printf(
/* istanbul ignore next */ (info) => {
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
},
),
winston.format.printf((info) => {
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
}),
),
}),
);
@@ -77,10 +75,13 @@ class Logger {
if (settings.get().advanced.log_symlink_current) {
const current = settings.get().advanced.log_directory.replace('%TIMESTAMP%', 'current');
const actual = './' + timestamp;
/* istanbul ignore next */
/* v8 ignore start */
if (fs.existsSync(current)) {
fs.unlinkSync(current);
}
/* v8 ignore stop */
fs.symlinkSync(actual, current);
}
@@ -88,11 +89,9 @@ class Logger {
// NOTE: the initiation of the logger even when not added as transport tries to create the logging directory
const transportFileOptions: winston.transports.FileTransportOptions = {
filename: path.join(this.directory, logFilename),
format: winston.format.printf(
/* istanbul ignore next */ (info) => {
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
},
),
format: winston.format.printf((info) => {
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
}),
};
if (settings.get().advanced.log_rotation) {
@@ -106,7 +105,7 @@ class Logger {
this.cleanup();
}
/* istanbul ignore next */
/* v8 ignore start */
if (this.output.includes('syslog')) {
logging += `, syslog`;
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unused-expressions
@@ -125,6 +124,7 @@ class Logger {
// @ts-expect-error untyped transport
this.logger.add(new winston.transports.Syslog(options));
}
/* v8 ignore stop */
this.setDebugNamespaceIgnore(settings.get().advanced.log_debug_namespace_ignore);
@@ -240,7 +240,7 @@ class Logger {
// Workaround for https://github.com/winstonjs/winston/issues/1629.
// https://github.com/Koenkk/zigbee2mqtt/pull/10905
/* istanbul ignore next */
/* v8 ignore start */
public async end(): Promise<void> {
this.logger.end();
@@ -259,6 +259,7 @@ class Logger {
}
});
}
/* v8 ignore stop */
}
export default new Logger();
+1 -2
View File
@@ -87,7 +87,7 @@ export const defaults: RecursivePartial<Settings> = {
log_output: ['console', 'file'],
log_directory: path.join(data.getPath(), 'log', '%TIMESTAMP%'),
log_file: 'log.log',
log_level: /* istanbul ignore next */ process.env.DEBUG ? 'debug' : 'info',
log_level: /* v8 ignore next */ process.env.DEBUG ? 'debug' : 'info',
log_namespaced_levels: {},
log_syslog: {},
log_debug_to_mqtt_frontend: false,
@@ -353,7 +353,6 @@ function applyEnvironmentVariables(settings: Partial<Settings>): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setting[key as keyof Settings] = (envVariable.toLowerCase() === 'true') as any;
} else {
/* istanbul ignore else */
if (type.indexOf('string') >= 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setting[key as keyof Settings] = envVariable as any;
+4 -7
View File
@@ -54,14 +54,15 @@ function setValue(currentSettings: any, path: string[], value: unknown, createPa
currentSettings[key] = value;
} else {
if (!currentSettings[key]) {
/* istanbul ignore else */
if (createPathIfNotExist) {
currentSettings[key] = {};
/* v8 ignore start */
} else {
// invalid path
// ignored in test since currently call is always guarded by get-validated path, so this is never reached
return false;
}
/* v8 ignore stop */
}
currentSettings = currentSettings[key];
@@ -407,7 +408,6 @@ function migrateToThree(
const changeToObject = (currentSettings: Partial<Settings>, path: string[]): ReturnType<SettingsCustomHandler['execute']> => {
const [validPath, previousValue] = getValue(currentSettings, path);
/* istanbul ignore else */
if (validPath) {
if (typeof previousValue === 'boolean') {
setValue(currentSettings, path, {enabled: previousValue});
@@ -456,8 +456,8 @@ export function migrateIfNecessary(): void {
);
}
/* istanbul ignore next */
const finalVersion = process.env.JEST_WORKER_ID ? settings.testing.CURRENT_VERSION : settings.CURRENT_VERSION;
/* v8 ignore next */
const finalVersion = process.env.VITEST_WORKER_ID ? settings.testing.CURRENT_VERSION : settings.CURRENT_VERSION;
// when same version as current, nothing left to do
while (currentSettings.version !== finalVersion) {
@@ -473,7 +473,6 @@ export function migrateIfNecessary(): void {
backupSettings(currentSettings.version || 1);
// each version should only bump to the next version so as to gradually migrate if necessary
/* istanbul ignore else */
if (currentSettings.version == undefined) {
// migrating from 1 (`version` did not exist) to 2
migrationNotesFileName = 'migration-1-to-2.log';
@@ -518,13 +517,11 @@ export function migrateIfNecessary(): void {
for (const customHandler of customHandlers) {
const [validPath, previousValue, changed] = customHandler.execute(currentSettings);
/* istanbul ignore else */
if (validPath && changed && (!customHandler.noteIf || customHandler.noteIf(previousValue))) {
migrationNotes.add(`[SPECIAL] ${customHandler.note}`);
}
}
/* istanbul ignore else */
if (migrationNotesFileName && migrationNotes.size > 0) {
migrationNotes.add(`For more details, see https://github.com/Koenkk/zigbee2mqtt/discussions/24198`);
const migrationNotesFilePath = data.joinPath(migrationNotesFileName);
+3 -2
View File
@@ -61,10 +61,11 @@ async function getZigbee2MQTTVersion(includeCommitHash = true): Promise<{commitH
if (err) {
try {
commitHash = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', '.hash'), 'utf-8');
/* v8 ignore start */
} catch {
/* istanbul ignore next */
commitHash = 'unknown';
}
/* v8 ignore stop */
} else {
commitHash = commit.shortHash;
}
@@ -369,7 +370,7 @@ function deviceNotCoordinator(device: zh.Device): boolean {
return device.type !== 'Coordinator';
}
/* istanbul ignore next */
/* v8 ignore next */
const noop = (): void => {};
export default {
+2 -3
View File
@@ -92,7 +92,7 @@ export default class Zigbee {
});
this.herdsman.on('deviceInterview', async (data: ZHEvents.DeviceInterviewPayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
/* istanbul ignore if */ if (!device) return; // Prevent potential race
/* v8 ignore next */ if (!device) return; // Prevent potential race
await device.resolveDefinition();
const d = {device, status: data.status};
this.logDeviceInterview(d);
@@ -100,7 +100,7 @@ export default class Zigbee {
});
this.herdsman.on('deviceJoined', async (data: ZHEvents.DeviceJoinedPayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
/* istanbul ignore if */ if (!device) return; // Prevent potential race
/* v8 ignore next */ if (!device) return; // Prevent potential race
await device.resolveDefinition();
logger.info(`Device '${device.name}' joined`);
this.eventBus.emitDeviceJoined({device});
@@ -309,7 +309,6 @@ export default class Zigbee {
// First split the input token by the latest slash
const match = ID.match(entityIDRegex);
/* istanbul ignore else */
if (match) {
// Get the resulting IDs from the match
entityName = match[1];