diff --git a/Source/Rendering.Cratis/Renderers/UnrenderedConstructs.cs b/Source/Rendering.Cratis/Renderers/UnrenderedConstructs.cs index 91b3ba6..4a129ab 100644 --- a/Source/Rendering.Cratis/Renderers/UnrenderedConstructs.cs +++ b/Source/Rendering.Cratis/Renderers/UnrenderedConstructs.cs @@ -45,6 +45,17 @@ public static void Report(CSharpCodeBuilder builder, SliceSyntax slice, Rendered rendered.HasFlag(RenderedConstructs.ReadModel) ? slice.Projections.Count() - 1 : slice.Projections.Count(), "projection", "no read model is rendered for it."); + + // A rendered read model is inferred from the slice's first projection, so at most one declared readmodel + // has a rendered counterpart — counted the same way the projections above are. + yield return ( + rendered.HasFlag(RenderedConstructs.ReadModel) ? ReadModels(slice).Count() - 1 : ReadModels(slice).Count(), + "readmodel", + "nothing in the rendered application holds the state it declares."); + yield return ( + Reducers(slice).Count(), + "reducer", + "the read model it builds is never populated in the rendered application."); yield return ( rendered.HasFlag(RenderedConstructs.Reactors) ? 0 : slice.Reactors.Count(), "reactor", @@ -74,4 +85,9 @@ public static void Report(CSharpCodeBuilder builder, SliceSyntax slice, Rendered "specification", "no specs are rendered for the generated application."); } + + // Both collections are trailing optionals on SliceSyntax and are null on a slice that declares neither. + static IEnumerable ReadModels(SliceSyntax slice) => slice.ReadModels ?? []; + + static IEnumerable Reducers(SliceSyntax slice) => slice.Reducers ?? []; } diff --git a/Source/Rendering.Cratis/for_UnrenderedConstructs/given/a_slice_declaring_every_family.cs b/Source/Rendering.Cratis/for_UnrenderedConstructs/given/a_slice_declaring_every_family.cs index 55780f1..c14255e 100644 --- a/Source/Rendering.Cratis/for_UnrenderedConstructs/given/a_slice_declaring_every_family.cs +++ b/Source/Rendering.Cratis/for_UnrenderedConstructs/given/a_slice_declaring_every_family.cs @@ -54,6 +54,10 @@ void Establish() [new ReactorTriggerSyntax("InvoiceRegistered", null, null, SourceLocation.Start)], SourceLocation.Start); + var readModel = new ReadModelSyntax("InvoiceSummary", [], SourceLocation.Start); + + var reducer = new ReducerSyntax("InvoiceTotals", "InvoiceSummary", [], SourceLocation.Start); + _slice = new SliceSyntax( SliceType.StateView, "Summary", @@ -66,6 +70,8 @@ [new ReactorTriggerSyntax("InvoiceRegistered", null, null, SourceLocation.Start) [screen], [constraint], [specification], - SourceLocation.Start); + SourceLocation.Start, + ReadModels: [readModel], + Reducers: [reducer]); } } diff --git a/Source/Rendering.Cratis/for_UnrenderedConstructs/when_reporting_what_a_slice_declares.cs b/Source/Rendering.Cratis/for_UnrenderedConstructs/when_reporting_what_a_slice_declares.cs index eb559e9..79e2560 100644 --- a/Source/Rendering.Cratis/for_UnrenderedConstructs/when_reporting_what_a_slice_declares.cs +++ b/Source/Rendering.Cratis/for_UnrenderedConstructs/when_reporting_what_a_slice_declares.cs @@ -16,9 +16,9 @@ public class when_reporting_what_a_slice_declares : a_slice_declaring_every_fami void Because() => UnrenderedConstructs.Report(_builder, _slice, RenderedConstructs.None, _diagnostics); - [Fact] void should_report_every_family_it_declares() => _diagnostics.Count.ShouldEqual(9); + [Fact] void should_report_every_family_it_declares() => _diagnostics.Count.ShouldEqual(11); [Fact] void should_note_every_family_in_the_emitted_file() => - _builder.ToString().Split('\n').Count(line => line.StartsWith("// TODO:", StringComparison.Ordinal)).ShouldEqual(9); + _builder.ToString().Split('\n').Count(line => line.StartsWith("// TODO:", StringComparison.Ordinal)).ShouldEqual(11); [Fact] void should_report_the_command() => _diagnostics.ShouldContain( "Slice 'Summary' declares 1 command declaration(s) with no rendered equivalent — neither its input, the events it " + @@ -26,6 +26,14 @@ [Fact] void should_report_the_command() => [Fact] void should_report_the_projection() => _diagnostics.ShouldContain( "Slice 'Summary' declares 1 projection declaration(s) with no rendered equivalent — no read model is rendered for it."); + [Fact] void should_report_the_read_model() => + _diagnostics.ShouldContain( + "Slice 'Summary' declares 1 readmodel declaration(s) with no rendered equivalent — nothing in the rendered " + + "application holds the state it declares."); + [Fact] void should_report_the_reducer() => + _diagnostics.ShouldContain( + "Slice 'Summary' declares 1 reducer declaration(s) with no rendered equivalent — the read model it builds is " + + "never populated in the rendered application."); [Fact] void should_report_the_reactor() => _diagnostics.ShouldContain( "Slice 'Summary' declares 1 reactor declaration(s) with no rendered equivalent — nothing reacts to the events in the " +