From 8fe609b2e97ae4a248a2b4b537ad92da7b0b7b1b Mon Sep 17 00:00:00 2001 From: Max Chodorowski Date: Fri, 16 Feb 2024 18:45:40 +0000 Subject: [PATCH 1/3] Fixed filtering by the exact value type --- src/filter.ts | 13 ++++++++----- src/typings.d.ts | 7 ++++++- src/utils.ts | 12 ++++++------ test/other/filter.test.ts | 19 +++++++++++++++++++ 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/filter.ts b/src/filter.ts index 95a0498..66504be 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -3,17 +3,17 @@ import { getRegexFromString, getValueFromObject, isNumber, log, toNumber } from /** * Functions to check if filter condition is met */ -const operatorHandlers: { [key in FilterOperator]: (val: string | number | undefined, expectedVal: string | number | undefined) => boolean } = { +const operatorHandlers: { [key in FilterOperator]: (val: FilterValueType, expectedVal: FilterValueType) => boolean } = { "exists": val => val !== undefined, "not_exists": val => val === undefined, - "contains": (val, searchString) => val !== undefined && val.toString().indexOf(searchString!.toString()) != -1, + "contains": (val, searchString) => val !== undefined && val !== null && val.toString().indexOf(searchString!.toString()) != -1, "=": (val, expectedVal) => isNumber(val) || isNumber(expectedVal) ? toNumber(val) == toNumber(expectedVal) : val == expectedVal, ">": (val, expectedVal) => toNumber(val) > toNumber(expectedVal), "<": (val, expectedVal) => toNumber(val) < toNumber(expectedVal), ">=": (val, expectedVal) => toNumber(val) >= toNumber(expectedVal), "<=": (val, expectedVal) => toNumber(val) <= toNumber(expectedVal), "matches": (val, pattern) => { - if (val === undefined) { + if (val === undefined || val === null) { return false; } @@ -62,7 +62,7 @@ export class Filter { * @param entityData Hass entity data * @param state State override - battery state/level */ - private getValue(entityData: any, state?: string): string | undefined { + private getValue(entityData: any, state?: string): FilterValueType { if (!this.config.name) { log("Missing filter 'name' property"); return; @@ -79,13 +79,16 @@ export class Filter { * Checks whether value meets the filter conditions. * @param val Value to validate */ - private meetsExpectations(val: string | number | undefined): boolean { + private meetsExpectations(val: FilterValueType): boolean { let operator = this.config.operator; if (!operator) { if (this.config.value === undefined) { operator = "exists"; } + else if (this.config.value === null) { + operator = "="; + } else { const expectedVal = this.config.value.toString(); const regex = getRegexFromString(expectedVal); diff --git a/src/typings.d.ts b/src/typings.d.ts index c88aac5..cc204de 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -153,6 +153,11 @@ type FilterGroups = "exclude" | "include"; */ type FilterOperator = "exists" | "not_exists" | "=" | ">" | "<" | ">=" | "<=" | "contains" | "matches"; +/** + * Allowed filter value types + */ +type FilterValueType = string | number | boolean | null | undefined; + /** * Filter object */ @@ -170,7 +175,7 @@ interface IFilter { /** * Value to compare with the extracted one */ - value?: string | number; + value?: FilterValueType; } interface IBatteryEntityConfig { diff --git a/src/utils.ts b/src/utils.ts index 644cf71..9494e99 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -17,8 +17,8 @@ export const log = (message: string, level: "warn" | "error" = "warn") => { * Checks whether given value is a number * @param val String value to check */ -export const isNumber = (value: string | number | undefined): boolean => { - if (value === undefined) { +export const isNumber = (value: string | number | boolean | null | undefined): boolean => { + if (value === undefined || value === null || typeof value === "boolean") { return false; } @@ -27,7 +27,7 @@ export const isNumber = (value: string | number | undefined): boolean => { value = value.replace(",", "."); } - return (value!== undefined && value !== null && value !== '' && !isNaN(Number(value))) + return value !== '' && !isNaN(Number(value)); } /** @@ -35,7 +35,7 @@ export const isNumber = (value: string | number | undefined): boolean => { * @param value String value to convert * @returns Result number */ -export const toNumber = (value: string | number | undefined): number => { +export const toNumber = (value: string | number | boolean | null | undefined): number => { if (typeof(value) == "string") { // trying to solve decimal number formatting in some langs value = value.replace(",", "."); @@ -153,7 +153,7 @@ export const getRegexFromString = (ruleVal: string): RegExp | null => { * @param path Path to the value * @returns Value from the path */ -export const getValueFromObject = (dataObject: any, path: string): string | undefined => { +export const getValueFromObject = (dataObject: any, path: string): string | number | boolean | null | undefined => { const chunks = path.split("."); for (let i = 0; i < chunks.length; i++) { @@ -167,5 +167,5 @@ export const getValueFromObject = (dataObject: any, path: string): string | unde dataObject = JSON.stringify(dataObject); } - return dataObject === undefined ? undefined : dataObject.toString(); + return dataObject; } \ No newline at end of file diff --git a/test/other/filter.test.ts b/test/other/filter.test.ts index d45c8b6..ba111fe 100644 --- a/test/other/filter.test.ts +++ b/test/other/filter.test.ts @@ -118,6 +118,25 @@ describe("Filter", () => { expect(isValid).toBe(expectedIsVlid); }) + test.each([ + [44, "<", "44,1", true], + [44, ">", "44.1", false], + [true, "=", "false", false], + [true, "=", "true", false], + [true, "=", true, true], + [true, undefined, true, true], + [false, undefined, true, false], + [true, undefined, false, false], + ])("non mixed types of values", (attributeValue: FilterValueType, operator: FilterOperator | undefined, value: FilterValueType, expectedIsVlid: boolean) => { + const hassMock = new HomeAssistantMock(); + + const entity = hassMock.addEntity("Entity name", "ok", { entity_attrib: attributeValue }); + + const filter = new Filter({ name: "attributes.entity_attrib", operator, value }); + const isValid = filter.isValid(entity); + + expect(isValid).toBe(expectedIsVlid); + }) test.each([ [{ state: "45", device: { name: "Device name" } }, "path.missing", "Device name", false], From 6e067e2d510c0ac9732a474cbb6404c5cdb2f35d Mon Sep 17 00:00:00 2001 From: Max Chodorowski Date: Fri, 16 Feb 2024 19:18:51 +0000 Subject: [PATCH 2/3] Fixed null case --- src/utils.ts | 2 +- test/other/filter.test.ts | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 9494e99..83bd4de 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -163,7 +163,7 @@ export const getValueFromObject = (dataObject: any, path: string): string | numb } } - if (typeof dataObject == "object") { + if (dataObject !== null && typeof dataObject == "object") { dataObject = JSON.stringify(dataObject); } diff --git a/test/other/filter.test.ts b/test/other/filter.test.ts index ba111fe..cc1cb7c 100644 --- a/test/other/filter.test.ts +++ b/test/other/filter.test.ts @@ -119,14 +119,16 @@ describe("Filter", () => { expect(isValid).toBe(expectedIsVlid); }) test.each([ - [44, "<", "44,1", true], - [44, ">", "44.1", false], - [true, "=", "false", false], - [true, "=", "true", false], - [true, "=", true, true], - [true, undefined, true, true], - [false, undefined, true, false], - [true, undefined, false, false], + // [44, "<", "44,1", true], + // [44, ">", "44.1", false], + // [true, "=", "false", false], + // [true, "=", "true", false], + // [true, "=", true, true], + // [true, undefined, true, true], + // [false, undefined, true, false], + // [true, undefined, false, false], + // [true, undefined, null, false], + [null, undefined, null, true], ])("non mixed types of values", (attributeValue: FilterValueType, operator: FilterOperator | undefined, value: FilterValueType, expectedIsVlid: boolean) => { const hassMock = new HomeAssistantMock(); From 27791b5ef3206eb793d938bb2708f268dd443509 Mon Sep 17 00:00:00 2001 From: Max Chodorowski Date: Fri, 16 Feb 2024 19:23:13 +0000 Subject: [PATCH 3/3] Reenabling tests --- test/other/filter.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/other/filter.test.ts b/test/other/filter.test.ts index cc1cb7c..33fd840 100644 --- a/test/other/filter.test.ts +++ b/test/other/filter.test.ts @@ -119,15 +119,15 @@ describe("Filter", () => { expect(isValid).toBe(expectedIsVlid); }) test.each([ - // [44, "<", "44,1", true], - // [44, ">", "44.1", false], - // [true, "=", "false", false], - // [true, "=", "true", false], - // [true, "=", true, true], - // [true, undefined, true, true], - // [false, undefined, true, false], - // [true, undefined, false, false], - // [true, undefined, null, false], + [44, "<", "44,1", true], + [44, ">", "44.1", false], + [true, "=", "false", false], + [true, "=", "true", false], + [true, "=", true, true], + [true, undefined, true, true], + [false, undefined, true, false], + [true, undefined, false, false], + [true, undefined, null, false], [null, undefined, null, true], ])("non mixed types of values", (attributeValue: FilterValueType, operator: FilterOperator | undefined, value: FilterValueType, expectedIsVlid: boolean) => { const hassMock = new HomeAssistantMock();