Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

235 строки
9.6KB

  1. using EnvelopeRenderer.Desktop.Core.Design;
  2. namespace EnvelopeRenderer.Desktop.Tests;
  3. public class TemplateLayoutXmlSerializerTests : IDisposable
  4. {
  5. private readonly string _tempPath = Path.Combine(Path.GetTempPath(), $"envelope-designer-test-{Guid.NewGuid():N}.xml");
  6. public void Dispose()
  7. {
  8. if (File.Exists(_tempPath))
  9. {
  10. File.Delete(_tempPath);
  11. }
  12. }
  13. private static TemplateLayoutDocument BuildSampleDocument()
  14. {
  15. var document = new TemplateLayoutDocument(CanvasSettings.FromUnitValues(4.125, 9.5, CanvasUnit.Inches));
  16. var staticElement = TextElementLayout.CreateStatic(120, 240, "Static label:", "Arial", 12, zOrder: 0);
  17. staticElement.Color = new RgbColor(200, 10, 30);
  18. var dynamicElement = TextElementLayout.CreateDynamic(120, 225, "Full Name", "Arial [Bold]", 14, zOrder: 1);
  19. document.Elements.Add(staticElement);
  20. document.Elements.Add(dynamicElement);
  21. return document;
  22. }
  23. [Fact]
  24. public void SaveThenLoad_RoundTripsCanvasSettingsExactly()
  25. {
  26. var original = BuildSampleDocument();
  27. TemplateLayoutXmlSerializer.Save(original, _tempPath);
  28. var loaded = TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out var errors);
  29. Assert.True(loaded, string.Join("; ", errors));
  30. Assert.Equal(original.Canvas.WidthPoints, document!.Canvas.WidthPoints, precision: 6);
  31. Assert.Equal(original.Canvas.HeightPoints, document.Canvas.HeightPoints, precision: 6);
  32. Assert.Equal(original.Canvas.DisplayUnit, document.Canvas.DisplayUnit);
  33. }
  34. [Fact]
  35. public void SaveThenLoad_RoundTripsElementPositionsAndProperties()
  36. {
  37. var original = BuildSampleDocument();
  38. TemplateLayoutXmlSerializer.Save(original, _tempPath);
  39. TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out var errors);
  40. Assert.NotNull(document);
  41. Assert.Equal(2, document!.Elements.Count);
  42. var loadedStatic = document.Elements.Single(e => !e.IsDynamic);
  43. Assert.Equal(120, loadedStatic.X, precision: 6);
  44. Assert.Equal(240, loadedStatic.Y, precision: 6);
  45. Assert.Equal("Static label:", loadedStatic.StaticText);
  46. Assert.Equal("Arial", loadedStatic.FontFamily);
  47. Assert.Equal(12, loadedStatic.FontSize, precision: 6);
  48. Assert.Equal(new RgbColor(200, 10, 30), loadedStatic.Color);
  49. Assert.Equal(0, loadedStatic.ZOrder);
  50. var loadedDynamic = document.Elements.Single(e => e.IsDynamic);
  51. Assert.Equal("Full Name", loadedDynamic.ColumnName);
  52. Assert.Equal("Arial [Bold]", loadedDynamic.FontFamily);
  53. Assert.Equal(14, loadedDynamic.FontSize, precision: 6);
  54. Assert.Equal(1, loadedDynamic.ZOrder);
  55. }
  56. [Theory]
  57. // Sprint 3, Batch 3: the persisted `column` attribute must round-trip arbitrary, non-hardcoded
  58. // CSV header text exactly, including characters XML must escape (`&`, `'`, `"`) — proving the
  59. // designer doesn't just work for the couple of column names already used elsewhere as
  60. // examples (e.g. "Full Name").
  61. [InlineData("Zip+4 Code")]
  62. [InlineData("Bill To & Ship To's Address")]
  63. [InlineData("Customer_Reference-No.")]
  64. [InlineData("Quoted \"Nickname\" Field")]
  65. public void SaveThenLoad_RoundTripsArbitraryColumnNameExactly(string columnName)
  66. {
  67. var document = new TemplateLayoutDocument(CanvasSettings.CreateDefault());
  68. document.Elements.Add(TextElementLayout.CreateDynamic(10, 20, columnName));
  69. TemplateLayoutXmlSerializer.Save(document, _tempPath);
  70. var loaded = TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var reopened, out var errors);
  71. Assert.True(loaded, string.Join("; ", errors));
  72. Assert.Equal(columnName, reopened!.Elements.Single().ColumnName);
  73. }
  74. [Fact]
  75. public void Save_DynamicElement_PersistsColumnAttributeInRenderTimeFormat()
  76. {
  77. // Confirms the designer writes the exact attribute shape TEMPLATE_FORMAT.md/
  78. // TemplateXmlParser already consume at render time (`column="..."` on `<text>`), not a
  79. // different token syntax invented for the designer.
  80. var document = new TemplateLayoutDocument(CanvasSettings.CreateDefault());
  81. document.Elements.Add(TextElementLayout.CreateDynamic(10, 20, "Full Name"));
  82. TemplateLayoutXmlSerializer.Save(document, _tempPath);
  83. var xml = File.ReadAllText(_tempPath);
  84. Assert.Contains("column=\"Full Name\"", xml);
  85. }
  86. [Fact]
  87. public void Load_MissingFile_ReturnsFalseWithErrorNotException()
  88. {
  89. var result = TemplateLayoutXmlSerializer.TryLoad(
  90. Path.Combine(Path.GetTempPath(), $"does-not-exist-{Guid.NewGuid():N}.xml"), out var document, out var errors);
  91. Assert.False(result);
  92. Assert.Null(document);
  93. Assert.NotEmpty(errors);
  94. }
  95. [Fact]
  96. public void Load_MalformedXml_ReturnsFalseWithClearError()
  97. {
  98. File.WriteAllText(_tempPath, "<envelopeTemplate pageWidth=\"297\" pageHeight=\"684\"><text></envelopeTemplate>");
  99. var result = TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out var errors);
  100. Assert.False(result);
  101. Assert.Null(document);
  102. Assert.Contains(errors, e => e.Contains("not valid XML", StringComparison.OrdinalIgnoreCase));
  103. }
  104. [Fact]
  105. public void Load_WrongRootElement_ReturnsFalseWithClearError()
  106. {
  107. File.WriteAllText(_tempPath, "<notATemplate />");
  108. var result = TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out var errors);
  109. Assert.False(result);
  110. Assert.Null(document);
  111. Assert.Contains(errors, e => e.Contains("envelopeTemplate", StringComparison.Ordinal));
  112. }
  113. [Fact]
  114. public void Load_MissingRequiredPageDimension_ReturnsFalseWithClearError()
  115. {
  116. File.WriteAllText(_tempPath, "<envelopeTemplate pageHeight=\"684\"><text x=\"1\" y=\"1\" font=\"Arial\" size=\"12\">Hi</text></envelopeTemplate>");
  117. var result = TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out var errors);
  118. Assert.False(result);
  119. Assert.Contains(errors, e => e.Contains("pageWidth", StringComparison.Ordinal));
  120. }
  121. [Fact]
  122. public void Load_TextElementWithBothColumnAndInlineText_ReturnsFalseWithClearError()
  123. {
  124. File.WriteAllText(
  125. _tempPath,
  126. "<envelopeTemplate pageWidth=\"297\" pageHeight=\"684\">" +
  127. "<text x=\"1\" y=\"1\" font=\"Arial\" size=\"12\" column=\"Name\">Hi</text>" +
  128. "</envelopeTemplate>");
  129. var result = TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out var errors);
  130. Assert.False(result);
  131. Assert.Contains(errors, e => e.Contains("exactly one", StringComparison.OrdinalIgnoreCase));
  132. }
  133. [Fact]
  134. public void Load_InvalidColorValue_ReturnsFalseWithClearError()
  135. {
  136. File.WriteAllText(
  137. _tempPath,
  138. "<envelopeTemplate pageWidth=\"297\" pageHeight=\"684\">" +
  139. "<text x=\"1\" y=\"1\" font=\"Arial\" size=\"12\" color=\"notacolor\">Hi</text>" +
  140. "</envelopeTemplate>");
  141. var result = TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out var errors);
  142. Assert.False(result);
  143. Assert.Contains(errors, e => e.Contains("color", StringComparison.OrdinalIgnoreCase));
  144. }
  145. [Fact]
  146. public void Load_TemplateWithoutDesignerAttributes_LoadsWithDefaults()
  147. {
  148. // Matches Sprint 1's plain render-time template shape (no canvasUnit/color/zOrder) —
  149. // the designer must still be able to open it, per this story's back-compat intent.
  150. File.WriteAllText(
  151. _tempPath,
  152. "<envelopeTemplate pageWidth=\"297\" pageHeight=\"684\">" +
  153. "<text x=\"120\" y=\"240\" font=\"Arial\" size=\"12\">Static label:</text>" +
  154. "<text x=\"120\" y=\"225\" font=\"Arial\" size=\"12\" column=\"Full Name\" />" +
  155. "</envelopeTemplate>");
  156. var result = TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out var errors);
  157. Assert.True(result, string.Join("; ", errors));
  158. Assert.Equal(CanvasUnit.Points, document!.Canvas.DisplayUnit);
  159. Assert.Equal(2, document.Elements.Count);
  160. Assert.All(document.Elements, e => Assert.Equal(RgbColor.Black, e.Color));
  161. Assert.Equal(0, document.Elements[0].ZOrder);
  162. Assert.Equal(1, document.Elements[1].ZOrder);
  163. }
  164. [Fact]
  165. public void Load_NegativeZOrder_ClampsToZeroRatherThanErroring()
  166. {
  167. File.WriteAllText(
  168. _tempPath,
  169. "<envelopeTemplate pageWidth=\"297\" pageHeight=\"684\">" +
  170. "<text x=\"1\" y=\"1\" font=\"Arial\" size=\"12\" zOrder=\"-5\">Hi</text>" +
  171. "</envelopeTemplate>");
  172. var result = TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out var errors);
  173. Assert.True(result, string.Join("; ", errors));
  174. Assert.Equal(0, document!.Elements[0].ZOrder);
  175. }
  176. [Fact]
  177. public void Save_OverwritesExistingFile()
  178. {
  179. var first = new TemplateLayoutDocument(CanvasSettings.CreateDefault());
  180. first.Elements.Add(TextElementLayout.CreateStatic(0, 0, "first"));
  181. TemplateLayoutXmlSerializer.Save(first, _tempPath);
  182. var second = new TemplateLayoutDocument(CanvasSettings.CreateDefault());
  183. second.Elements.Add(TextElementLayout.CreateStatic(0, 0, "second"));
  184. TemplateLayoutXmlSerializer.Save(second, _tempPath);
  185. TemplateLayoutXmlSerializer.TryLoad(_tempPath, out var document, out _);
  186. Assert.Single(document!.Elements);
  187. Assert.Equal("second", document.Elements[0].StaticText);
  188. }
  189. }

Powered by TurnKey Linux.