Skip to content

Commit 4e25cee

Browse files
authored
fix(templates): v0.3.0-rc.1 follow-up fixes - Tabs, Select, SidebarInset (#29)
## Summary - Fixes three template bugs surfaced integrating ShellUI 0.3.0-rc.1 into consumer projects: `TabsTemplate`'s unterminated string literal, `SelectTemplate`'s hidden Material Symbols dependency, and `SidebarInsetTemplate` missing `min-w-0` (wide content pushes the page sideways instead of scrolling in place). - Also fixes two more instances of the same bug classes (`FileUploadTemplate` icon-font dependency, `DashboardLayout01Template` verbatim-quote bug), the `accordion-type` and `context-menu-models` missing-dependency bugs, and widens the icon-font regression test to catch Material Icons and Font Awesome too, not just Material Symbols. - `SidebarInset` fix is applied in both the CLI template (`SidebarInsetTemplate.cs`) and the live `ShellUI.Components` package copy (`SidebarInset.razor`), so both install paths are covered. ## Test plan - [x] `TemplateCompileTests` — every template's `@code` block still parses - [x] `DependencyGraphTests` — every declared dependency resolves in the registry - [x] `HiddenNetworkDependencyTests` — no template references an external icon font - [x] `TemplateSyncTests` — live library components match their CLI templates - [x] 18/18 relevant tests passing locally
2 parents 7c02941 + 7fcf429 commit 4e25cee

11 files changed

Lines changed: 166 additions & 7 deletions

‎ShellUI.Tests/NuGetDepsAndSuggestionsTests.cs‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,82 @@ public void ChartVariants_TransitivelyPullInBlazorApexCharts(string componentNam
8484
}
8585
}
8686

87+
public class DependencyGraphTests
88+
{
89+
// Every dependency name declared by any registered component must itself resolve
90+
// via ComponentRegistry.GetMetadata. Catches the class of bug where a sub-component
91+
// template exists on disk but is never wired into the registry — the CLI then
92+
// reports "Failed: <dep-name>" and the consumer project won't compile.
93+
[Fact]
94+
public void EveryDeclaredDependency_ResolvesInRegistry()
95+
{
96+
var missing = new List<string>();
97+
foreach (var (name, metadata) in ComponentRegistry.Components)
98+
{
99+
foreach (var dep in metadata.Dependencies)
100+
{
101+
if (ComponentRegistry.GetMetadata(dep) is null)
102+
missing.Add($"{name} -> {dep}");
103+
}
104+
}
105+
Assert.True(missing.Count == 0,
106+
"The following declared dependencies are not registered in ComponentRegistry:\n " +
107+
string.Join("\n ", missing));
108+
}
109+
110+
// Every installable component (IsAvailable = true) must route through the
111+
// GetComponentContent switch. Sub-component stubs registered as IsAvailable = false
112+
// with intentionally-empty Content are allowed — they exist only so the CLI can
113+
// resolve them as dependency names when the parent renders their markup inline.
114+
[Fact]
115+
public void EveryInstallableComponent_HasContentMapping()
116+
{
117+
var missing = new List<string>();
118+
foreach (var (name, metadata) in ComponentRegistry.Components)
119+
{
120+
if (!metadata.IsAvailable) continue;
121+
var content = ComponentRegistry.GetComponentContent(name);
122+
if (string.IsNullOrWhiteSpace(content))
123+
missing.Add(name);
124+
}
125+
Assert.True(missing.Count == 0,
126+
"The following installable components have no content mapping:\n " +
127+
string.Join("\n ", missing));
128+
}
129+
}
130+
131+
public class HiddenNetworkDependencyTests
132+
{
133+
// ShellUI never links Google Material Symbols/Icons or Font Awesome and every
134+
// component uses inline SVG for its chrome. When a template depends on an
135+
// external icon font the raw icon name ("expand_more", "cloud_upload", ...)
136+
// shows as literal text or the glyph slot renders empty on consumers.
137+
// Fail loudly here so the SVG-swap convention stays enforced across the whole
138+
// registry — the specific classes below cover both Material families and
139+
// Font Awesome's solid/regular/brands.
140+
[Theory]
141+
[InlineData("material-symbols-outlined")] // Material Symbols (newer variable font)
142+
[InlineData("material-symbols-rounded")]
143+
[InlineData("material-symbols-sharp")]
144+
[InlineData("material-icons")] // Material Icons (older static font)
145+
[InlineData("fa-solid")] // Font Awesome v6+
146+
[InlineData("fa-regular")]
147+
[InlineData("fa-brands")]
148+
public void NoTemplate_DependsOnExternalIconFont(string cssClass)
149+
{
150+
var offenders = new List<string>();
151+
foreach (var (name, _) in ComponentRegistry.Components)
152+
{
153+
var content = ComponentRegistry.GetComponentContent(name);
154+
if (content is not null && content.Contains(cssClass))
155+
offenders.Add(name);
156+
}
157+
Assert.True(offenders.Count == 0,
158+
$"The following templates reference `{cssClass}` (external icon font, not shipped by ShellUI):\n " +
159+
string.Join("\n ", offenders));
160+
}
161+
}
162+
87163
public class DataTableTemplateContentTests
88164
{
89165
// The library-wide convention is `Components.Models` for model namespaces regardless

‎ShellUI.Tests/TemplateCompileTests.cs‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public void CsharpTemplate_GeneratedContentParses(string componentName)
3838
[InlineData("dashboard-02")]
3939
[InlineData("button")]
4040
[InlineData("dialog")]
41+
[InlineData("tabs")]
42+
[InlineData("select")]
4143
public void RazorTemplate_CodeBlockParses(string componentName)
4244
{
4345
var content = ComponentRegistry.GetComponentContent(componentName);
@@ -76,6 +78,44 @@ public void RazorTemplate_CodeBlockParses(string componentName)
7678
string.Join("\n", errors.Select(e => $" {e.Location.GetLineSpan().StartLinePosition}: {e.GetMessage()}")));
7779
}
7880

81+
/// Exhaustive sweep: parse the @code block of every installable .razor template
82+
/// in the registry. This is the safety net for the class of bugs where a template
83+
/// ships with an unescaped quote inside its verbatim string (Tabs, PieChart, etc.).
84+
/// Kept separate from the targeted Theory above so a regression's failure point
85+
/// lists just the offending component(s) rather than aborting on the first one.
86+
[Fact]
87+
public void EveryRazorTemplate_CodeBlockParses()
88+
{
89+
var failures = new List<string>();
90+
foreach (var (name, metadata) in ComponentRegistry.Components)
91+
{
92+
if (!metadata.IsAvailable) continue;
93+
if (!metadata.FilePath.EndsWith(".razor", System.StringComparison.OrdinalIgnoreCase)) continue;
94+
95+
var content = ComponentRegistry.GetComponentContent(name);
96+
if (string.IsNullOrWhiteSpace(content)) continue;
97+
if (!content.Contains("@code")) continue;
98+
99+
var codeBlock = ExtractCodeBlock(content!);
100+
if (string.IsNullOrWhiteSpace(codeBlock))
101+
{
102+
failures.Add($"{name}: @code block could not be extracted (likely unterminated string literal)");
103+
continue;
104+
}
105+
106+
var wrapped = $"class __Probe {{ {codeBlock} }}";
107+
var errors = CSharpSyntaxTree.ParseText(wrapped).GetDiagnostics()
108+
.Where(d => d.Severity == DiagnosticSeverity.Error)
109+
.Select(d => $"{d.Location.GetLineSpan().StartLinePosition}: {d.GetMessage()}")
110+
.ToList();
111+
if (errors.Count > 0)
112+
failures.Add($"{name}: {errors.Count} parse error(s) — {errors[0]}");
113+
}
114+
Assert.True(failures.Count == 0,
115+
"The following templates have parse errors in their @code block:\n " +
116+
string.Join("\n ", failures));
117+
}
118+
79119
/// Strips Razor markup directives so the remaining text can be best-effort
80120
/// parsed as C#. Not a real Razor parser — just enough to surface useful
81121
/// diagnostics when ExtractCodeBlock fails.

‎src/ShellUI.Components/Components/SidebarInset.razor‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@namespace ShellUI.Components
22

33
<main class="@Shell.Cn(
4-
"relative flex min-h-svh flex-1 flex-col bg-background",
4+
"relative flex min-h-svh min-w-0 flex-1 flex-col bg-background",
55
"peer-data-[variant=inset]:min-h-[calc(100svh-1rem)] md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
66
Class)"
77
@attributes="AdditionalAttributes">

‎src/ShellUI.Templates/ComponentRegistry.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public static class ComponentRegistry
8181
{ "slider", SliderTemplate.Metadata },
8282
{ "toggle", ToggleTemplate.Metadata },
8383
{ "accordion", AccordionTemplate.Metadata },
84+
{ "accordion-type", AccordionTypeTemplate.Metadata },
8485
{ "accordion-item", AccordionItemTemplate.Metadata },
8586
{ "accordion-trigger", AccordionTriggerTemplate.Metadata },
8687
{ "accordion-content", AccordionContentTemplate.Metadata },
@@ -150,6 +151,7 @@ public static class ComponentRegistry
150151
{ "context-menu-trigger", ContextMenuTriggerTemplate.Metadata },
151152
{ "context-menu-content", ContextMenuContentTemplate.Metadata },
152153
{ "context-menu-option", ContextMenuOptionTemplate.Metadata },
154+
{ "context-menu-models", ContextMenuModelsTemplate.Metadata },
153155
{ "empty-state", EmptyStateTemplate.Metadata },
154156
{ "stepper", StepperTemplate.Metadata },
155157
{ "stepper-list", StepperListTemplate.Metadata },
@@ -258,6 +260,7 @@ public static class ComponentRegistry
258260
"slider" => SliderTemplate.Content,
259261
"toggle" => ToggleTemplate.Content,
260262
"accordion" => AccordionTemplate.Content,
263+
"accordion-type" => AccordionTypeTemplate.Content,
261264
"accordion-item" => AccordionItemTemplate.Content,
262265
"accordion-trigger" => AccordionTriggerTemplate.Content,
263266
"accordion-content" => AccordionContentTemplate.Content,
@@ -327,6 +330,7 @@ public static class ComponentRegistry
327330
"context-menu-trigger" => ContextMenuTriggerTemplate.Content,
328331
"context-menu-content" => ContextMenuContentTemplate.Content,
329332
"context-menu-option" => ContextMenuOptionTemplate.Content,
333+
"context-menu-models" => ContextMenuModelsTemplate.Content,
330334
"empty-state" => EmptyStateTemplate.Content,
331335
"stepper" => StepperTemplate.Content,
332336
"stepper-list" => StepperListTemplate.Content,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using ShellUI.Core.Models;
2+
3+
namespace ShellUI.Templates.Templates;
4+
5+
public static class ContextMenuModelsTemplate
6+
{
7+
public static ComponentMetadata Metadata => new()
8+
{
9+
Name = "context-menu-models",
10+
DisplayName = "ContextMenu Models",
11+
Description = "Models for ContextMenu component",
12+
Category = ComponentCategory.Navigation,
13+
FilePath = "Models/ContextMenuModels.cs",
14+
IsAvailable = false,
15+
Dependencies = new List<string>()
16+
};
17+
18+
public static string Content => @"namespace YourProjectNamespace.Components.Models;
19+
20+
public class ContextMenuItem
21+
{
22+
public string Label { get; set; } = string.Empty;
23+
public string? Icon { get; set; }
24+
public string? Shortcut { get; set; }
25+
public bool Disabled { get; set; }
26+
public bool IsSeparator { get; set; }
27+
public object? Data { get; set; }
28+
}
29+
";
30+
}

‎src/ShellUI.Templates/Templates/ContextMenuTemplate.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public static class ContextMenuTemplate
1212
Category = ComponentCategory.Navigation,
1313
FilePath = "ContextMenu.razor",
1414
Tags = new List<string> { "menu", "context", "right-click", "dropdown" },
15-
Dependencies = new List<string>()
15+
Dependencies = new List<string> { "context-menu-models" }
1616
};
1717

1818
public const string Content = """

‎src/ShellUI.Templates/Templates/DashboardLayout01Template.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private void BuildBreadcrumb()
8383
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
8484
8585
_breadcrumbItems.Clear();
86-
if (segments.Length == 0 || (segments.Length == 1 && segments[0] == ""))
86+
if (segments.Length == 0 || (segments.Length == 1 && segments[0] == """"))
8787
{
8888
_breadcrumbItems.Add(new BreadcrumbItemData { Label = ""Home"", Href = ""/"", IsLast = true });
8989
return;

‎src/ShellUI.Templates/Templates/FileUploadTemplate.cs‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ @using Microsoft.JSInterop
2828
@ondragover:preventDefault=""true""
2929
@ondrop:preventDefault=""true"">
3030
<div class=""text-center pointer-events-none"">
31-
<span class=""material-symbols-outlined mx-auto text-5xl text-muted-foreground block"">cloud_upload</span>
31+
<svg class=""mx-auto h-12 w-12 text-muted-foreground block""
32+
xmlns=""http://www.w3.org/2000/svg"" fill=""none"" viewBox=""0 0 24 24"" stroke=""currentColor"">
33+
<path stroke-linecap=""round"" stroke-linejoin=""round"" stroke-width=""1.5""
34+
d=""M7 16a4 4 0 01-.88-7.9 5 5 0 019.9-1A5.5 5.5 0 0118.5 16H7z"" />
35+
<path stroke-linecap=""round"" stroke-linejoin=""round"" stroke-width=""1.5""
36+
d=""M12 12v6m0-6l-2 2m2-2l2 2"" />
37+
</svg>
3238
<div class=""mt-4"">
3339
<p class=""text-sm text-foreground font-medium"">@Text</p>
3440
@if (Multiple && AllowedFileCount > 1)

‎src/ShellUI.Templates/Templates/SelectTemplate.cs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ public static class SelectTemplate
2626
@attributes=""AdditionalAttributes"">
2727
@ChildContent
2828
</select>
29-
<span class=""material-symbols-outlined absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none"">expand_more</span>
29+
<svg class=""pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground""
30+
xmlns=""http://www.w3.org/2000/svg"" fill=""none"" viewBox=""0 0 24 24"" stroke=""currentColor"">
31+
<path stroke-linecap=""round"" stroke-linejoin=""round"" stroke-width=""2"" d=""M19 9l-7 7-7-7"" />
32+
</svg>
3033
</div>
3134
3235
@code {

‎src/ShellUI.Templates/Templates/SidebarInsetTemplate.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static class SidebarInsetTemplate
1717
public static string Content => @"@namespace YourProjectNamespace.Components.UI
1818
1919
<main class=""@Shell.Cn(
20-
""relative flex min-h-svh flex-1 flex-col bg-background"",
20+
""relative flex min-h-svh min-w-0 flex-1 flex-col bg-background"",
2121
""peer-data-[variant=inset]:min-h-[calc(100svh-1rem)] md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow"",
2222
Class)""
2323
@attributes=""AdditionalAttributes"">

0 commit comments

Comments
 (0)