Skip to content

Commit f92712d

Browse files
authored
Nest snapshots by updating the SDK's None items (#1911)
Verify.AfterMicrosoftNetSdk.props added a None item per snapshot to carry DependentUpon, on top of the item the SDK's **/* glob had already produced. So every snapshot sat in None twice. DotNetProjectFile.Analyzers copies @(None) into Content for its SonarQube integration in non-IDE builds, which turned those into duplicate Content items and failed the build with NETSDK1022. The metadata is now applied with None Update to the SDK's item. The Include remains only for snapshots the SDK did not glob, such as when default items are disabled. FileNestingTests run the SDK's CheckForDuplicateItems target alongside the item evaluation, fail on any file listed twice, and cover a project that promotes None to Content as well as one that declares html snapshots as Content itself. VerifyTests/Verify.Bunit#108
1 parent 7ccba46 commit f92712d

5 files changed

Lines changed: 58 additions & 16 deletions

readme.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -945,23 +945,30 @@ Verify comes with default MSBuild includes for snapshot files (`*.received.*` an
945945
<PropertyGroup Condition="('$(DisableVerifyFileNesting)' != 'true')">
946946
<VerifyRazorFileCount>@(VerifyRazorFile->Count())</VerifyRazorFileCount>
947947
</PropertyGroup>
948+
<!--
949+
The SDK has already globbed the snapshots into None, so the metadata is applied with an Update.
950+
Adding a second item for a file the SDK holds would leave it in None twice, and anything that
951+
copies None into Content (eg DotNetProjectFile.Analyzers) then fails the build with NETSDK1022.
952+
The Include only picks up snapshots the SDK did not, such as when default items are disabled.
953+
-->
948954
<ItemGroup Condition="('$(DisableVerifyFileNesting)' != 'true')">
949-
<None Include="**\*.received.*;**\*.verified.*" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" Condition="$(Language) == 'C#'">
955+
<None Include="**\*.received.*;**\*.verified.*" Exclude="@(None);$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" Condition="$(Language) == 'C#' Or $(Language) == 'VB'" />
956+
<None Update="**\*.received.*;**\*.verified.*" Condition="$(Language) == 'C#'">
950957
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0].Split('(')[0])</ParentFile>
951958
<ParentExtension>.cs</ParentExtension>
952959
<!-- A Blazor component keeps its code in a .razor.cs code-behind, or in the .razor file itself -->
953960
<ParentExtension Condition="'$(VerifyRazorFileCount)' != '0' And Exists('$(MSBuildProjectDirectory)\%(RelativeDir)%(ParentFile).razor.cs')">.razor.cs</ParentExtension>
954961
<ParentExtension Condition="'$(VerifyRazorFileCount)' != '0' And '%(ParentExtension)' == '.cs' And !Exists('$(MSBuildProjectDirectory)\%(RelativeDir)%(ParentFile).cs') And Exists('$(MSBuildProjectDirectory)\%(RelativeDir)%(ParentFile).razor')">.razor</ParentExtension>
955962
<DependentUpon>%(ParentFile)%(ParentExtension)</DependentUpon>
956963
</None>
957-
<None Include="**\*.received.*;**\*.verified.*" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" Condition="$(Language) == 'VB'">
964+
<None Update="**\*.received.*;**\*.verified.*" Condition="$(Language) == 'VB'">
958965
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0].Split('(')[0])</ParentFile>
959966
<DependentUpon>%(ParentFile).vb</DependentUpon>
960967
</None>
961968
</ItemGroup>
962969
</Project>
963970
```
964-
<sup><a href='/src/Verify/buildTransitive/Verify.AfterMicrosoftNetSdk.props#L1-L28' title='Snippet source file'>snippet source</a> | <a href='#snippet-Verify.AfterMicrosoftNetSdk.props' title='Start of snippet'>anchor</a></sup>
971+
<sup><a href='/src/Verify/buildTransitive/Verify.AfterMicrosoftNetSdk.props#L1-L35' title='Snippet source file'>snippet source</a> | <a href='#snippet-Verify.AfterMicrosoftNetSdk.props' title='Start of snippet'>anchor</a></sup>
965972
<!-- endSnippet -->
966973

967974
To opt out of this feature, include the following in the project file:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
None
2+
ComponentTests.Simple.verified.html: ComponentTests.razor.cs
3+
Content
4+
ComponentTests.Simple.verified.html: ComponentTests.razor.cs
5+
ComponentTests.Simple.verified.json: ComponentTests.razor.cs
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
None
2+
none
3+
Content
4+
ComponentTests.Simple.verified.html: ComponentTests.razor.cs
5+
ComponentTests.Simple.verified.json: ComponentTests.razor.cs

src/Verify.Tests/FileNestingTests.cs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,27 @@ await Evaluate(
7676
"ComponentTests.Simple.verified.html",
7777
"ComponentTests.Simple.verified.json"));
7878

79+
// https://github.com/VerifyTests/Verify.Bunit/issues/108#issuecomment-5523781048
80+
// DotNetProjectFile.Analyzers promotes every None item to Content for its SonarQube
81+
// integration, in builds outside an IDE. A snapshot is None twice, once from the SDK glob and
82+
// once from Verify, so it became Content twice and the build failed with NETSDK1022.
83+
[Fact]
84+
public async Task RazorProjectWithNonePromotedToContent() =>
85+
await Verify(
86+
await Evaluate(
87+
CSharp(
88+
"Microsoft.NET.Sdk.Razor",
89+
body:
90+
"""
91+
<ItemGroup>
92+
<Content Include="@(None)" Exclude="@(Content)" Visible="false" />
93+
</ItemGroup>
94+
"""),
95+
"ComponentTests.razor",
96+
"ComponentTests.razor.cs",
97+
"ComponentTests.Simple.verified.html",
98+
"ComponentTests.Simple.verified.json"));
99+
79100
[Fact]
80101
public async Task RazorProjectWithNestingDisabled() =>
81102
await Verify(
@@ -196,21 +217,18 @@ static string Format(string json)
196217
return builder.ToString();
197218
}
198219

199-
// Verify adds its own item for a snapshot on top of the one the SDK globbed, so the same file
200-
// can appear more than once. Any disagreement between those items is a bug.
201-
static string Parent(IEnumerable<(string Identity, string? Parent)> snapshot)
220+
// The SDK globs a snapshot into None and Verify only updates that item, so a file listed twice
221+
// means an item was added on top of the glob. That is what the SDK rejects once the None items
222+
// are copied into Content, so it is reported rather than merged.
223+
static string Parent(IGrouping<string, (string Identity, string? Parent)> snapshot)
202224
{
203-
var parents = snapshot
204-
.Select(_ => _.Parent)
205-
.Where(_ => _ != null)
206-
.Distinct()
207-
.ToList();
208-
if (parents.Count == 0)
225+
var items = snapshot.ToList();
226+
if (items.Count > 1)
209227
{
210-
return "not nested";
228+
throw new($"{snapshot.Key} is included {items.Count} times");
211229
}
212230

213-
return string.Join(" and ", parents);
231+
return items[0].Parent ?? "not nested";
214232
}
215233

216234
static List<IGrouping<string, (string Identity, string? Parent)>> Snapshots(JsonElement items, string type)

src/Verify/buildTransitive/Verify.AfterMicrosoftNetSdk.props

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,23 @@
1111
<PropertyGroup Condition="('$(DisableVerifyFileNesting)' != 'true')">
1212
<VerifyRazorFileCount>@(VerifyRazorFile->Count())</VerifyRazorFileCount>
1313
</PropertyGroup>
14+
<!--
15+
The SDK has already globbed the snapshots into None, so the metadata is applied with an Update.
16+
Adding a second item for a file the SDK holds would leave it in None twice, and anything that
17+
copies None into Content (eg DotNetProjectFile.Analyzers) then fails the build with NETSDK1022.
18+
The Include only picks up snapshots the SDK did not, such as when default items are disabled.
19+
-->
1420
<ItemGroup Condition="('$(DisableVerifyFileNesting)' != 'true')">
15-
<None Include="**\*.received.*;**\*.verified.*" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" Condition="$(Language) == 'C#'">
21+
<None Include="**\*.received.*;**\*.verified.*" Exclude="@(None);$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" Condition="$(Language) == 'C#' Or $(Language) == 'VB'" />
22+
<None Update="**\*.received.*;**\*.verified.*" Condition="$(Language) == 'C#'">
1623
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0].Split('(')[0])</ParentFile>
1724
<ParentExtension>.cs</ParentExtension>
1825
<!-- A Blazor component keeps its code in a .razor.cs code-behind, or in the .razor file itself -->
1926
<ParentExtension Condition="'$(VerifyRazorFileCount)' != '0' And Exists('$(MSBuildProjectDirectory)\%(RelativeDir)%(ParentFile).razor.cs')">.razor.cs</ParentExtension>
2027
<ParentExtension Condition="'$(VerifyRazorFileCount)' != '0' And '%(ParentExtension)' == '.cs' And !Exists('$(MSBuildProjectDirectory)\%(RelativeDir)%(ParentFile).cs') And Exists('$(MSBuildProjectDirectory)\%(RelativeDir)%(ParentFile).razor')">.razor</ParentExtension>
2128
<DependentUpon>%(ParentFile)%(ParentExtension)</DependentUpon>
2229
</None>
23-
<None Include="**\*.received.*;**\*.verified.*" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" Condition="$(Language) == 'VB'">
30+
<None Update="**\*.received.*;**\*.verified.*" Condition="$(Language) == 'VB'">
2431
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0].Split('(')[0])</ParentFile>
2532
<DependentUpon>%(ParentFile).vb</DependentUpon>
2633
</None>

0 commit comments

Comments
 (0)