Skip to content

Commit 0c88bf8

Browse files
committed
feat: add integration tests for AddCommand, Assembly scanning, and component alias registration to ensure functionality and correctness
1 parent 1716673 commit 0c88bf8

3 files changed

Lines changed: 275 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using System.Reflection;
2+
using Xunit;
3+
4+
namespace ShellDocs.Tests;
5+
6+
/* Integration tests for `shelldocs add`. Uses reflection to reach the internal
7+
AddCommand.Run entry point (matches the InitCommand test pattern). */
8+
public class AddCommandTests : IDisposable
9+
{
10+
private readonly string _sandbox;
11+
private readonly MethodInfo _run;
12+
13+
public AddCommandTests()
14+
{
15+
_sandbox = Path.Combine(Path.GetTempPath(), "shelldocs-add-tests-" + Guid.NewGuid().ToString("N")[..8]);
16+
Directory.CreateDirectory(Path.Combine(_sandbox, "content"));
17+
18+
var cli = AppDomain.CurrentDomain.GetAssemblies()
19+
.FirstOrDefault(a => a.GetName().Name == "shelldocs")
20+
?? Assembly.Load("shelldocs");
21+
var type = cli.GetType("ShellDocs.CLI.Commands.AddCommand", throwOnError: true)!;
22+
_run = type.GetMethod("Run", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)!;
23+
}
24+
25+
public void Dispose()
26+
{
27+
try { Directory.Delete(_sandbox, recursive: true); } catch { }
28+
}
29+
30+
private int Add(string template, string name, bool force = false, string? dir = null) =>
31+
(int)_run.Invoke(null, new object?[] { template, name, dir ?? _sandbox, force })!;
32+
33+
[Fact]
34+
public void Add_Component_WritesToContentDocsComponents_WithSlugFilename()
35+
{
36+
var exit = Add("component", "MyBigCard");
37+
38+
Assert.Equal(0, exit);
39+
var expected = Path.Combine(_sandbox, "content", "docs", "components", "my-big-card.md");
40+
Assert.True(File.Exists(expected));
41+
var body = File.ReadAllText(expected);
42+
Assert.Contains("title: MyBigCard", body);
43+
Assert.Contains("`<MyBigCard>`", body);
44+
Assert.Contains("razor:preview", body);
45+
Assert.Contains("<TypeTable>", body);
46+
}
47+
48+
[Fact]
49+
public void Add_Guide_WritesToContentDocsGuides_WithTitleCaseName()
50+
{
51+
var exit = Add("guide", "getting-started");
52+
53+
Assert.Equal(0, exit);
54+
var expected = Path.Combine(_sandbox, "content", "docs", "guides", "getting-started.md");
55+
Assert.True(File.Exists(expected));
56+
var body = File.ReadAllText(expected);
57+
Assert.Contains("title: Getting Started", body);
58+
Assert.Contains("<Steps>", body);
59+
}
60+
61+
[Fact]
62+
public void Add_Page_WritesToContentDocs_WithBlankBody()
63+
{
64+
var exit = Add("page", "faq");
65+
66+
Assert.Equal(0, exit);
67+
var expected = Path.Combine(_sandbox, "content", "docs", "faq.md");
68+
Assert.True(File.Exists(expected));
69+
var body = File.ReadAllText(expected);
70+
Assert.Contains("title: Faq", body);
71+
Assert.DoesNotContain("razor:preview", body);
72+
}
73+
74+
[Fact]
75+
public void Add_UnknownTemplate_ReturnsNonZero()
76+
{
77+
Assert.NotEqual(0, Add("widget", "Foo"));
78+
}
79+
80+
[Fact]
81+
public void Add_MissingContentDir_ReturnsNonZero()
82+
{
83+
var stray = Path.Combine(Path.GetTempPath(), "shelldocs-add-no-content-" + Guid.NewGuid().ToString("N")[..8]);
84+
Directory.CreateDirectory(stray);
85+
try
86+
{
87+
Assert.NotEqual(0, Add("component", "Foo", dir: stray));
88+
}
89+
finally
90+
{
91+
Directory.Delete(stray, recursive: true);
92+
}
93+
}
94+
95+
[Fact]
96+
public void Add_ExistingFileWithoutForce_Fails()
97+
{
98+
Add("component", "Duplicate");
99+
Assert.NotEqual(0, Add("component", "Duplicate"));
100+
}
101+
102+
[Fact]
103+
public void Add_ExistingFileWithForce_Overwrites()
104+
{
105+
Add("component", "Duplicate");
106+
var target = Path.Combine(_sandbox, "content", "docs", "components", "duplicate.md");
107+
File.WriteAllText(target, "custom content");
108+
109+
var exit = Add("component", "Duplicate", force: true);
110+
111+
Assert.Equal(0, exit);
112+
var body = File.ReadAllText(target);
113+
Assert.Contains("`<Duplicate>`", body);
114+
Assert.DoesNotContain("custom content", body);
115+
}
116+
117+
[Fact]
118+
public void Add_EmptyName_ReturnsNonZero()
119+
{
120+
Assert.NotEqual(0, Add("component", ""));
121+
}
122+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using Microsoft.AspNetCore.Components;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using ShellDocs.Components;
4+
using ShellDocs.Markdown;
5+
using Xunit;
6+
7+
namespace ShellDocs.Tests;
8+
9+
public class AssemblyScanTests
10+
{
11+
[Fact]
12+
public void RegisterComponentsFromAssembly_GenericMarker_PicksUpConcretePublicComponents()
13+
{
14+
var options = new ShellDocsOptions();
15+
options.RegisterComponentsFromAssembly<TestMarker>();
16+
17+
Assert.Contains(typeof(ScannableAlpha), options.RegisteredComponents);
18+
Assert.Contains(typeof(ScannableBeta), options.RegisteredComponents);
19+
}
20+
21+
[Fact]
22+
public void RegisterComponentsFromAssembly_SkipsAbstractTypes()
23+
{
24+
var options = new ShellDocsOptions();
25+
options.RegisterComponentsFromAssembly<TestMarker>();
26+
27+
Assert.DoesNotContain(typeof(AbstractShouldSkip), options.RegisteredComponents);
28+
}
29+
30+
[Fact]
31+
public void RegisterComponentsFromAssembly_SkipsGenericDefinitions()
32+
{
33+
var options = new ShellDocsOptions();
34+
options.RegisterComponentsFromAssembly<TestMarker>();
35+
36+
Assert.DoesNotContain(options.RegisteredComponents, t => t.Name.StartsWith("GenericShouldSkip"));
37+
}
38+
39+
[Fact]
40+
public void RegisterComponentsFromAssembly_SkipsNonComponentBase()
41+
{
42+
var options = new ShellDocsOptions();
43+
options.RegisterComponentsFromAssembly<TestMarker>();
44+
45+
Assert.DoesNotContain(typeof(NotAComponent), options.RegisteredComponents);
46+
}
47+
48+
[Fact]
49+
public void RegisterComponentsFromAssembly_RespectsShellDocsIgnore()
50+
{
51+
var options = new ShellDocsOptions();
52+
options.RegisterComponentsFromAssembly<TestMarker>();
53+
54+
Assert.DoesNotContain(typeof(IgnoredExplicitly), options.RegisteredComponents);
55+
}
56+
57+
[Fact]
58+
public void RegisterComponentsFromAssembly_FilterOverload_AppliedOnTopOfSystemRules()
59+
{
60+
var options = new ShellDocsOptions();
61+
options.RegisterComponentsFromAssembly<TestMarker>(t => t.Name == nameof(ScannableAlpha));
62+
63+
Assert.Contains(typeof(ScannableAlpha), options.RegisteredComponents);
64+
Assert.DoesNotContain(typeof(ScannableBeta), options.RegisteredComponents);
65+
}
66+
67+
[Fact]
68+
public void RegisterComponentsFromAssembly_FlowsIntoTypeRegistry()
69+
{
70+
var services = new ServiceCollection();
71+
services.AddShellDocs(o => o.RegisterComponentsFromAssembly<TestMarker>());
72+
var sp = services.BuildServiceProvider();
73+
74+
var registry = sp.GetRequiredService<TypeRegistry>();
75+
Assert.Equal(typeof(ScannableAlpha), registry.Resolve(nameof(ScannableAlpha)));
76+
Assert.Equal(typeof(ScannableBeta), registry.Resolve(nameof(ScannableBeta)));
77+
}
78+
79+
[Fact]
80+
public void RegisterComponent_TypeOverload_RejectsNonComponentBase()
81+
{
82+
var options = new ShellDocsOptions();
83+
Assert.Throws<ArgumentException>(() => options.RegisterComponent(typeof(NotAComponent)));
84+
}
85+
86+
// ---- test doubles used by the scan (all live in this assembly) ----
87+
public class TestMarker { }
88+
public class ScannableAlpha : ComponentBase { }
89+
public class ScannableBeta : ComponentBase { }
90+
public abstract class AbstractShouldSkip : ComponentBase { }
91+
public class GenericShouldSkip<T> : ComponentBase { }
92+
public class NotAComponent { }
93+
[ShellDocsIgnore] public class IgnoredExplicitly : ComponentBase { }
94+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using Microsoft.AspNetCore.Components;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using ShellDocs.Components;
4+
using ShellDocs.Markdown;
5+
using Xunit;
6+
7+
namespace ShellDocs.Tests;
8+
9+
public class ComponentAliasTests
10+
{
11+
[Fact]
12+
public void RegisterComponent_GenericAliasOverload_RegistersUnderTagName()
13+
{
14+
var services = new ServiceCollection();
15+
services.AddShellDocs(o => o.RegisterComponent<AliasWidget>("Widget"));
16+
var sp = services.BuildServiceProvider();
17+
var registry = sp.GetRequiredService<TypeRegistry>();
18+
19+
Assert.Equal(typeof(AliasWidget), registry.Resolve("Widget"));
20+
// The type's short name should NOT resolve when an alias is in effect.
21+
Assert.Null(registry.Resolve(nameof(AliasWidget)));
22+
}
23+
24+
[Fact]
25+
public void RegisterComponent_TypeAliasOverload_RegistersUnderTagName()
26+
{
27+
var services = new ServiceCollection();
28+
services.AddShellDocs(o => o.RegisterComponent(typeof(AliasWidget), "W"));
29+
var sp = services.BuildServiceProvider();
30+
var registry = sp.GetRequiredService<TypeRegistry>();
31+
32+
Assert.Equal(typeof(AliasWidget), registry.Resolve("W"));
33+
}
34+
35+
[Fact]
36+
public void RegisterComponent_AliasOverload_RejectsBlankTagName()
37+
{
38+
var options = new ShellDocsOptions();
39+
Assert.Throws<ArgumentException>(() => options.RegisterComponent<AliasWidget>(""));
40+
Assert.Throws<ArgumentException>(() => options.RegisterComponent<AliasWidget>(" "));
41+
}
42+
43+
[Fact]
44+
public void RegisterComponent_LastAliasWinsForSameType()
45+
{
46+
var services = new ServiceCollection();
47+
services.AddShellDocs(o =>
48+
{
49+
o.RegisterComponent<AliasWidget>("First");
50+
o.RegisterComponent<AliasWidget>("Second");
51+
});
52+
var sp = services.BuildServiceProvider();
53+
var registry = sp.GetRequiredService<TypeRegistry>();
54+
55+
Assert.Equal(typeof(AliasWidget), registry.Resolve("Second"));
56+
}
57+
58+
public class AliasWidget : ComponentBase { }
59+
}

0 commit comments

Comments
 (0)