1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . IO ;
4+ using System . Linq ;
5+ using System . Text . Json ;
6+ using DocumentFormat . OpenXml . Packaging ;
7+ using DocumentFormat . OpenXml . Wordprocessing ;
18using eAuthor . Models ;
29using eAuthor . Services ;
310using eAuthor . Services . Expressions ;
411using NUnit . Framework ;
5- using System ;
6- using System . Text . Json ;
712
8- namespace eAuthor . API . Tests ;
9-
10- public class DocumentGenerationTests
13+ namespace eAuthor . API . Tests
1114{
12- private DocumentGenerationService _service = null ! ;
13-
14- [ SetUp ]
15- public void Setup ( )
15+ [ TestFixture ]
16+ public class DocumentGenerationTests
1617 {
17- _service = new DocumentGenerationService ( new ExpressionParser ( ) , new ExpressionEvaluator ( ) ) ;
18- }
18+ private StyleRenderer _styleRenderer = null ! ;
19+ private DynamicDocxBuilderService _docxBuilder = null ! ;
20+ private IExpressionParser _parser = null ! ;
21+ private IExpressionEvaluator _evaluator = null ! ;
22+ private IConditionalBlockProcessor _conditional = null ! ;
1923
20- [ Test ]
21- public void ReplacesTokensInHtml ( )
22- {
23- var template = new Template
24+ [ SetUp ]
25+ public void SetUp ( )
26+ {
27+ _styleRenderer = new StyleRenderer ( ) ;
28+ _docxBuilder = new DynamicDocxBuilderService ( _styleRenderer ) ;
29+ _parser = new ExpressionParser ( ) ;
30+ _evaluator = new ExpressionEvaluator ( ) ;
31+ _conditional = new ConditionalBlockProcessor ( _parser , _evaluator ) ;
32+ }
33+
34+ [ Test ]
35+ public void Build_TextAndCheckboxControls_ProduceExpectedTags ( )
36+ {
37+ var template = new Template
38+ {
39+ Id = Guid . NewGuid ( ) ,
40+ Name = "Basic" ,
41+ Controls = new List < TemplateControl >
42+ {
43+ new TemplateControl
44+ {
45+ Id = Guid . NewGuid ( ) ,
46+ ControlType = "TextBox" ,
47+ DataPath = "/Customer/Name" ,
48+ Label = "Customer Name" ,
49+ StyleJson = "{\" bold\" :true}"
50+ } ,
51+ new TemplateControl
52+ {
53+ Id = Guid . NewGuid ( ) ,
54+ ControlType = "CheckBox" ,
55+ DataPath = "/Customer/IsPremium" ,
56+ Label = "Premium?"
57+ }
58+ }
59+ } ;
60+
61+ var docBytes = _docxBuilder . Build ( template ) ;
62+
63+ var xml = ExtractMainDocumentXml ( docBytes ) ;
64+ StringAssert . Contains ( "|TextBox|/Customer/Name" , xml , "TextBox tag missing." ) ;
65+ StringAssert . Contains ( "|CheckBox|/Customer/IsPremium" , xml , "CheckBox tag missing." ) ;
66+ }
67+
68+ [ Test ]
69+ public void Build_RadioGroup_EmitsIndividualOptionTags ( )
70+ {
71+ var ctrl = new TemplateControl
72+ {
73+ Id = Guid . NewGuid ( ) ,
74+ ControlType = "RadioGroup" ,
75+ DataPath = "/Case/Status" ,
76+ Label = "Status" ,
77+ OptionsJson = "[\" Open\" ,\" Closed\" ,\" Archived\" ]"
78+ } ;
79+
80+ var template = new Template
81+ {
82+ Id = Guid . NewGuid ( ) ,
83+ Name = "RadioGroupDoc" ,
84+ Controls = new List < TemplateControl > { ctrl }
85+ } ;
86+
87+ var bytes = _docxBuilder . Build ( template ) ;
88+ var xml = ExtractMainDocumentXml ( bytes ) ;
89+
90+ // Pattern: {Id}|RadioGroup|/Case/Status|{index}|{option}
91+ StringAssert . Contains ( "|RadioGroup|/Case/Status|0|Open" , xml ) ;
92+ StringAssert . Contains ( "|RadioGroup|/Case/Status|1|Closed" , xml ) ;
93+ StringAssert . Contains ( "|RadioGroup|/Case/Status|2|Archived" , xml ) ;
94+ }
95+
96+ [ Test ]
97+ public void Build_RepeaterWithBindings_IncludesRepeatTag_AndPossiblyW15Elements ( )
98+ {
99+ var repeater = new TemplateControl
100+ {
101+ Id = Guid . NewGuid ( ) ,
102+ ControlType = "Repeater" ,
103+ DataPath = "/Invoice/Lines/Line" ,
104+ Label = "Invoice Lines" ,
105+ Bindings = new List < TemplateControlBinding >
106+ {
107+ new TemplateControlBinding
108+ {
109+ Id = Guid . NewGuid ( ) ,
110+ ColumnHeader = "Description" ,
111+ DataPath = "Description"
112+ } ,
113+ new TemplateControlBinding
114+ {
115+ Id = Guid . NewGuid ( ) ,
116+ ColumnHeader = "Amount" ,
117+ DataPath = "Amount"
118+ }
119+ }
120+ } ;
121+
122+ var template = new Template
123+ {
124+ Id = Guid . NewGuid ( ) ,
125+ Name = "Repeater" ,
126+ Controls = new List < TemplateControl > { repeater }
127+ } ;
128+
129+ var bytes = _docxBuilder . Build ( template ) ;
130+ var xml = ExtractMainDocumentXml ( bytes ) ;
131+
132+ StringAssert . Contains ( "|Repeat|/Invoice/Lines/Line" , xml , "Repeater tag missing." ) ;
133+
134+ // Soft assertions: present only if raw w15 repeating section succeeded
135+ var hasSection = xml . Contains ( "<w15:repeatingSection" ) ;
136+ var hasItem = xml . Contains ( "<w15:repeatingSectionItem" ) ;
137+ TestContext . WriteLine ( "w15 repeatingSection present: " + hasSection ) ;
138+ TestContext . WriteLine ( "w15 repeatingSectionItem present: " + hasItem ) ;
139+ // Do not assert they must exist—fallback may have been used.
140+ }
141+
142+ [ Test ]
143+ public void Build_RepeaterWithoutBindings_CreatesPlaceholderBinding ( )
24144 {
25- Id = Guid . NewGuid ( ) ,
26- Name = "Test" ,
27- HtmlBody = "<h1>{{ /Customer/Name | upper }}</h1>"
28- } ;
29- var dataJson = """
145+ var repeater = new TemplateControl
146+ {
147+ Id = Guid . NewGuid ( ) ,
148+ ControlType = "Repeater" ,
149+ DataPath = "/Root/Entries" ,
150+ Label = "Entries"
151+ // No bindings provided => should create placeholder "Value"
152+ } ;
153+
154+ var template = new Template
155+ {
156+ Id = Guid . NewGuid ( ) ,
157+ Name = "RepeaterNoBindings" ,
158+ Controls = new List < TemplateControl > { repeater }
159+ } ;
160+
161+ var bytes = _docxBuilder . Build ( template ) ;
162+ var xml = ExtractMainDocumentXml ( bytes ) ;
163+
164+ StringAssert . Contains ( ">Value<" , xml , "Expected placeholder header 'Value' not found." ) ;
165+ StringAssert . Contains ( "{{ Value }}" , xml , "Expected placeholder cell token not found." ) ;
166+ }
167+
168+ [ Test ]
169+ public void ConditionalBlockProcessor_SelectsElseIfBranch ( )
170+ {
171+ var json = JsonDocument . Parse ( """
172+ {
173+ "Customer": {
174+ "IsPremium": false,
175+ "IsTrial": true
176+ }
177+ }
178+ """ ) . RootElement ;
179+
180+ var source = @"
181+ Start
182+ {{ if /Customer/IsPremium }}PREMIUM
183+ {{ elseif /Customer/IsTrial }}TRIAL
184+ {{ else }}NONE
185+ {{ end }}
186+ End" ;
187+
188+ var processed = _conditional . ProcessConditionals ( source , json ) ;
189+
190+ // Expect TRIAL chosen, not PREMIUM or NONE
191+ StringAssert . Contains ( "TRIAL" , processed ) ;
192+ Assert . False ( processed . Contains ( "PREMIUM" ) , "Unexpected PREMIUM branch present." ) ;
193+ Assert . False ( processed . Contains ( "NONE" ) , "Unexpected ELSE branch present." ) ;
194+ }
195+
196+ [ Test ]
197+ public void ExpressionEvaluator_ResolvesSimplePaths ( )
198+ {
199+ var json = JsonDocument . Parse ( """
200+ {
201+ "Order": {
202+ "Id": 123,
203+ "Customer": { "Name": "Alice" }
204+ }
205+ }
206+ """ ) . RootElement ;
207+
208+ var exprOrderId = _parser . Parse ( "/Order/Id" ) ;
209+ var exprCustName = _parser . Parse ( "/Order/Customer/Name" ) ;
210+
211+ var idEl = _evaluator . ResolvePath ( json , exprOrderId . DataPath ) ;
212+ var nameEl = _evaluator . ResolvePath ( json , exprCustName . DataPath ) ;
213+
214+ Assert . That ( idEl ? . ValueKind , Is . EqualTo ( JsonValueKind . Number ) ) ;
215+ Assert . That ( idEl ? . GetInt32 ( ) , Is . EqualTo ( 123 ) ) ;
216+ Assert . That ( nameEl ? . ValueKind , Is . EqualTo ( JsonValueKind . String ) ) ;
217+ Assert . That ( nameEl ? . GetString ( ) , Is . EqualTo ( "Alice" ) ) ;
218+ }
219+
220+ [ Test ]
221+ public void ExpressionEvaluator_ArrayIndexIfSupported_OtherwiseInconclusive ( )
222+ {
223+ var json = JsonDocument . Parse ( """
224+ {
225+ "Data": {
226+ "Items": [
227+ { "Value": "A" },
228+ { "Value": "B" }
229+ ]
230+ }
231+ }
232+ """ ) . RootElement ;
233+
234+ // If indexing syntax not implemented in current parser, test should not hard fail.
235+ try
236+ {
237+ var exprIndex = _parser . Parse ( "/Data/Items[1]/Value" ) ;
238+ var el = _evaluator . ResolvePath ( json , exprIndex . DataPath ) ;
239+ if ( el ? . ValueKind == JsonValueKind . String )
240+ {
241+ Assert . That ( el ? . GetString ( ) , Is . EqualTo ( "B" ) ) ;
242+ }
243+ else
244+ {
245+ Assert . Inconclusive ( "Array index parse returned non-string element; parser may not support indexing." ) ;
246+ }
247+ }
248+ catch ( Exception ex )
249+ {
250+ Assert . Inconclusive ( "Array indexing not supported by current parser/evaluator. " + ex . Message ) ;
251+ }
252+ }
253+
254+ private static string ExtractMainDocumentXml ( byte [ ] bytes )
30255 {
31- "Customer": { "Name": "Alice" }
256+ using var ms = new MemoryStream ( bytes ) ;
257+ using var doc = WordprocessingDocument . Open ( ms , false ) ;
258+ using var sr = new StreamReader ( doc . MainDocumentPart ! . GetStream ( ) ) ;
259+ return sr . ReadToEnd ( ) ;
32260 }
33- """ ;
34- var data = JsonDocument . Parse ( dataJson ) . RootElement ;
35- var bytes = _service . Generate ( template , data , null ) ;
36- Assert . That ( bytes . Length , Is . GreaterThan ( 0 ) ) ;
37- // Not parsing the docx in test for brevity
38261 }
39262}
0 commit comments