Skip to content

Commit 7d0b317

Browse files
Merge branch 'main' into minimal_damon
2 parents 9a7487f + 074af55 commit 7d0b317

File tree

4 files changed

+136
-5
lines changed

4 files changed

+136
-5
lines changed

.github/copilot-instructions.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copilot Instructions for NetDaemon
2+
3+
This document provides guidance for AI assistants working on the NetDaemon project.
4+
5+
## .NET Version
6+
7+
- **Always use the .NET version specified in `global.json`**
8+
9+
## Development Workflow
10+
11+
### Building the Project
12+
```bash
13+
dotnet restore
14+
dotnet build --configuration Release
15+
```
16+
17+
### Running Tests
18+
**Always verify code changes by running tests with `dotnet test`**
19+
20+
The project has comprehensive test coverage including:
21+
- Unit tests for individual components
22+
- Integration tests with Home Assistant (stable and beta versions)
23+
24+
Run specific test projects:
25+
```bash
26+
# Unit tests
27+
dotnet test src/HassModel/NetDaemon.HassModel.Tests
28+
dotnet test src/Extensions/NetDaemon.Extensions.Scheduling.Tests
29+
dotnet test src/Client/NetDaemon.HassClient.Tests
30+
dotnet test src/AppModel/NetDaemon.AppModel.Tests
31+
dotnet test src/Runtime/NetDaemon.Runtime.Tests
32+
33+
# Integration tests
34+
dotnet test tests/Integration/NetDaemon.Tests.Integration
35+
```
36+
37+
### Code Quality
38+
- Follow the existing `.editorconfig` settings
39+
- The project uses Roslynator for additional code analysis
40+
- Build warnings are treated as errors in CI
41+
- Maintain test coverage for new features
42+
43+
### Making Changes
44+
- Focus on minimal, surgical changes
45+
- Maintain backward compatibility where possible
46+
- Update tests for any functional changes
47+
- Consider integration test coverage for Home Assistant interactions
48+
49+
## Key Documentation Links
50+
51+
### NetDaemon Documentation
52+
- **NetDaemon User Docs**: https://netdaemon.xyz/docs/user/
53+
- **NetDaemon Developer Site**: https://netdaemon.xyz/docs/developer
54+
55+
### Home Assistant Documentation
56+
- **Home Assistant Docs**: https://www.home-assistant.io/docs/
57+
- **Home Assistant Developer Docs**: https://developers.home-assistant.io/

global.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"sdk": {
3-
"version": "8.0.0",
4-
"rollForward": "latestMajor",
5-
"allowPrerelease": true
3+
"version": "9.0.0",
4+
"rollForward": "latestMinor"
65
}
76
}

src/HassModel/NetDaemon.HassModel.CodeGenerator/CodeGeneration/HelpersGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ private static MethodDeclarationSyntax BuildAddHomeAssistantGenerated(IEnumerabl
6565
private static IEnumerable<string> GetInjectableTypes(IEnumerable<EntityDomainMetadata> domains, IEnumerable<HassServiceDomain> orderedServiceDomains) =>
6666
[
6767
EntitiesClassName,
68-
.. domains.Select(d => d.EntitiesForDomainClassName),
68+
.. domains.Select(d => d.EntitiesForDomainClassName).Distinct(),
6969
ServicesClassName,
70-
..orderedServiceDomains.Select(d => GetServicesTypeName(d.Domain))
70+
..orderedServiceDomains.Select(d => GetServicesTypeName(d.Domain)).Distinct()
7171
];
7272
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.Text.RegularExpressions;
2+
using NetDaemon.Client.HomeAssistant.Model;
3+
using NetDaemon.HassModel.CodeGenerator;
4+
using NetDaemon.HassModel.CodeGenerator.CodeGeneration;
5+
6+
namespace NetDaemon.HassModel.Tests.CodeGenerator;
7+
8+
public class HelpersGeneratorTest
9+
{
10+
[Fact]
11+
public void AddHomeAssistantGenerated_ShouldNotRegisterDuplicateEntityClasses()
12+
{
13+
// Arrange: Create test data that would lead to duplicate SensorEntities registrations
14+
var states = new HassState[]
15+
{
16+
// Non-numeric sensor (no unit_of_measurement)
17+
new() { EntityId = "sensor.simple_text", Attributes = new Dictionary<string, object>() },
18+
19+
// Numeric sensor (has unit_of_measurement)
20+
new() { EntityId = "sensor.temperature", Attributes = new Dictionary<string, object> { ["unit_of_measurement"] = "°C" } }
21+
};
22+
23+
// Act: Generate metadata which creates both numeric and non-numeric sensor domains
24+
var metaData = EntityMetaDataGenerator.GetEntityDomainMetaData(states);
25+
26+
// Both should have the same EntitiesForDomainClassName
27+
var sensorDomains = metaData.Domains.Where(d => d.Domain == "sensor").ToList();
28+
sensorDomains.Should().HaveCount(2, "there should be both numeric and non-numeric sensor domains");
29+
sensorDomains.Should().AllSatisfy(d => d.EntitiesForDomainClassName.Should().Be("SensorEntities"));
30+
31+
// Generate the extension method code
32+
var generatedMembers = HelpersGenerator.Generate(metaData.Domains, []).ToList();
33+
var generatedCode = generatedMembers.First().ToString();
34+
35+
// Assert: The generated code should not contain duplicate SensorEntities registrations
36+
var sensorEntitiesMatches = Regex.Matches(
37+
generatedCode,
38+
@"serviceCollection\.AddTransient<SensorEntities>\(\);");
39+
40+
sensorEntitiesMatches.Should().HaveCount(1, "SensorEntities should only be registered once, not duplicated");
41+
}
42+
43+
[Fact]
44+
public void AddHomeAssistantGenerated_ShouldRegisterAllUniqueEntityClasses()
45+
{
46+
// Arrange: Create test data with different domains
47+
var states = new HassState[]
48+
{
49+
new() { EntityId = "sensor.temperature", Attributes = new Dictionary<string, object> { ["unit_of_measurement"] = "°C" } },
50+
new() { EntityId = "light.living_room", Attributes = new Dictionary<string, object>() },
51+
new() { EntityId = "switch.kitchen", Attributes = new Dictionary<string, object>() }
52+
};
53+
54+
// Act: Generate metadata
55+
var metaData = EntityMetaDataGenerator.GetEntityDomainMetaData(states);
56+
57+
// Generate the extension method code
58+
var generatedMembers = HelpersGenerator.Generate(metaData.Domains, []).ToList();
59+
var generatedCode = generatedMembers.First().ToString();
60+
61+
// Assert: Each domain should be registered exactly once
62+
generatedCode.Should().Contain("serviceCollection.AddTransient<SensorEntities>();");
63+
generatedCode.Should().Contain("serviceCollection.AddTransient<LightEntities>();");
64+
generatedCode.Should().Contain("serviceCollection.AddTransient<SwitchEntities>();");
65+
66+
// Verify no duplicates
67+
var sensorMatches = Regex.Matches(generatedCode, @"serviceCollection\.AddTransient<SensorEntities>\(\);");
68+
var lightMatches = Regex.Matches(generatedCode, @"serviceCollection\.AddTransient<LightEntities>\(\);");
69+
var switchMatches = Regex.Matches(generatedCode, @"serviceCollection\.AddTransient<SwitchEntities>\(\);");
70+
71+
sensorMatches.Should().HaveCount(1);
72+
lightMatches.Should().HaveCount(1);
73+
switchMatches.Should().HaveCount(1);
74+
}
75+
}

0 commit comments

Comments
 (0)