@@ -128,6 +128,235 @@ await File.WriteAllTextAsync(filePath, $$"""
128128 }
129129 }
130130
131+ [ Fact ]
132+ public async Task ValidateAsync_FlagsHtmlTagMismatch ( )
133+ {
134+ var tempDir = CreateTempDirectory ( ) ;
135+
136+ try
137+ {
138+ var filePath = Path . Combine ( tempDir , "hindi.json" ) ;
139+ await File . WriteAllTextAsync ( filePath , """
140+ {
141+ "<strong>Never</strong> trust anything but <code>id</code>": "केवल <code>id</code> पर भरोसा करें",
142+ "kept-intact": "<code>foo</code> bar <code>baz</code>"
143+ }
144+ """ . Replace ( "<code>foo</code> bar <code>baz</code>" ,
145+ "<code>foo</code> bar <code>baz</code>" ) ) ;
146+
147+ // Re-write with a balanced kept-intact entry so only the first entry fails the rule
148+ await File . WriteAllTextAsync ( filePath , """
149+ {
150+ "<strong>Never</strong> trust anything but <code>id</code>": "केवल <code>id</code> पर भरोसा करें",
151+ "<code>foo</code>": "<code>foo</code>"
152+ }
153+ """ ) ;
154+
155+ var sut = CreateSut ( tempDir ) ;
156+ var result = await sut . ValidateAsync ( fix : false ) ;
157+
158+ Assert . Equal ( 2 , result . EntriesScanned ) ;
159+ var issue = Assert . Single ( result . Issues ) ;
160+ Assert . StartsWith ( "<strong>Never" , issue . Key ) ;
161+ Assert . Contains ( "Structural HTML tag mismatch" , issue . Reason ) ;
162+ }
163+ finally
164+ {
165+ if ( Directory . Exists ( tempDir ) )
166+ {
167+ Directory . Delete ( tempDir , recursive : true ) ;
168+ }
169+ }
170+ }
171+
172+ [ Fact ]
173+ public async Task ValidateAsync_IgnoresExampleEmailAngleBrackets ( )
174+ {
175+ // The HTML-tag check uses a curated allowlist of structural elements
176+ // (strong/em/code/br/p/a/etc.) so localized example data like
177+ // "<email@primer.com>" doesn't trip the rule even though the bare
178+ // HtmlTagRegex would match it.
179+ var tempDir = CreateTempDirectory ( ) ;
180+
181+ try
182+ {
183+ var filePath = Path . Combine ( tempDir , "serbian.json" ) ;
184+ await File . WriteAllTextAsync ( filePath , """
185+ {
186+ "Firstname Lastname <email@example.com>": "Ime Prezime <email@primer.com>"
187+ }
188+ """ ) ;
189+
190+ var sut = CreateSut ( tempDir ) ;
191+ var result = await sut . ValidateAsync ( fix : false ) ;
192+
193+ Assert . Equal ( 1 , result . EntriesScanned ) ;
194+ Assert . Empty ( result . Issues ) ;
195+ }
196+ finally
197+ {
198+ if ( Directory . Exists ( tempDir ) )
199+ {
200+ Directory . Delete ( tempDir , recursive : true ) ;
201+ }
202+ }
203+ }
204+
205+ [ Fact ]
206+ public async Task ValidateAsync_FlagsInvalidMaintainerField ( )
207+ {
208+ var tempDir = CreateTempDirectory ( ) ;
209+
210+ try
211+ {
212+ var filePath = Path . Combine ( tempDir , "bad-maintainer.json" ) ;
213+ await File . WriteAllTextAsync ( filePath , """
214+ {
215+ "_maintainer": "someone with no pipe or URL",
216+ "hello": "bonjour"
217+ }
218+ """ ) ;
219+
220+ var sut = CreateSut ( tempDir ) ;
221+ var result = await sut . ValidateAsync ( fix : false ) ;
222+
223+ // _maintainer is not counted as a translation entry
224+ Assert . Equal ( 1 , result . EntriesScanned ) ;
225+ var issue = Assert . Single ( result . Issues ) ;
226+ Assert . Equal ( "_maintainer" , issue . Key ) ;
227+ Assert . Contains ( "Invalid _maintainer value" , issue . Reason ) ;
228+ }
229+ finally
230+ {
231+ if ( Directory . Exists ( tempDir ) )
232+ {
233+ Directory . Delete ( tempDir , recursive : true ) ;
234+ }
235+ }
236+ }
237+
238+ [ Fact ]
239+ public async Task ValidateAsync_AcceptsWellFormedMaintainerField ( )
240+ {
241+ var tempDir = CreateTempDirectory ( ) ;
242+
243+ try
244+ {
245+ var filePath = Path . Combine ( tempDir , "ok-maintainer.json" ) ;
246+ await File . WriteAllTextAsync ( filePath , """
247+ {
248+ "_maintainer": "thgO-O|https://github.com/thgO-O",
249+ "hello": "olá"
250+ }
251+ """ ) ;
252+
253+ var sut = CreateSut ( tempDir ) ;
254+ var result = await sut . ValidateAsync ( fix : false ) ;
255+
256+ Assert . Equal ( 1 , result . EntriesScanned ) ;
257+ Assert . Empty ( result . Issues ) ;
258+ }
259+ finally
260+ {
261+ if ( Directory . Exists ( tempDir ) )
262+ {
263+ Directory . Delete ( tempDir , recursive : true ) ;
264+ }
265+ }
266+ }
267+
268+ [ Fact ]
269+ public async Task ValidateAsync_RejectsMaintainerWithHttpScheme ( )
270+ {
271+ var tempDir = CreateTempDirectory ( ) ;
272+
273+ try
274+ {
275+ var filePath = Path . Combine ( tempDir , "http-maintainer.json" ) ;
276+ await File . WriteAllTextAsync ( filePath , """
277+ {
278+ "_maintainer": "thgO-O|http://github.com/thgO-O"
279+ }
280+ """ ) ;
281+
282+ var sut = CreateSut ( tempDir ) ;
283+ var result = await sut . ValidateAsync ( fix : false ) ;
284+
285+ var issue = Assert . Single ( result . Issues ) ;
286+ Assert . Equal ( "_maintainer" , issue . Key ) ;
287+ Assert . Contains ( "Invalid _maintainer" , issue . Reason ) ;
288+ }
289+ finally
290+ {
291+ if ( Directory . Exists ( tempDir ) )
292+ {
293+ Directory . Delete ( tempDir , recursive : true ) ;
294+ }
295+ }
296+ }
297+
298+ [ Fact ]
299+ public async Task ValidateAsync_AcceptsNullMaintainerField ( )
300+ {
301+ var tempDir = CreateTempDirectory ( ) ;
302+
303+ try
304+ {
305+ var filePath = Path . Combine ( tempDir , "null-maintainer.json" ) ;
306+ await File . WriteAllTextAsync ( filePath , """
307+ {
308+ "_maintainer": null,
309+ "hello": "hei"
310+ }
311+ """ ) ;
312+
313+ var sut = CreateSut ( tempDir ) ;
314+ var result = await sut . ValidateAsync ( fix : false ) ;
315+
316+ Assert . Equal ( 1 , result . EntriesScanned ) ;
317+ Assert . Empty ( result . Issues ) ;
318+ }
319+ finally
320+ {
321+ if ( Directory . Exists ( tempDir ) )
322+ {
323+ Directory . Delete ( tempDir , recursive : true ) ;
324+ }
325+ }
326+ }
327+
328+ [ Fact ]
329+ public async Task ValidateAsync_RejectsBlankMaintainerField_WhenPresent ( )
330+ {
331+ var tempDir = CreateTempDirectory ( ) ;
332+
333+ try
334+ {
335+ var filePath = Path . Combine ( tempDir , "blank-maintainer.json" ) ;
336+ await File . WriteAllTextAsync ( filePath , """
337+ {
338+ "_maintainer": " ",
339+ "hello": "hei"
340+ }
341+ """ ) ;
342+
343+ var sut = CreateSut ( tempDir ) ;
344+ var result = await sut . ValidateAsync ( fix : false ) ;
345+
346+ Assert . Equal ( 1 , result . EntriesScanned ) ;
347+ var issue = Assert . Single ( result . Issues ) ;
348+ Assert . Equal ( "_maintainer" , issue . Key ) ;
349+ Assert . Contains ( "Invalid _maintainer" , issue . Reason ) ;
350+ }
351+ finally
352+ {
353+ if ( Directory . Exists ( tempDir ) )
354+ {
355+ Directory . Delete ( tempDir , recursive : true ) ;
356+ }
357+ }
358+ }
359+
131360 private static LanguagePackValidator CreateSut ( string outputDirectory )
132361 {
133362 var configuration = new ConfigurationBuilder ( )
0 commit comments