Skip to content

Commit

Permalink
JLW Merge branch 'homeseer-internal/master' into 'github/master' for …
Browse files Browse the repository at this point in the history
…release v1.4.1.0
  • Loading branch information
jldubz committed Jun 6, 2022
2 parents dd7cca3 + 9404cd0 commit 3bacebb
Show file tree
Hide file tree
Showing 21 changed files with 1,591 additions and 60 deletions.
1 change: 0 additions & 1 deletion Devices/EFeatureDisplayType.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace HomeSeer.PluginSdk.Devices {

/// <summary>
/// <para> NOTE - THIS IS PREVIEW MATERIAL AND WILL NOT FUNCTION UNTIL HS v4.2.0.0 </para>
/// <para>
/// The display type for a <see cref="HsFeature"/>.
/// This controls the way a feature and its controls are displayed in the HomeSeer UI.
Expand Down
34 changes: 32 additions & 2 deletions Devices/FeatureFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,38 @@ public FeatureFactory AsType(EFeatureType featureType, int featureSubType) {

return this;
}

#region Controls

/// <summary>
/// Add a <see cref="StatusControl"/> to the <see cref="HsFeature"/> being built
/// </summary>
/// <param name="statusControl">The <see cref="StatusControl"/> to add.</param>
/// <returns>The calling <see cref="FeatureFactory"/> with an added <see cref="StatusControl"/></returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="statusControl"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when a <see cref="StatusControl"/> for the values targeted by <paramref name="statusControl"/> already exists.</exception>
public FeatureFactory AddControl(StatusControl statusControl) {

if (statusControl == null) {
throw new ArgumentNullException(nameof(statusControl));
}
if (statusControl.IsRange ) {
//If the control is a range
if (_feature.HasControlForRange(statusControl.TargetRange)) {
//If the feature already has a control for the range
throw new ArgumentException("A value targeted by the specified statusControl already has a control bound to it.", nameof(statusControl));
}
}
else if (_feature.HasControlForValue(statusControl.TargetValue)) {
//The control is not a range
//If the feature already has a control for the value
throw new ArgumentException("The value targeted by the specified statusControl already has a control bound to it.", nameof(statusControl));
}

_feature.AddStatusControl(statusControl);

return this;
}

/// <summary>
/// Add a button to the <see cref="HsFeature"/>.
Expand Down Expand Up @@ -673,7 +703,7 @@ public FeatureFactory AddGraphic(StatusGraphic statusGraphic) {
if (statusGraphic.IsRange && _feature.HasGraphicForRange(statusGraphic.TargetRange)) {
throw new ArgumentException("A value targeted by the specified statusGraphic already has a graphic bound to it.", nameof(statusGraphic));
}
if (_feature.HasGraphicForValue(statusGraphic.Value)) {
if (!statusGraphic.IsRange && _feature.HasGraphicForValue(statusGraphic.Value)) {
throw new ArgumentException("The value targeted by the specified statusGraphic already has a graphic bound to it.", nameof(statusGraphic));
}

Expand Down
1 change: 0 additions & 1 deletion Devices/HsDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public class HsDevice : AbstractHsDevice {
public List<HsFeature> Features { get; } = new List<HsFeature>();

/// <summary>
/// <para> NOTE - THIS IS PREVIEW MATERIAL AND WILL NOT FUNCTION UNTIL HS v4.2.0.0 </para>
/// <para>
/// A list of <see cref="AbstractHsDevice.Ref"/>s indicating the order of importance for the features of a
/// device where 1 is the most important. This helps HomeSeer determine how to display features in the UI.
Expand Down
2 changes: 0 additions & 2 deletions Devices/HsFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public List<string> AdditionalStatusData {
}

/// <summary>
/// <para> NOTE - THIS IS PREVIEW MATERIAL AND WILL NOT FUNCTION UNTIL HS v4.2.0.0 </para>
/// <para>
/// The priority of the feature when being considered for display where 1 is the most important.
/// </para>
Expand All @@ -74,7 +73,6 @@ public List<string> AdditionalStatusData {
public int DisplayPriority => _displayPriority;

/// <summary>
/// <para> NOTE - THIS IS PREVIEW MATERIAL AND WILL NOT FUNCTION UNTIL HS v4.2.0.0 </para>
/// The <see cref="EFeatureDisplayType"/> for a feature.
/// </summary>
/// <remarks>This is used to help HS determine how it should be displayed to the user</remarks>
Expand Down
310 changes: 310 additions & 0 deletions PluginSdkTests/Devices/FeatureFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework.Internal;
using HomeSeer.PluginSdk.Devices.Controls;

namespace HomeSeer.PluginSdk.Devices {

[TestFixture(Description = "Tests of the FeatureFactory class to ensure it behaves as expected under normal conditions.",
TestOf = typeof(FeatureFactory))]
public class FeatureFactoryTests {

private const string IMAGES_STATUS_DIR = "images/HomeSeer/status/";
private const double EPSILON = 0.0001;
private FeatureFactory _ff;

[SetUp]
public void InitFeatureFactory() {
_ff = FeatureFactory.CreateFeature("TestPluginId");
}

private static IEnumerable<StatusGraphic> StatusGraphicTestCaseSource() {
var sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-00.png", -100, -18);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-10.png", -18 + EPSILON, -12);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-20.png", -12 + EPSILON, -7);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-30.png", -7 + EPSILON, -1);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-40.png", -1 + EPSILON, 4);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-50.png", 4 + EPSILON, 10);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-60.png", 10 + EPSILON, 16);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-70.png", 16 + EPSILON, 21);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-80.png", 21 + EPSILON, 27);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-90.png", 27 + EPSILON, 32);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-100.png", 32 + EPSILON, 38);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-110.png", 38 + EPSILON, 100);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "heating.png", 0, "Heat");
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "cooling.png", 1, "Cool");
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "auto-mode.png", 2, "Auto");
yield return sg;

sg = new StatusGraphic(IMAGES_STATUS_DIR + "off.gif", 3, "Off");
yield return sg;
}

[TestCaseSource(nameof(StatusGraphicTestCaseSource))]
[Description("Add a Single Valid StatusGraphic")]
public void AddGraphic_Single_Valid(StatusGraphic sg) {
Assert.DoesNotThrow(() => {
_ff.AddGraphic(sg);
});
}

[Test]
[Description("Add Multiple Valid StatusGraphics that target range of values")]
public void AddGraphic_Multiple_Valid_Ranges() {

var sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-00.png", -100, -18);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-10.png", -18 + EPSILON, -12);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-20.png", -12 + EPSILON, -7);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-30.png", -7 + EPSILON, -1);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-40.png", -1 + EPSILON, 4);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-50.png", 4 + EPSILON, 10);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-60.png", 10 + EPSILON, 16);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-70.png", 16 + EPSILON, 21);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-80.png", 21 + EPSILON, 27);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-90.png", 27 + EPSILON, 32);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-100.png", 32 + EPSILON, 38 );
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-110.png", 38 + EPSILON, 100);
sg.TargetRange.DecimalPlaces = 1;
sg.TargetRange.Suffix = " °C";
_ff.AddGraphic(sg);
}

[Test]
[Description("Add Multiple Valid StatusGraphics that target single values")]
public void AddGraphic_Multiple_Valid_SingleValues() {

var sg = new StatusGraphic(IMAGES_STATUS_DIR + "heating.png", 0, "Heat");
_ff.AddGraphic(sg);
sg = new StatusGraphic(IMAGES_STATUS_DIR + "cooling.png", 1, "Cool");
_ff.AddGraphic(sg);
sg = new StatusGraphic(IMAGES_STATUS_DIR + "auto-mode.png", 2, "Auto");
_ff.AddGraphic(sg);
sg = new StatusGraphic(IMAGES_STATUS_DIR + "off.gif", 3, "Off");
_ff.AddGraphic(sg);
}

[Test]
[Description("Add overlapping StatusGraphics that target range of values and expect an exception to be thrown.")]
public void AddGraphic_Multiple_Invalid_Ranges_Throws() {

var sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-00.png", -100, 50);
_ff.AddGraphic(sg);

sg = new StatusGraphic(IMAGES_STATUS_DIR + "Thermometer-10.png", 0, 100);
Assert.Throws<ArgumentException>(() => {
_ff.AddGraphic(sg);
});
}

[Test]
[Description("Add multiple StatusGraphics that target the same single value and expect an exception to be thrown.")]
public void AddGraphic_Multiple_Invalid_SingleValues_Throws() {

var sg = new StatusGraphic(IMAGES_STATUS_DIR + "heating.png", 0, "Heat");
_ff.AddGraphic(sg);
sg = new StatusGraphic(IMAGES_STATUS_DIR + "cooling.png", 1, "Cool");
_ff.AddGraphic(sg);
sg = new StatusGraphic(IMAGES_STATUS_DIR + "auto-mode.png", 2, "Auto");
_ff.AddGraphic(sg);
sg = new StatusGraphic(IMAGES_STATUS_DIR + "off.gif", 2, "Off");
Assert.Throws<ArgumentException>(() => {
_ff.AddGraphic(sg);
});
}

[Test]
[Description("Add null StatusGraphic and expect an exception to be thrown.")]
public void AddGraphic_Null_Throws() {

Assert.Throws<ArgumentNullException>(() => {
_ff.AddGraphic(null);
});
}

[Test]
[Description("Add Valid StatusControls that target range of values")]
public void AddControl_Valid_Ranges() {

var sc = new StatusControl(EControlType.ValueRangeSlider);
sc.TargetRange = new ValueRange(-100, 0);
_ff.AddControl(sc);

sc = new StatusControl(EControlType.ValueRangeSlider);
sc.TargetRange = new ValueRange(EPSILON, 100);
_ff.AddControl(sc);

sc = new StatusControl(EControlType.ValueRangeSlider);
sc.TargetRange = new ValueRange(100+EPSILON, 200);
_ff.AddControl(sc);
}

[Test]
[Description("Add Valid StatusControls that target single values")]
public void AddControl_Valid_SingleValues() {

var sc = new StatusControl(EControlType.Button);
sc.TargetValue = 0;
sc.Label = "Heat";
_ff.AddControl(sc);
sc = new StatusControl(EControlType.Button);
sc.TargetValue = 1;
sc.Label = "Cool";
_ff.AddControl(sc);
sc = new StatusControl(EControlType.Button);
sc.TargetValue = 2;
sc.Label = "Auto";
_ff.AddControl(sc);
sc = new StatusControl(EControlType.Button);
sc.TargetValue = 3;
sc.Label = "Off";
_ff.AddControl(sc);
}

[Test]
[Description("Add overlapping StatusControls that target range of values and expect an exception to be thrown.")]
public void AddControl_Invalid_Ranges_Throws() {

var sc = new StatusControl(EControlType.ValueRangeSlider);
sc.TargetRange = new ValueRange(-100, 0);
_ff.AddControl(sc);

sc = new StatusControl(EControlType.ValueRangeSlider);
sc.TargetRange = new ValueRange(0, 100);

Assert.Throws<ArgumentException>(() => {
_ff.AddControl(sc);
});
}

[Test]
[Description("Add multiple StatusControls that target the same single value and expect an exception to be thrown.")]
public void AddControl_Invalid_SingleValues_Throws() {

var sc = new StatusControl(EControlType.Button);
sc.TargetValue = 0;
sc.Label = "Heat";
_ff.AddControl(sc);
sc = new StatusControl(EControlType.Button);
sc.TargetValue = 1;
sc.Label = "Cool";
_ff.AddControl(sc);
sc = new StatusControl(EControlType.Button);
sc.TargetValue = 2;
sc.Label = "Auto";
_ff.AddControl(sc);
sc = new StatusControl(EControlType.Button);
sc.TargetValue = 2;
sc.Label = "Off";
Assert.Throws<ArgumentException>(() => {
_ff.AddControl(sc);
});
}

[Test]
[Description("Add null StatusControl and expect an exception to be thrown.")]
public void AddControl_Null_Throws() {

Assert.Throws<ArgumentNullException>(() => {
_ff.AddControl(null);
});
}
}
}
Loading

0 comments on commit 3bacebb

Please sign in to comment.