Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CodeCasa.sln
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutomationPipeline
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Lights.NetDaemon.Scenes", "src\CodeCasa.Lights.NetDaemon.Scenes\CodeCasa.Lights.NetDaemon.Scenes.csproj", "{AA5FEE82-9A79-4AA7-BC3E-4769638FF8A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutomationPipelines.Lights.Tests", "tests\CodeCasa.AutomationPipelines.Lights.Tests\CodeCasa.AutomationPipelines.Lights.Tests.csproj", "{96DB93C1-036A-436A-AF7A-AEC07243A929}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -107,6 +109,10 @@ Global
{AA5FEE82-9A79-4AA7-BC3E-4769638FF8A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA5FEE82-9A79-4AA7-BC3E-4769638FF8A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA5FEE82-9A79-4AA7-BC3E-4769638FF8A6}.Release|Any CPU.Build.0 = Release|Any CPU
{96DB93C1-036A-436A-AF7A-AEC07243A929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{96DB93C1-036A-436A-AF7A-AEC07243A929}.Debug|Any CPU.Build.0 = Debug|Any CPU
{96DB93C1-036A-436A-AF7A-AEC07243A929}.Release|Any CPU.ActiveCfg = Release|Any CPU
{96DB93C1-036A-436A-AF7A-AEC07243A929}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -116,6 +122,7 @@ Global
{E2FA49AB-BFC2-4DDD-B743-F5AAB96F3FEC} = {5BCD08C3-3034-4D08-AC01-2AB6DFD67C33}
{FA26C18B-24D0-4F3D-958C-A9BA61861C65} = {5BCD08C3-3034-4D08-AC01-2AB6DFD67C33}
{5C7FB111-095B-F881-7268-4284370C8AAA} = {5BCD08C3-3034-4D08-AC01-2AB6DFD67C33}
{96DB93C1-036A-436A-AF7A-AEC07243A929} = {5BCD08C3-3034-4D08-AC01-2AB6DFD67C33}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5AAE8D3A-9457-4676-8F57-B2D78594CCC7}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<PackageIcon>cc_icon.png</PackageIcon>
<PackageReleaseNotes>https://github.com/DevJasperNL/CodeCasa/releases</PackageReleaseNotes>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="CodeCasa.AutomationPipelines.Lights.Tests" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\LICENSE">
Expand Down
4 changes: 2 additions & 2 deletions src/CodeCasa.AutomationPipelines.Lights/Nodes/FactoryNode.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace CodeCasa.AutomationPipelines.Lights.Nodes;

internal class FactoryNode<TState>(Func<TState?, TState?> lightTransitionFactory)
internal class FactoryNode<TState>(Func<TState?, TState?> stateFactory)
: PipelineNode<TState>
{
/// <inheritdoc />
protected override void InputReceived(TState? input)
{
Output = lightTransitionFactory(input);
Output = stateFactory(input);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ namespace CodeCasa.AutomationPipelines.Lights.Nodes
public abstract class LightTransitionNode(IScheduler scheduler) : IPipelineNode<LightTransition>
{
private readonly Subject<LightTransition?> _newOutputSubject = new();
private LightTransition? _input;
private LightParameters? _inputLightDestinationParameters;
private DateTime? _inputStartOfTransition;
private DateTime? _inputEndOfTransition;
private LightTransition? _output;
private bool _passThroughNextInput;
private bool _passThrough;
private IDisposable? _scheduledAction;

/// <summary>
Expand All @@ -33,29 +31,31 @@ public abstract class LightTransitionNode(IScheduler scheduler) : IPipelineNode<
/// <inheritdoc />
public LightTransition? Input
{
get => _input;
get;
set
{
_scheduledAction?.Dispose(); // Always cancel scheduled actions when the input changes.
// We save additional information on the light transition that we can later use to continue the transition if it would be interrupted.
InputLightSourceParameters = _inputLightDestinationParameters;
_input = value;
field = value;
_inputLightDestinationParameters = value?.LightParameters;
var transitionTime = value?.TransitionTime;
_inputStartOfTransition = DateTime.UtcNow;
_inputEndOfTransition = transitionTime == null ? null : _inputStartOfTransition + transitionTime;
_inputEndOfTransition = _inputStartOfTransition + transitionTime;

if (_passThroughNextInput)
{
PassThrough = true;
return;
}

if (PassThrough)
{
SetOutputInternal(_input);
SetOutputInternal(field);
return;
}
InputReceived(_input);

InputReceived(field);
}
}

Expand Down Expand Up @@ -110,24 +110,25 @@ protected void ScheduleInterpolatedLightTransitionUsingInputTransitionTime(Light
/// </summary>
public bool PassThrough
{
get => _passThrough;
get;
set
{
// Always reset _passThroughNextInput when PassThrough is explicitly called.
_passThroughNextInput = false;

if (_passThrough == value)
if (field == value)
{
return;
}

_scheduledAction?.Dispose(); // Always cancel scheduled actions when the pass through value changes.

_passThrough = value;
if (_passThrough)
field = value;
if (field)
{
_scheduledAction = scheduler.ScheduleInterpolatedLightTransition(InputLightSourceParameters,
_inputLightDestinationParameters, _inputStartOfTransition, _inputEndOfTransition, SetOutputInternal);
_inputLightDestinationParameters, _inputStartOfTransition, _inputEndOfTransition,
SetOutputInternal);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>CodeCasa.AutomationPipelines.Tests</_Parameter1>
</AssemblyAttribute>
<InternalsVisibleTo Include="CodeCasa.AutomationPipelines.Tests" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
<ItemGroup>
<PackageReference Include="NetDaemon.HassModel" Version="25.48.0" />
<PackageReference Include="Reactive.Boolean" Version="1.1.0" />
<PackageReference Include="System.Reactive" Version="6.1.0" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Reactive" Version="6.1.0" />
<InternalsVisibleTo Include="CodeCasa.NetDaemon.Extensions.Observables.Tests" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>NetDaemon.Notifications.InputSelect.Tests</_Parameter1>
</AssemblyAttribute>
<InternalsVisibleTo Include="NetDaemon.Notifications.InputSelect.Tests" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MSTest" Version="4.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\CodeCasa.AutomationPipelines.Lights\CodeCasa.AutomationPipelines.Lights.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

</Project>
135 changes: 135 additions & 0 deletions tests/CodeCasa.AutomationPipelines.Lights.Tests/FactoryNodeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
namespace CodeCasa.AutomationPipelines.Lights.Tests;

using AutomationPipelines;
using Nodes;

[TestClass]
public sealed class FactoryNodeTests
{
[TestMethod]
public void FactoryNode_TransformsInput()
{
// Arrange
const string inputValue = "Test";
const string expectedOutput = "Test_Transformed";
string? emittedOutput = null;

var factoryNode = new FactoryNode<string>(input => $"{input}_Transformed");
factoryNode.OnNewOutput.Subscribe(o => emittedOutput = o);

// Act
factoryNode.Input = inputValue;

// Assert
Assert.AreEqual(expectedOutput, emittedOutput);
Assert.AreEqual(expectedOutput, factoryNode.Output);
}

[TestMethod]
public void FactoryNode_HandlesNullInput()
{
// Arrange
string? emittedOutput = null;
var factoryNode = new FactoryNode<string?>(input => input ?? "NULL");
factoryNode.OnNewOutput.Subscribe(o => emittedOutput = o);

// Act
factoryNode.Input = null;

// Assert
Assert.AreEqual("NULL", emittedOutput);
Assert.AreEqual("NULL", factoryNode.Output);
}

[TestMethod]
public void FactoryNode_ProducesNullOutput()
{
// Arrange
var emittedOutput = "NotNull";
var factoryNode = new FactoryNode<string?>(_ => null);
factoryNode.OnNewOutput.Subscribe(o => emittedOutput = o);

// Act
factoryNode.Input = "Test";

// Assert
Assert.IsNull(emittedOutput);
Assert.IsNull(factoryNode.Output);
}

[TestMethod]
public void FactoryNode_MultipleInputs()
{
// Arrange
var outputs = new List<string?>();
var factoryNode = new FactoryNode<string>(input => $"{input}_transformed");
factoryNode.OnNewOutput.Subscribe(o => outputs.Add(o));

// Act
factoryNode.Input = "First";
factoryNode.Input = "Second";
factoryNode.Input = "Third";

// Assert
CollectionAssert.AreEqual(
new[] { "First_transformed", "Second_transformed", "Third_transformed" },
outputs);
Assert.AreEqual("Third_transformed", factoryNode.Output);
}

[TestMethod]
public void FactoryNode_WithIntegerTransformation()
{
// Arrange
var emittedOutputs = new List<int?>();
var factoryNode = new FactoryNode<int>(input => input * 2);
factoryNode.OnNewOutput.Subscribe(o => emittedOutputs.Add(o));

// Act
factoryNode.Input = 5;
factoryNode.Input = 10;
factoryNode.Input = 0;

// Assert
CollectionAssert.AreEqual(new[] { 10, 20, 0 }, emittedOutputs);
Assert.AreEqual(0, factoryNode.Output);
}

[TestMethod]
public void FactoryNode_OutputNotificationCalledForEachInput()
{
// Arrange
var outputNotificationCount = 0;
var factoryNode = new FactoryNode<string>(input => input);
factoryNode.OnNewOutput.Subscribe(_ => outputNotificationCount++);

// Act
factoryNode.Input = "Test1";
factoryNode.Input = "Test2";
factoryNode.Input = "Test3";

// Assert
Assert.AreEqual(3, outputNotificationCount);
}

[TestMethod]
public void FactoryNode_ChainedWithOtherNodes()
{
// Arrange
string? finalOutput = null;
var pipeline = new Pipeline<string>();
pipeline.OnNewOutput.Subscribe(o => finalOutput = o);

var factoryNode1 = new FactoryNode<string>(input => $"{input}_1");
var factoryNode2 = new FactoryNode<string>(input => $"{input}_2");

// Act
pipeline.SetDefault("Test");
pipeline.RegisterNode(factoryNode1);
pipeline.RegisterNode(factoryNode2);

// Assert
Assert.AreEqual("Test_1_2", finalOutput);
Assert.AreEqual("Test_1_2", pipeline.Output);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]