Add FeatureMap template helpers for static cluster config (#1714)
- Add if_cluster_has_feature_bitmap, if_feature_bit_enabled, and cluster_feature_items helpers for Feature bitmap detection and per-bit checks in generated server cluster config templates.
- Fix Electron 41 navigation and console-message handling, use navigationHistory for back/forward when available and support the new console-message event shape; allow Electron 41.x.x in env checks.
- Adding unit tests
- JIRA: ZAPP-1716
diff --git a/docs/api.md b/docs/api.md
index b6ff0da..c169bf4 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -9196,6 +9196,10 @@
* [~featureBits(options)](#module_Templating API_ Attribute helpers..featureBits) ⇒
* [~attributeDefault()](#module_Templating API_ Attribute helpers..attributeDefault) ⇒
* [~as_underlying_atomic_identifier_for_attribute_id(attributeId)](#module_Templating API_ Attribute helpers..as_underlying_atomic_identifier_for_attribute_id)
+ * [~selectFeatureBitmapForCluster(db, packageIds, clusterId)](#module_Templating API_ Attribute helpers..selectFeatureBitmapForCluster) ⇒ <code>Promise.<(object\|null)></code>
+ * [~if_cluster_has_feature_bitmap(options)](#module_Templating API_ Attribute helpers..if_cluster_has_feature_bitmap) ⇒ <code>Promise.<string></code>
+ * [~if_feature_bit_enabled(options)](#module_Templating API_ Attribute helpers..if_feature_bit_enabled) ⇒ <code>string</code>
+ * [~cluster_feature_items(options)](#module_Templating API_ Attribute helpers..cluster_feature_items) ⇒ <code>Promise.<string></code>
<a name="module_Templating API_ Attribute helpers..count_mandatory_matter_attributes"></a>
@@ -9236,6 +9240,139 @@
| --- | --- |
| attributeId | <code>\*</code> |
+<a name="module_Templating API_ Attribute helpers..selectFeatureBitmapForCluster"></a>
+
+### Templating API: Attribute helpers~selectFeatureBitmapForCluster(db, packageIds, clusterId) ⇒ <code>Promise.<(object\|null)></code>
+Returns the cluster-scoped 'Feature' bitmap, or null when the cluster does not
+define one. Uses selectBitmapByNameAndClusterId for a targeted lookup, then
+confirms the bitmap is associated with clusterId (the DB helper may return a
+package-wide singleton when only one bitmap with that name exists).
+
+**Kind**: inner method of [<code>Templating API: Attribute helpers</code>](#module_Templating API_ Attribute helpers)
+
+| Param | Type |
+| --- | --- |
+| db | <code>\*</code> |
+| packageIds | <code>Array.<number></code> |
+| clusterId | <code>number</code> |
+
+<a name="module_Templating API_ Attribute helpers..if_cluster_has_feature_bitmap"></a>
+
+### Templating API: Attribute helpers~if\_cluster\_has\_feature\_bitmap(options) ⇒ <code>Promise.<string></code>
+Block helper that renders its body when the cluster in the current context
+defines a bitmap named 'Feature', and its inverse ({{else}}) when it does
+not. Intended for choosing between `using FeatureBitmapType = Feature;` and
+`using FeatureBitmapType = Clusters::StaticApplicationConfig::NoFeatureFlagsDefined;`
+in static-cluster-config headers.
+
+Must be used inside a context that exposes a cluster `id` field, such as
+`zcl_clusters`, `selectedServerCluster`, or any block helper whose context
+object carries the ZCL cluster database ID as `id`.
+
+**Kind**: inner method of [<code>Templating API: Attribute helpers</code>](#module_Templating API_ Attribute helpers)
+**Returns**: <code>Promise.<string></code> - Rendered `fn` block when the cluster has a
+ bitmap named 'Feature'; rendered `inverse` block otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| options | <code>\*</code> | Handlebars options object (fn / inverse blocks). |
+
+**Example**
+```js
+// Inside a selectedServerCluster or zcl_clusters block:
+{{#if_cluster_has_feature_bitmap}}
+using FeatureBitmapType = Feature;
+{{else}}
+using FeatureBitmapType = Clusters::StaticApplicationConfig::NoFeatureFlagsDefined;
+{{/if_cluster_has_feature_bitmap}}
+```
+<a name="module_Templating API_ Attribute helpers..if_feature_bit_enabled"></a>
+
+### Templating API: Attribute helpers~if\_feature\_bit\_enabled(options) ⇒ <code>string</code>
+Block helper that renders its body when the bitwise AND of `featureMapValue`
+and `mask` is non-zero (i.e. the specific feature bit is set), and its
+inverse ({{else}}) when the bit is clear.
+
+Both arguments are parsed as integers so decimal strings (e.g. `"3"`) and
+hex strings (e.g. `"0x3"`) are accepted.
+
+Combine with `cluster_feature_items` when you want to iterate over feature
+fields and selectively render only those that are enabled:
+{{#cluster_feature_items clusterCode}}
+ {{#if_feature_bit_enabled featureMapValue mask}}...{{/if_feature_bit_enabled}}
+{{/cluster_feature_items}}
+
+**Kind**: inner method of [<code>Templating API: Attribute helpers</code>](#module_Templating API_ Attribute helpers)
+**Returns**: <code>string</code> - Rendered `fn` block when `(featureMapValue & mask) !== 0`;
+ rendered `inverse` block otherwise.
+**Given**: <code>string\|number</code> featureMapValue - The raw FeatureMap attribute default
+ value for the current endpoint cluster (e.g. `"3"`).
+**Given**: <code>string\|number</code> mask - The bitmask of the feature field being tested
+ (e.g. `1`).
+
+| Param | Type | Description |
+| --- | --- | --- |
+| options | <code>\*</code> | Handlebars options object (fn / inverse blocks). |
+
+**Example**
+```js
+// LevelControl, featureMap default = 3 (kOnOff | kLighting):
+{{#if_feature_bit_enabled "3" "1"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+// → "enabled"
+{{#if_feature_bit_enabled "3" "4"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+// → "disabled" (kFrequency bit 0x4 is not set)
+```
+<a name="module_Templating API_ Attribute helpers..cluster_feature_items"></a>
+
+### Templating API: Attribute helpers~cluster\_feature\_items(options) ⇒ <code>Promise.<string></code>
+Block helper that iterates over all fields of the Feature bitmap for a
+cluster identified by its ZCL cluster code, regardless of which bits are
+currently enabled. Use `if_feature_bit_enabled` inside the body to act on
+only those fields whose bit is set in a given FeatureMap value.
+
+This is the preferred way to access Feature bitmap fields inside
+`user_cluster_attributes` because `zcl_bitmaps` requires a cluster database
+`id` in context that is not available there. This helper performs the cluster
+lookup by ZCL code (not name) for robustness.
+
+Each iteration context exposes:
+ - `name` {string} field name as stored in the ZCL database (e.g. `"kOnOff"`)
+ - `label` {string} same as `name`
+ - `mask` {number} the field's bitmask (e.g. `1`)
+
+If the cluster has no bitmap named 'Feature' the body is never rendered.
+
+**Kind**: inner method of [<code>Templating API: Attribute helpers</code>](#module_Templating API_ Attribute helpers)
+**Returns**: <code>Promise.<string></code> - Concatenated rendered blocks for each feature field.
+**Given**: <code>string\|number</code> clusterCode - The ZCL cluster code (e.g. `8` for
+ LevelControl). Inside `user_cluster_attributes` pass `../code` to
+ reference the enclosing `user_clusters` cluster code.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| options | <code>\*</code> | Handlebars options object. |
+
+**Example**
+```js
+// List only enabled feature bits for LevelControl (featureMap default = 3):
+{{#if (is_str_equal name "FeatureMap")}}
+{{#cluster_feature_items ../code}}
+{{#if_feature_bit_enabled ../defaultValue mask}}
+ FeatureBitmapType::k{{asUpperCamelCase label preserveAcronyms=true}}, // feature bit {{as_hex mask}}
+{{/if_feature_bit_enabled}}
+{{/cluster_feature_items}}
+{{/if}}
+// Emits: FeatureBitmapType::kOnOff, // feature bit 0x1
+// FeatureBitmapType::kLighting, // feature bit 0x2
+// Skips kFrequency (mask 0x4 not set in value 3)
+```
+**Example**
+```js
+// List ALL defined feature bits regardless of enabled state:
+{{#cluster_feature_items clusterCode}}
+ {{label}}: {{as_hex mask}}
+{{/cluster_feature_items}}
+```
<a name="module_Templating API_ C formatting helpers"></a>
## Templating API: C formatting helpers
diff --git a/docs/helpers.md b/docs/helpers.md
index 9b47035..7a1aad7 100644
--- a/docs/helpers.md
+++ b/docs/helpers.md
@@ -221,6 +221,10 @@
* [~featureBits(options)](#module_Templating API_ Attribute helpers..featureBits) ⇒
* [~attributeDefault()](#module_Templating API_ Attribute helpers..attributeDefault) ⇒
* [~as_underlying_atomic_identifier_for_attribute_id(attributeId)](#module_Templating API_ Attribute helpers..as_underlying_atomic_identifier_for_attribute_id)
+ * [~selectFeatureBitmapForCluster(db, packageIds, clusterId)](#module_Templating API_ Attribute helpers..selectFeatureBitmapForCluster) ⇒ <code>Promise.<(object\|null)></code>
+ * [~if_cluster_has_feature_bitmap(options)](#module_Templating API_ Attribute helpers..if_cluster_has_feature_bitmap) ⇒ <code>Promise.<string></code>
+ * [~if_feature_bit_enabled(options)](#module_Templating API_ Attribute helpers..if_feature_bit_enabled) ⇒ <code>string</code>
+ * [~cluster_feature_items(options)](#module_Templating API_ Attribute helpers..cluster_feature_items) ⇒ <code>Promise.<string></code>
<a name="module_Templating API_ Attribute helpers..count_mandatory_matter_attributes"></a>
@@ -261,6 +265,139 @@
| --- | --- |
| attributeId | <code>\*</code> |
+<a name="module_Templating API_ Attribute helpers..selectFeatureBitmapForCluster"></a>
+
+### Templating API: Attribute helpers~selectFeatureBitmapForCluster(db, packageIds, clusterId) ⇒ <code>Promise.<(object\|null)></code>
+Returns the cluster-scoped 'Feature' bitmap, or null when the cluster does not
+define one. Uses selectBitmapByNameAndClusterId for a targeted lookup, then
+confirms the bitmap is associated with clusterId (the DB helper may return a
+package-wide singleton when only one bitmap with that name exists).
+
+**Kind**: inner method of [<code>Templating API: Attribute helpers</code>](#module_Templating API_ Attribute helpers)
+
+| Param | Type |
+| --- | --- |
+| db | <code>\*</code> |
+| packageIds | <code>Array.<number></code> |
+| clusterId | <code>number</code> |
+
+<a name="module_Templating API_ Attribute helpers..if_cluster_has_feature_bitmap"></a>
+
+### Templating API: Attribute helpers~if\_cluster\_has\_feature\_bitmap(options) ⇒ <code>Promise.<string></code>
+Block helper that renders its body when the cluster in the current context
+defines a bitmap named 'Feature', and its inverse ({{else}}) when it does
+not. Intended for choosing between `using FeatureBitmapType = Feature;` and
+`using FeatureBitmapType = Clusters::StaticApplicationConfig::NoFeatureFlagsDefined;`
+in static-cluster-config headers.
+
+Must be used inside a context that exposes a cluster `id` field, such as
+`zcl_clusters`, `selectedServerCluster`, or any block helper whose context
+object carries the ZCL cluster database ID as `id`.
+
+**Kind**: inner method of [<code>Templating API: Attribute helpers</code>](#module_Templating API_ Attribute helpers)
+**Returns**: <code>Promise.<string></code> - Rendered `fn` block when the cluster has a
+ bitmap named 'Feature'; rendered `inverse` block otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| options | <code>\*</code> | Handlebars options object (fn / inverse blocks). |
+
+**Example**
+```js
+// Inside a selectedServerCluster or zcl_clusters block:
+{{#if_cluster_has_feature_bitmap}}
+using FeatureBitmapType = Feature;
+{{else}}
+using FeatureBitmapType = Clusters::StaticApplicationConfig::NoFeatureFlagsDefined;
+{{/if_cluster_has_feature_bitmap}}
+```
+<a name="module_Templating API_ Attribute helpers..if_feature_bit_enabled"></a>
+
+### Templating API: Attribute helpers~if\_feature\_bit\_enabled(options) ⇒ <code>string</code>
+Block helper that renders its body when the bitwise AND of `featureMapValue`
+and `mask` is non-zero (i.e. the specific feature bit is set), and its
+inverse ({{else}}) when the bit is clear.
+
+Both arguments are parsed as integers so decimal strings (e.g. `"3"`) and
+hex strings (e.g. `"0x3"`) are accepted.
+
+Combine with `cluster_feature_items` when you want to iterate over feature
+fields and selectively render only those that are enabled:
+{{#cluster_feature_items clusterCode}}
+ {{#if_feature_bit_enabled featureMapValue mask}}...{{/if_feature_bit_enabled}}
+{{/cluster_feature_items}}
+
+**Kind**: inner method of [<code>Templating API: Attribute helpers</code>](#module_Templating API_ Attribute helpers)
+**Returns**: <code>string</code> - Rendered `fn` block when `(featureMapValue & mask) !== 0`;
+ rendered `inverse` block otherwise.
+**Given**: <code>string\|number</code> featureMapValue - The raw FeatureMap attribute default
+ value for the current endpoint cluster (e.g. `"3"`).
+**Given**: <code>string\|number</code> mask - The bitmask of the feature field being tested
+ (e.g. `1`).
+
+| Param | Type | Description |
+| --- | --- | --- |
+| options | <code>\*</code> | Handlebars options object (fn / inverse blocks). |
+
+**Example**
+```js
+// LevelControl, featureMap default = 3 (kOnOff | kLighting):
+{{#if_feature_bit_enabled "3" "1"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+// → "enabled"
+{{#if_feature_bit_enabled "3" "4"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+// → "disabled" (kFrequency bit 0x4 is not set)
+```
+<a name="module_Templating API_ Attribute helpers..cluster_feature_items"></a>
+
+### Templating API: Attribute helpers~cluster\_feature\_items(options) ⇒ <code>Promise.<string></code>
+Block helper that iterates over all fields of the Feature bitmap for a
+cluster identified by its ZCL cluster code, regardless of which bits are
+currently enabled. Use `if_feature_bit_enabled` inside the body to act on
+only those fields whose bit is set in a given FeatureMap value.
+
+This is the preferred way to access Feature bitmap fields inside
+`user_cluster_attributes` because `zcl_bitmaps` requires a cluster database
+`id` in context that is not available there. This helper performs the cluster
+lookup by ZCL code (not name) for robustness.
+
+Each iteration context exposes:
+ - `name` {string} field name as stored in the ZCL database (e.g. `"kOnOff"`)
+ - `label` {string} same as `name`
+ - `mask` {number} the field's bitmask (e.g. `1`)
+
+If the cluster has no bitmap named 'Feature' the body is never rendered.
+
+**Kind**: inner method of [<code>Templating API: Attribute helpers</code>](#module_Templating API_ Attribute helpers)
+**Returns**: <code>Promise.<string></code> - Concatenated rendered blocks for each feature field.
+**Given**: <code>string\|number</code> clusterCode - The ZCL cluster code (e.g. `8` for
+ LevelControl). Inside `user_cluster_attributes` pass `../code` to
+ reference the enclosing `user_clusters` cluster code.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| options | <code>\*</code> | Handlebars options object. |
+
+**Example**
+```js
+// List only enabled feature bits for LevelControl (featureMap default = 3):
+{{#if (is_str_equal name "FeatureMap")}}
+{{#cluster_feature_items ../code}}
+{{#if_feature_bit_enabled ../defaultValue mask}}
+ FeatureBitmapType::k{{asUpperCamelCase label preserveAcronyms=true}}, // feature bit {{as_hex mask}}
+{{/if_feature_bit_enabled}}
+{{/cluster_feature_items}}
+{{/if}}
+// Emits: FeatureBitmapType::kOnOff, // feature bit 0x1
+// FeatureBitmapType::kLighting, // feature bit 0x2
+// Skips kFrequency (mask 0x4 not set in value 3)
+```
+**Example**
+```js
+// List ALL defined feature bits regardless of enabled state:
+{{#cluster_feature_items clusterCode}}
+ {{label}}: {{as_hex mask}}
+{{/cluster_feature_items}}
+```
<a name="module_Templating API_ C formatting helpers"></a>
## Templating API: C formatting helpers
diff --git a/src-electron/generator/helper-attribute.js b/src-electron/generator/helper-attribute.js
index 613ca92..0ce7109 100644
--- a/src-electron/generator/helper-attribute.js
+++ b/src-electron/generator/helper-attribute.js
@@ -205,6 +205,190 @@
}
}
+/**
+ * Returns the cluster-scoped 'Feature' bitmap, or null when the cluster does not
+ * define one. Uses selectBitmapByNameAndClusterId for a targeted lookup, then
+ * confirms the bitmap is associated with clusterId (the DB helper may return a
+ * package-wide singleton when only one bitmap with that name exists).
+ *
+ * @param {*} db
+ * @param {number[]} packageIds
+ * @param {number} clusterId
+ * @returns {Promise<object|null>}
+ */
+async function selectFeatureBitmapForCluster(db, packageIds, clusterId) {
+ const candidate = await queryZcl.selectBitmapByNameAndClusterId(
+ db,
+ 'Feature',
+ clusterId,
+ packageIds
+ )
+ if (!candidate) {
+ return null
+ }
+ const clusterBitmaps = (
+ await Promise.all(
+ packageIds.map((pkgId) =>
+ queryZcl.selectClusterBitmaps(db, pkgId, clusterId)
+ )
+ )
+ ).flat()
+ return clusterBitmaps.some((b) => b.id === candidate.id) ? candidate : null
+}
+
+/**
+ * Block helper that renders its body when the cluster in the current context
+ * defines a bitmap named 'Feature', and its inverse ({{else}}) when it does
+ * not. Intended for choosing between `using FeatureBitmapType = Feature;` and
+ * `using FeatureBitmapType = Clusters::StaticApplicationConfig::NoFeatureFlagsDefined;`
+ * in static-cluster-config headers.
+ *
+ * Must be used inside a context that exposes a cluster `id` field, such as
+ * `zcl_clusters`, `selectedServerCluster`, or any block helper whose context
+ * object carries the ZCL cluster database ID as `id`.
+ *
+ * @param {*} options - Handlebars options object (fn / inverse blocks).
+ * @returns {Promise<string>} Rendered `fn` block when the cluster has a
+ * bitmap named 'Feature'; rendered `inverse` block otherwise.
+ *
+ * @example
+ * // Inside a selectedServerCluster or zcl_clusters block:
+ * {{#if_cluster_has_feature_bitmap}}
+ * using FeatureBitmapType = Feature;
+ * {{else}}
+ * using FeatureBitmapType = Clusters::StaticApplicationConfig::NoFeatureFlagsDefined;
+ * {{/if_cluster_has_feature_bitmap}}
+ */
+async function if_cluster_has_feature_bitmap(options) {
+ if (!('id' in this))
+ throw new Error(
+ 'if_cluster_has_feature_bitmap requires a cluster id inside the context.'
+ )
+ const packageIds = await templateUtil.ensureZclPackageIds(this)
+ const featureBitmap = await selectFeatureBitmapForCluster(
+ this.global.db,
+ packageIds,
+ this.id
+ )
+ return featureBitmap ? options.fn(this) : options.inverse(this)
+}
+
+/**
+ * Block helper that renders its body when the bitwise AND of `featureMapValue`
+ * and `mask` is non-zero (i.e. the specific feature bit is set), and its
+ * inverse ({{else}}) when the bit is clear.
+ *
+ * Both arguments are parsed as integers so decimal strings (e.g. `"3"`) and
+ * hex strings (e.g. `"0x3"`) are accepted.
+ *
+ * Combine with `cluster_feature_items` when you want to iterate over feature
+ * fields and selectively render only those that are enabled:
+ * {{#cluster_feature_items clusterCode}}
+ * {{#if_feature_bit_enabled featureMapValue mask}}...{{/if_feature_bit_enabled}}
+ * {{/cluster_feature_items}}
+ *
+ * @given {string|number} featureMapValue - The raw FeatureMap attribute default
+ * value for the current endpoint cluster (e.g. `"3"`).
+ * @given {string|number} mask - The bitmask of the feature field being tested
+ * (e.g. `1`).
+ * @param {*} options - Handlebars options object (fn / inverse blocks).
+ * @returns {string} Rendered `fn` block when `(featureMapValue & mask) !== 0`;
+ * rendered `inverse` block otherwise.
+ *
+ * @example
+ * // LevelControl, featureMap default = 3 (kOnOff | kLighting):
+ * {{#if_feature_bit_enabled "3" "1"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+ * // → "enabled"
+ * {{#if_feature_bit_enabled "3" "4"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+ * // → "disabled" (kFrequency bit 0x4 is not set)
+ */
+function if_feature_bit_enabled(featureMapValue, mask, options) {
+ const value = parseInt(featureMapValue)
+ return !isNaN(value) && (value & parseInt(mask)) !== 0
+ ? options.fn(this)
+ : options.inverse(this)
+}
+
+/**
+ * Block helper that iterates over all fields of the Feature bitmap for a
+ * cluster identified by its ZCL cluster code, regardless of which bits are
+ * currently enabled. Use `if_feature_bit_enabled` inside the body to act on
+ * only those fields whose bit is set in a given FeatureMap value.
+ *
+ * This is the preferred way to access Feature bitmap fields inside
+ * `user_cluster_attributes` because `zcl_bitmaps` requires a cluster database
+ * `id` in context that is not available there. This helper performs the cluster
+ * lookup by ZCL code (not name) for robustness.
+ *
+ * Each iteration context exposes:
+ * - `name` {string} field name as stored in the ZCL database (e.g. `"kOnOff"`)
+ * - `label` {string} same as `name`
+ * - `mask` {number} the field's bitmask (e.g. `1`)
+ *
+ * If the cluster has no bitmap named 'Feature' the body is never rendered.
+ *
+ * @given {string|number} clusterCode - The ZCL cluster code (e.g. `8` for
+ * LevelControl). Inside `user_cluster_attributes` pass `../code` to
+ * reference the enclosing `user_clusters` cluster code.
+ * @param {*} options - Handlebars options object.
+ * @returns {Promise<string>} Concatenated rendered blocks for each feature field.
+ *
+ * @example
+ * // List only enabled feature bits for LevelControl (featureMap default = 3):
+ * {{#if (is_str_equal name "FeatureMap")}}
+ * {{#cluster_feature_items ../code}}
+ * {{#if_feature_bit_enabled ../defaultValue mask}}
+ * FeatureBitmapType::k{{asUpperCamelCase label preserveAcronyms=true}}, // feature bit {{as_hex mask}}
+ * {{/if_feature_bit_enabled}}
+ * {{/cluster_feature_items}}
+ * {{/if}}
+ * // Emits: FeatureBitmapType::kOnOff, // feature bit 0x1
+ * // FeatureBitmapType::kLighting, // feature bit 0x2
+ * // Skips kFrequency (mask 0x4 not set in value 3)
+ *
+ * @example
+ * // List ALL defined feature bits regardless of enabled state:
+ * {{#cluster_feature_items clusterCode}}
+ * {{label}}: {{as_hex mask}}
+ * {{/cluster_feature_items}}
+ */
+async function cluster_feature_items(clusterCode, options) {
+ const packageIds = await templateUtil.ensureZclPackageIds(this)
+
+ // Resolve the internal cluster DB ID from the ZCL cluster code.
+ let clusterId = null
+ for (const pkgId of packageIds) {
+ const cluster = await queryZcl.selectClusterByCode(
+ this.global.db,
+ pkgId,
+ parseInt(clusterCode)
+ )
+ if (cluster) {
+ clusterId = cluster.id
+ break
+ }
+ }
+ if (clusterId == null) {
+ return ''
+ }
+
+ const featureBitmap = await selectFeatureBitmapForCluster(
+ this.global.db,
+ packageIds,
+ clusterId
+ )
+ if (!featureBitmap) {
+ return ''
+ }
+
+ const allFields = await queryZcl.selectAllBitmapFieldsById(
+ this.global.db,
+ featureBitmap.id
+ )
+ const p = templateUtil.collectBlocks(allFields, options, this)
+ return templateUtil.templatePromise(this.global, p)
+}
+
// WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
//
// Note: these exports are public API. Templates that might have been created in the past and are
@@ -216,3 +400,6 @@
exports.as_underlying_atomic_identifier_for_attribute_id =
as_underlying_atomic_identifier_for_attribute_id
exports.count_mandatory_matter_attributes = count_mandatory_matter_attributes
+exports.if_cluster_has_feature_bitmap = if_cluster_has_feature_bitmap
+exports.if_feature_bit_enabled = if_feature_bit_enabled
+exports.cluster_feature_items = cluster_feature_items
diff --git a/src-electron/ui/menu.js b/src-electron/ui/menu.js
index afac220..1ec2c9f 100644
--- a/src-electron/ui/menu.js
+++ b/src-electron/ui/menu.js
@@ -108,13 +108,23 @@
{
label: 'Navigate back ...',
click(menuItem, browserWindow, event) {
- browserWindow.webContents.goBack()
+ const nav = browserWindow.webContents.navigationHistory
+ if (nav) {
+ nav.goBack()
+ } else {
+ browserWindow.webContents.goBack()
+ }
}
},
{
label: 'Navigate forward ...',
click(menuItem, browserWindow, event) {
- browserWindow.webContents.goForward()
+ const nav = browserWindow.webContents.navigationHistory
+ if (nav) {
+ nav.goForward()
+ } else {
+ browserWindow.webContents.goForward()
+ }
}
},
{ role: 'reload' },
diff --git a/src-electron/ui/window.js b/src-electron/ui/window.js
index b017b33..994958e 100644
--- a/src-electron/ui/window.js
+++ b/src-electron/ui/window.js
@@ -187,14 +187,15 @@
}
}) // EO close
- w.webContents.on(
- 'console-message',
- (event, level, message, line, sourceId) => {
- if (!browserApi.processRendererNotify(w, message)) {
- env.logBrowser(message)
- }
+ w.webContents.on('console-message', (event, ...legacyArgs) => {
+ // Electron 41+ passes a single WebContentsConsoleMessageEventParams object;
+ // older versions passed (level, message, line, sourceId) positional args.
+ const message =
+ typeof event.message === 'string' ? event.message : (legacyArgs[1] ?? '')
+ if (!browserApi.processRendererNotify(w, message)) {
+ env.logBrowser(message)
}
- )
+ })
w.webContents.on('before-input-event', (e, input) => {
if (input.type === 'keyUp' && input.key.toLowerCase() === 'alt') {
menu.toggleMenu(port)
diff --git a/src-electron/util/env.js b/src-electron/util/env.js
index 7a87bcd..5690303 100644
--- a/src-electron/util/env.js
+++ b/src-electron/util/env.js
@@ -596,7 +596,8 @@
'18.x.x',
'24.x.x',
'27.x.x',
- '31.x.x'
+ '31.x.x',
+ '41.x.x'
]
let nodeVersion = process.version
let electronVersion = process.versions.electron
diff --git a/test/gen-matter-1.test.js b/test/gen-matter-1.test.js
index bc43450..867d3fd 100644
--- a/test/gen-matter-1.test.js
+++ b/test/gen-matter-1.test.js
@@ -324,6 +324,49 @@
'> Field: 0 SoftwareVersion default_value=0x00000000'
)
expect(eventOut).not.toContain('> Field: ProductID default_value=')
+
+ // Testing the FeatureMap template helpers added in helper-attribute.js:
+ // if_cluster_has_feature_bitmap, cluster_feature_items, if_feature_bit_enabled
+ // The feature-map.zapt template exercises these helpers; assertions below
+ // verify that the generated feature-map.h reflects the Feature bitmap
+ // information loaded from cluster XMLs (e.g. level-control-cluster.xml and
+ // color-control-cluster.xml).
+ let featureMap = genResult.content['feature-map.h']
+ expect(featureMap).not.toBeNull()
+
+ // if_cluster_has_feature_bitmap: clusters defining <features> in their XML
+ // (Level Control / Color Control) must render the affirmative branch.
+ expect(featureMap).toContain('- Level Control has Feature bitmap')
+ expect(featureMap).toContain('- Color Control has Feature bitmap')
+ // A cluster with no Feature bitmap defined must render the {{else}} branch.
+ expect(featureMap).toContain('- Descriptor has no Feature bitmap')
+
+ // cluster_feature_items: iterates all fields of the Feature bitmap for the
+ // given cluster code. Level Control (0x0008) defines OO/LT/FQ feature bits;
+ // Color Control (0x0300) defines HS/EHUE/CL/XY/CT feature bits. The helper
+ // exposes each field's name as `label` and bit-position as `mask`.
+ expect(featureMap).toContain('LevelControl feature: OnOff mask=0x01')
+ expect(featureMap).toContain('LevelControl feature: Lighting mask=0x02')
+ expect(featureMap).toContain('LevelControl feature: Frequency mask=0x04')
+ expect(featureMap).toContain(
+ 'ColorControl feature: Hue/Saturation mask=0x01'
+ )
+ expect(featureMap).toContain('ColorControl feature: Enhanced Hue mask=0x02')
+ expect(featureMap).toContain('ColorControl feature: Color loop mask=0x04')
+ expect(featureMap).toContain('ColorControl feature: XY mask=0x08')
+ expect(featureMap).toContain(
+ 'ColorControl feature: Color temperature mask=0x10'
+ )
+ // Unknown cluster code: helper renders an empty inner block.
+ expect(featureMap).not.toContain('UnknownCluster feature:')
+
+ // if_feature_bit_enabled: bitwise-AND check accepting both decimal and hex
+ // string inputs; non-numeric input renders the {{else}} branch.
+ expect(featureMap).toContain('- value=3 mask=1 => enabled')
+ expect(featureMap).toContain('- value=3 mask=2 => enabled')
+ expect(featureMap).toContain('- value=3 mask=4 => disabled')
+ expect(featureMap).toContain('- value=0x1F mask=0x10 => enabled')
+ expect(featureMap).toContain('- value=notanumber mask=1 => disabled')
},
testUtil.timeout.long()
)
diff --git a/test/gen-template/matter/feature-map.zapt b/test/gen-template/matter/feature-map.zapt
index 8c1f317..db83a84 100644
--- a/test/gen-template/matter/feature-map.zapt
+++ b/test/gen-template/matter/feature-map.zapt
@@ -9,4 +9,30 @@
{{index}}: Bit {{bit}} is assigned to tag {{tag}} => value = {{value}}
{{/feature_bits}}
{{/global_attribute_default}}
-{{/zcl_clusters}}
\ No newline at end of file
+{{#if_cluster_has_feature_bitmap}}
+- {{label}} has Feature bitmap
+{{else}}
+- {{label}} has no Feature bitmap
+{{/if_cluster_has_feature_bitmap}}
+{{/zcl_clusters}}
+
+// cluster_feature_items helper:
+// Level Control (code 0x0008) -- features defined inline in level-control-cluster.xml:
+{{#cluster_feature_items 8}}
+ LevelControl feature: {{label}} mask={{asHex mask 2}}
+{{/cluster_feature_items}}
+// Color Control (code 0x0300) -- features defined inline in color-control-cluster.xml:
+{{#cluster_feature_items 768}}
+ ColorControl feature: {{label}} mask={{asHex mask 2}}
+{{/cluster_feature_items}}
+// Unknown cluster code (no output expected, no Feature bitmap defined):
+{{#cluster_feature_items 65535}}
+ UnknownCluster feature: {{label}} mask={{asHex mask 2}}
+{{/cluster_feature_items}}
+
+// if_feature_bit_enabled helper:
+- value=3 mask=1 => {{#if_feature_bit_enabled "3" "1"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+- value=3 mask=2 => {{#if_feature_bit_enabled "3" "2"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+- value=3 mask=4 => {{#if_feature_bit_enabled "3" "4"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+- value=0x1F mask=0x10 => {{#if_feature_bit_enabled "0x1F" "0x10"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
+- value=notanumber mask=1 => {{#if_feature_bit_enabled "notanumber" "1"}}enabled{{else}}disabled{{/if_feature_bit_enabled}}
\ No newline at end of file