• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

maxwroc / battery-state-card / 23565614488

25 Mar 2026 09:42PM UTC coverage: 91.696% (-2.0%) from 93.709%
23565614488

Pull #865

github

web-flow
Merge 71a98021e into a7256b01c
Pull Request #865: Fixed battery notes handling

671 of 770 branches covered (87.14%)

Branch coverage included in aggregate %.

146 of 157 new or added lines in 10 files covered. (92.99%)

1 existing line in 1 file now uncovered.

875 of 916 relevant lines covered (95.52%)

33.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.14
/src/rich-string-processor.ts
1
import { log } from "./utils";
6✔
2
import { EntityDataAccessor } from "./entity-data-accessor";
3

4
/**
5
 * Class for processing keyword strings
6
 */
7
 export class RichStringProcessor {
6✔
8

9
    constructor(private accessor: EntityDataAccessor | undefined) {
133✔
10
    }
11

12
    /**
13
     * Replaces keywords in given string with the data
14
     */
15
    process(text: string): string {
16
        if (!text) {
100✔
17
            return "";
3✔
18
        }
19

20
        return text.replace(/\{([^\}]+)\}/g, (matchWithBraces, keyword) => this.replaceKeyword(keyword, ""));
97✔
21
    }
22

23
    /**
24
     * Converts keyword in the final value
25
     */
26
    private replaceKeyword(keyword: string, defaultValue: string): string {
27
        const processingDetails = keyword.split("|");
85✔
28
        const dataSource = processingDetails.shift();
85✔
29

30
        const value = this.getValue(dataSource);
85✔
31

32
        if (value === undefined) {
85✔
33
            return defaultValue;
1✔
34
        }
35

36
        const processors = processingDetails.map(command => {
84✔
37
            const match = commandPattern.exec(command);
85✔
38
            if (!match || !match.groups || !availableProcessors[match.groups.func]) {
85!
39
                return undefined;
×
40
            }
41

42
            return availableProcessors[match.groups.func](match.groups.params);
85✔
43
        });
44

45
        const result = processors.filter(p => p !== undefined).reduce((res, proc) => proc!(res), value);
85✔
46

47
        return result === undefined ? defaultValue : result;
84!
48
    }
49

50
    private getValue(dataSource: string | undefined): string | undefined {
51

52
        if (dataSource === undefined) {
85!
53
            return dataSource;
×
54
        }
55

56
        if (this.accessor) {
85!
57
            let data = this.accessor.resolve(dataSource);
85✔
58
            if (typeof data == "object") {
85!
NEW
59
                data = JSON.stringify(data);
×
60
            }
61
            return data === undefined ? undefined : data.toString();
85✔
62
        }
63

NEW
64
        return undefined;
×
65
    }
66
}
67

68
const commandPattern = /(?<func>[a-z]+)\((?<params>[^\)]*)\)/;
6✔
69

70
const availableProcessors: IMap<IProcessorCtor> = {
6✔
71
    "replace": (params) => {
72
        const separatorIndex = params.indexOf(",");
3✔
73
        if (separatorIndex == -1) {
3!
74
            log("'replace' function requires two params");
×
75
            return undefined;
×
76
        }
77
        const replaceDataChunks = [params.substring(0, separatorIndex), params.substring(separatorIndex + 1)];
3✔
78

79
        return val => {
3✔
80
            return val.replace(replaceDataChunks[0], replaceDataChunks[1])
3✔
81
        };
82
    },
83
    "round": (params) => {
84
        let decimalPlaces = parseInt(params);
5✔
85
        if (isNaN(decimalPlaces)) {
5✔
86
            decimalPlaces = 0;
2✔
87
        }
88

89
        return val => parseFloat(val).toFixed(decimalPlaces);
5✔
90
    },
91
    "multiply": (params) => {
92
        if (params === "") {
4✔
93
            log("[KString]multiply function is missing parameter");
1✔
94
            return val => val;
1✔
95
        }
96

97
        const multiplier = Number(params);
3✔
98

99
        return val => isNaN(multiplier) ? val : (Number(val) * multiplier).toString();
3!
100
    },
101
    "greaterthan": (params) => {
102
        const chunks = params.split(",");
25✔
103
        if (chunks.length != 2) {
25✔
104
            log("[KString]greaterthan function requires two parameters");
1✔
105
            return val => val;
1✔
106
        }
107

108
        const compareTo = Number(chunks[0]);
24✔
109
        return val =>  Number(val) > compareTo ? chunks[1] : val;
24✔
110
    },
111
    "lessthan": (params) => {
112
        const chunks = params.split(",");
9✔
113
        if (chunks.length != 2) {
9✔
114
            log("[KString]lessthan function requires two parameters");
1✔
115
            return val => val;
1✔
116
        }
117

118
        const compareTo = Number(chunks[0]);
8✔
119
        return val =>  Number(val) < compareTo ? chunks[1] : val;
8✔
120
    },
121
    "between": (params) => {
122
        const chunks = params.split(",");
14✔
123
        if (chunks.length != 3) {
14✔
124
            log("[KString]between function requires three parameters");
1✔
125
            return val => val;
1✔
126
        }
127

128
        const compareLower = Number(chunks[0]);
13✔
129
        const compareGreater = Number(chunks[1]);
13✔
130
        return val => {
13✔
131
            const numericVal = Number(val);
13✔
132
            return compareLower <= numericVal && compareGreater >= numericVal ? chunks[2] : val;
13✔
133
        }
134
    },
135
    "thresholds": (params) => {
136
        const thresholds = params.split(",").map(v => Number(v));
34✔
137

138
        return val => {
9✔
139
            const numericVal = Number(val);
9✔
140
            const result = thresholds.findIndex(v => numericVal < v);
26✔
141

142
            if (result == -1) {
9✔
143
                // looks like the value is higher than the last threshold
144
                return "100";
2✔
145
            }
146

147
            return Math.round(100 / thresholds.length * result).toString();
7✔
148
        }
149
    },
150
    "abs": () =>
151
        val => Math.abs(Number(val)).toString(),
6✔
152
    "equals": (params) => {
153
        const chunks = params.split(",");
3✔
154
        if (chunks.length != 2) {
3✔
155
            log("[KString]equals function requires two parameters");
1✔
156
            return val => val;
1✔
157
        }
158

159
        return val =>  val == chunks[0] ? chunks[1] : val;
2✔
160
    },
161
    "add": (params) => {
162
        if (params === "") {
4✔
163
            log("[KString]add function is missing parameter");
1✔
164
            return val => val;
1✔
165
        }
166

167
        const addend = Number(params);
3✔
168

169
        return val => isNaN(addend) ? val : (Number(val) + addend).toString();
3!
170
    },
171
    "reltime": () => {
172
        return val => {
3✔
173
            const unixTime = Date.parse(val);
3✔
174
            if (isNaN(unixTime)) {
3✔
175
                log("[KString]value isn't a valid date: " + val);
1✔
176
                return val;
1✔
177
            }
178

179
            // The RT tags will be converted to proper HA tags at the views layer
180
            return `<rt>${val}</rt>`
2✔
181
        };
182
    }
183
}
184

185
interface IProcessor {
186
    (val: string): string;
187
}
188

189
interface IProcessorCtor {
190
    (params: string): IProcessor | undefined
191
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc