Fix calculate readOnly field in stateDescription for items with more than one linked channel (#4838) (#4845)
Signed-off-by: Maksym Krasovskyi <maxx@maxx.net.ua>pull/4846/head
parent
95cb442524
commit
48e20d660a
|
@ -91,24 +91,33 @@ public class ChannelStateDescriptionProvider implements StateDescriptionFragment
|
||||||
|
|
||||||
private @Nullable StateDescription getStateDescription(String itemName, @Nullable Locale locale) {
|
private @Nullable StateDescription getStateDescription(String itemName, @Nullable Locale locale) {
|
||||||
Set<ChannelUID> boundChannels = itemChannelLinkRegistry.getBoundChannels(itemName);
|
Set<ChannelUID> boundChannels = itemChannelLinkRegistry.getBoundChannels(itemName);
|
||||||
if (!boundChannels.isEmpty()) {
|
StateDescription stateDescription = null;
|
||||||
ChannelUID channelUID = boundChannels.iterator().next();
|
for (ChannelUID channelUID : boundChannels) {
|
||||||
Channel channel = thingRegistry.getChannel(channelUID);
|
Channel channel = thingRegistry.getChannel(channelUID);
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
StateDescription stateDescription = null;
|
|
||||||
ChannelType channelType = thingTypeRegistry.getChannelType(channel, locale);
|
ChannelType channelType = thingTypeRegistry.getChannelType(channel, locale);
|
||||||
|
StateDescription nextStateDescription = null;
|
||||||
if (channelType != null) {
|
if (channelType != null) {
|
||||||
stateDescription = channelType.getState();
|
nextStateDescription = channelType.getState();
|
||||||
}
|
}
|
||||||
StateDescription dynamicStateDescription = getDynamicStateDescription(channel, stateDescription,
|
StateDescription dynamicStateDescription = getDynamicStateDescription(channel, nextStateDescription,
|
||||||
locale);
|
locale);
|
||||||
if (dynamicStateDescription != null) {
|
if (dynamicStateDescription != null) {
|
||||||
return dynamicStateDescription;
|
nextStateDescription = dynamicStateDescription;
|
||||||
|
}
|
||||||
|
if (nextStateDescription != null) {
|
||||||
|
if (stateDescription == null) {
|
||||||
|
stateDescription = nextStateDescription;
|
||||||
|
} else {
|
||||||
|
if (stateDescription.isReadOnly() && !nextStateDescription.isReadOnly()) {
|
||||||
|
stateDescription = nextStateDescription;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return stateDescription;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return stateDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.thing.internal;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.openhab.core.thing.Channel;
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.ThingRegistry;
|
||||||
|
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||||
|
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||||
|
import org.openhab.core.thing.type.ChannelType;
|
||||||
|
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||||
|
import org.openhab.core.thing.type.ThingTypeRegistry;
|
||||||
|
import org.openhab.core.types.StateDescription;
|
||||||
|
import org.openhab.core.types.StateDescriptionFragment;
|
||||||
|
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Maksym Krasovskyi - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public class ChannelStateDescriptionProviderTest {
|
||||||
|
|
||||||
|
private @NonNullByDefault({}) ChannelStateDescriptionProvider channelStateDescriptionProvider;
|
||||||
|
private @Mock @NonNullByDefault({}) DynamicStateDescriptionProvider dynamicStateDescriptionProvider;
|
||||||
|
private @Mock @NonNullByDefault({}) ItemChannelLinkRegistry itemChannelLinkRegistry;
|
||||||
|
private @Mock @NonNullByDefault({}) ThingTypeRegistry thingTypeRegistry;
|
||||||
|
private @Mock @NonNullByDefault({}) ThingRegistry thingRegistry;
|
||||||
|
|
||||||
|
private static final ChannelUID CHANNEL_UID_1 = new ChannelUID("channel:f:g:1");
|
||||||
|
private static final ChannelUID CHANNEL_UID_2 = new ChannelUID("channel:f:g:2");
|
||||||
|
|
||||||
|
private static final String ITEM_1 = "item1";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
channelStateDescriptionProvider = new ChannelStateDescriptionProvider(itemChannelLinkRegistry,
|
||||||
|
thingTypeRegistry, thingRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({ "true, true, true", "true, false, false", "false, true, false", "false, false, false" })
|
||||||
|
public void testStateDescriptionFromMultipleChannels(Boolean channel1State, Boolean channel2State,
|
||||||
|
Boolean expectedItemState) {
|
||||||
|
when(itemChannelLinkRegistry.getBoundChannels(ITEM_1))
|
||||||
|
.thenReturn(new HashSet<>(Arrays.asList(CHANNEL_UID_1, CHANNEL_UID_2)));
|
||||||
|
// Setup channel 1
|
||||||
|
Channel channel1 = ChannelBuilder.create(CHANNEL_UID_1).build();
|
||||||
|
when(thingRegistry.getChannel(CHANNEL_UID_1)).thenReturn(channel1);
|
||||||
|
StateDescription stateDescription1 = StateDescriptionFragmentBuilder.create().withMinimum(BigDecimal.ZERO)
|
||||||
|
.withMaximum(new BigDecimal(100)).withStep(BigDecimal.ONE).withReadOnly(channel1State).withPattern("%s")
|
||||||
|
.build().toStateDescription();
|
||||||
|
|
||||||
|
ChannelType channelType1 = Mockito.mock(ChannelType.class);
|
||||||
|
|
||||||
|
when(channelType1.getState()).thenReturn(stateDescription1);
|
||||||
|
when(thingTypeRegistry.getChannelType(channel1, Locale.ENGLISH)).thenReturn(channelType1);
|
||||||
|
when(dynamicStateDescriptionProvider.getStateDescription(channel1, stateDescription1, Locale.ENGLISH))
|
||||||
|
.thenReturn(StateDescriptionFragmentBuilder.create(stateDescription1).build().toStateDescription());
|
||||||
|
|
||||||
|
// Setup channel 2
|
||||||
|
Channel channel2 = ChannelBuilder.create(CHANNEL_UID_2).build();
|
||||||
|
when(thingRegistry.getChannel(CHANNEL_UID_2)).thenReturn(channel2);
|
||||||
|
StateDescription stateDescription2 = StateDescriptionFragmentBuilder.create().withMinimum(BigDecimal.ZERO)
|
||||||
|
.withMaximum(new BigDecimal(100)).withStep(BigDecimal.ONE).withReadOnly(channel2State).withPattern("%s")
|
||||||
|
.build().toStateDescription();
|
||||||
|
ChannelType channelType2 = Mockito.mock(ChannelType.class);
|
||||||
|
when(channelType2.getState()).thenReturn(stateDescription2);
|
||||||
|
when(thingTypeRegistry.getChannelType(channel2, Locale.ENGLISH)).thenReturn(channelType2);
|
||||||
|
when(dynamicStateDescriptionProvider.getStateDescription(channel2, stateDescription2, Locale.ENGLISH))
|
||||||
|
.thenReturn(StateDescriptionFragmentBuilder.create(stateDescription2).build().toStateDescription());
|
||||||
|
|
||||||
|
channelStateDescriptionProvider.addDynamicStateDescriptionProvider(dynamicStateDescriptionProvider);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
StateDescriptionFragment stateDescriptionResult = channelStateDescriptionProvider
|
||||||
|
.getStateDescriptionFragment(ITEM_1, Locale.ENGLISH);
|
||||||
|
assertNotNull(stateDescriptionResult);
|
||||||
|
assertEquals(expectedItemState, stateDescriptionResult.isReadOnly());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStateDescriptionWithSingleReadOnlyChannel() {
|
||||||
|
when(itemChannelLinkRegistry.getBoundChannels(ITEM_1)).thenReturn(new HashSet<>(Arrays.asList(CHANNEL_UID_1)));
|
||||||
|
// Setup channel 1
|
||||||
|
Channel channel1 = ChannelBuilder.create(CHANNEL_UID_1).build();
|
||||||
|
when(thingRegistry.getChannel(CHANNEL_UID_1)).thenReturn(channel1);
|
||||||
|
StateDescription stateDescription1 = StateDescriptionFragmentBuilder.create().withMinimum(BigDecimal.ZERO)
|
||||||
|
.withMaximum(new BigDecimal(100)).withStep(BigDecimal.ONE).withReadOnly(Boolean.TRUE).withPattern("%s")
|
||||||
|
.build().toStateDescription();
|
||||||
|
ChannelType channelType = Mockito.mock(ChannelType.class);
|
||||||
|
when(channelType.getState()).thenReturn(stateDescription1);
|
||||||
|
when(thingTypeRegistry.getChannelType(channel1, Locale.ENGLISH)).thenReturn(channelType);
|
||||||
|
|
||||||
|
when(dynamicStateDescriptionProvider.getStateDescription(channel1, stateDescription1, Locale.ENGLISH))
|
||||||
|
.thenReturn(StateDescriptionFragmentBuilder.create(stateDescription1).build().toStateDescription());
|
||||||
|
|
||||||
|
channelStateDescriptionProvider.addDynamicStateDescriptionProvider(dynamicStateDescriptionProvider);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
StateDescriptionFragment stateDescriptionResult = channelStateDescriptionProvider
|
||||||
|
.getStateDescriptionFragment(ITEM_1, Locale.ENGLISH);
|
||||||
|
assertNotNull(stateDescriptionResult);
|
||||||
|
assertTrue(stateDescriptionResult.isReadOnly());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStateDescriptionWithSingleWriteOnlyChannel() {
|
||||||
|
when(itemChannelLinkRegistry.getBoundChannels(ITEM_1)).thenReturn(new HashSet<>(Arrays.asList(CHANNEL_UID_1)));
|
||||||
|
// Setup channel 1
|
||||||
|
Channel channel1 = ChannelBuilder.create(CHANNEL_UID_1).build();
|
||||||
|
when(thingRegistry.getChannel(CHANNEL_UID_1)).thenReturn(channel1);
|
||||||
|
StateDescription stateDescription1 = StateDescriptionFragmentBuilder.create().withMinimum(BigDecimal.ZERO)
|
||||||
|
.withMaximum(new BigDecimal(100)).withStep(BigDecimal.ONE).withReadOnly(Boolean.FALSE).withPattern("%s")
|
||||||
|
.build().toStateDescription();
|
||||||
|
ChannelType channelType = Mockito.mock(ChannelType.class);
|
||||||
|
when(channelType.getState()).thenReturn(stateDescription1);
|
||||||
|
when(thingTypeRegistry.getChannelType(channel1, Locale.ENGLISH)).thenReturn(channelType);
|
||||||
|
|
||||||
|
when(dynamicStateDescriptionProvider.getStateDescription(channel1, stateDescription1, Locale.ENGLISH))
|
||||||
|
.thenReturn(StateDescriptionFragmentBuilder.create(stateDescription1).build().toStateDescription());
|
||||||
|
|
||||||
|
channelStateDescriptionProvider.addDynamicStateDescriptionProvider(dynamicStateDescriptionProvider);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
StateDescriptionFragment stateDescriptionResult = channelStateDescriptionProvider
|
||||||
|
.getStateDescriptionFragment(ITEM_1, Locale.ENGLISH);
|
||||||
|
assertNotNull(stateDescriptionResult);
|
||||||
|
assertFalse(stateDescriptionResult.isReadOnly());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue