Skip to content

Commit fa7b662

Browse files
authored
[tuya] Fix unitHint set on non-dimension item types (#20617)
* Fix unit hint on non-dimension item types in Tuya Signed-off-by: Çağlar Eker <ekercaglar@gmail.com>
1 parent 862b408 commit fa7b662

2 files changed

Lines changed: 137 additions & 1 deletion

File tree

bundles/org.openhab.binding.tuya/src/main/java/org/openhab/binding/tuya/internal/TuyaChannelTypeProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ private ChannelType clone(ChannelTypeUID channelTypeUID, ChannelType orig) {
370370
} catch (URISyntaxException e) {
371371
}
372372

373-
if (!schemaDp.unit.isEmpty()) {
373+
if (!schemaDp.unit.isEmpty() && acceptedItemType.startsWith(NUMBER + ":")) {
374374
channelTypeBuilder.withUnitHint(schemaDp.unit);
375375
}
376376

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright (c) 2010-2026 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.tuya.internal;
14+
15+
import static org.junit.jupiter.api.Assertions.assertEquals;
16+
import static org.junit.jupiter.api.Assertions.assertNotNull;
17+
import static org.junit.jupiter.api.Assertions.assertNull;
18+
import static org.mockito.ArgumentMatchers.any;
19+
import static org.mockito.Mockito.lenient;
20+
import static org.openhab.binding.tuya.internal.TuyaBindingConstants.BINDING_ID;
21+
22+
import java.util.Map;
23+
import java.util.concurrent.ConcurrentHashMap;
24+
25+
import org.eclipse.jdt.annotation.NonNullByDefault;
26+
import org.junit.jupiter.api.AfterEach;
27+
import org.junit.jupiter.api.BeforeEach;
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.api.extension.ExtendWith;
30+
import org.mockito.Mock;
31+
import org.mockito.junit.jupiter.MockitoExtension;
32+
import org.openhab.binding.tuya.internal.util.SchemaDp;
33+
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
34+
import org.openhab.core.thing.type.ChannelType;
35+
import org.openhab.core.thing.type.ChannelTypeUID;
36+
37+
/**
38+
* The {@link TuyaChannelTypeProviderTest} verifies that the channel type provider
39+
* only sets a unit hint when the accepted item type is a {@code Number:<Dimension>}.
40+
* <p>
41+
* Previously, an unparseable or dimensionless unit on a {@code value}-type schema
42+
* produced an item type of {@code Number} while still calling
43+
* {@code withUnitHint(...)}, which made {@code ChannelType} throw
44+
* {@code IllegalArgumentException: A unit hint must not be set if the item type is
45+
* not a number with dimension!} and left the thing in
46+
* {@code HANDLER_INITIALIZING_ERROR}. See issue #20616.
47+
*
48+
* @author Caglar Eker - Initial contribution
49+
*/
50+
@ExtendWith(MockitoExtension.class)
51+
@NonNullByDefault
52+
public class TuyaChannelTypeProviderTest {
53+
54+
private @Mock @NonNullByDefault({}) ChannelTypeI18nLocalizationService localizationServiceMock;
55+
56+
private static final String PRODUCT_ID = "testprod";
57+
58+
@BeforeEach
59+
public void setUp() {
60+
// Return the input channel type unchanged so the test focuses on
61+
// TuyaChannelTypeProvider behavior, not on localization.
62+
lenient().when(localizationServiceMock.createLocalizedChannelType(any(), any(ChannelType.class), any()))
63+
.thenAnswer(inv -> inv.getArgument(1));
64+
TuyaSchemaDB.cache.put(PRODUCT_ID, new ConcurrentHashMap<>());
65+
}
66+
67+
@AfterEach
68+
public void tearDown() {
69+
TuyaSchemaDB.cache.remove(PRODUCT_ID);
70+
}
71+
72+
@Test
73+
public void parseableDimensionalUnitSetsNumberDimensionAndUnitHint() {
74+
SchemaDp dp = valueDp("°C");
75+
putDp("temp", dp);
76+
77+
ChannelType ct = getChannelType("temp");
78+
79+
assertNotNull(ct);
80+
assertEquals("Number:Temperature", ct.getItemType());
81+
assertEquals("°C", ct.getUnitHint());
82+
}
83+
84+
@Test
85+
public void unparseableUnitKeepsPlainNumberAndOmitsUnitHint() {
86+
// A unit string openHAB can't parse used to trigger
87+
// "A unit hint must not be set if the item type is not a number with dimension!"
88+
SchemaDp dp = valueDp("bogusunit");
89+
putDp("garbage", dp);
90+
91+
ChannelType ct = getChannelType("garbage");
92+
93+
assertNotNull(ct);
94+
assertEquals("Number", ct.getItemType());
95+
assertNull(ct.getUnitHint(), "unit hint must not be set on plain Number item type");
96+
}
97+
98+
@Test
99+
public void boolSchemaProducesSwitchWithoutUnitHint() {
100+
// Defensive check: even if unit is somehow populated, non-numeric item
101+
// types must never receive a unit hint.
102+
SchemaDp dp = new SchemaDp();
103+
dp.type = "bool";
104+
dp.label = "Power";
105+
dp.unit = "";
106+
putDp("power", dp);
107+
108+
ChannelType ct = getChannelType("power");
109+
110+
assertNotNull(ct);
111+
assertEquals("Switch", ct.getItemType());
112+
assertNull(ct.getUnitHint());
113+
}
114+
115+
private SchemaDp valueDp(String unit) {
116+
SchemaDp dp = new SchemaDp();
117+
dp.type = "value";
118+
dp.label = "Test";
119+
dp.unit = unit;
120+
return dp;
121+
}
122+
123+
private void putDp(String channelTypeId, SchemaDp dp) {
124+
Map<String, SchemaDp> schema = TuyaSchemaDB.cache.get(PRODUCT_ID);
125+
assertNotNull(schema);
126+
schema.put(channelTypeId, dp);
127+
}
128+
129+
private ChannelType getChannelType(String channelTypeId) {
130+
TuyaChannelTypeProvider provider = new TuyaChannelTypeProvider(localizationServiceMock);
131+
ChannelTypeUID uid = new ChannelTypeUID(BINDING_ID, PRODUCT_ID + "_" + channelTypeId);
132+
ChannelType ct = provider.getChannelType(uid, null);
133+
assertNotNull(ct);
134+
return ct;
135+
}
136+
}

0 commit comments

Comments
 (0)