pict-section-formeditor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/docs/.nojekyll +0 -0
  4. package/docs/README.md +162 -0
  5. package/docs/_sidebar.md +23 -0
  6. package/docs/_topbar.md +5 -0
  7. package/docs/cover.md +12 -0
  8. package/docs/css/docuserve.css +73 -0
  9. package/docs/index.html +39 -0
  10. package/docs/retold-catalog.json +224 -0
  11. package/docs/retold-keyword-index.json +46846 -0
  12. package/example_applications/form_editor/.quackage.json +10 -0
  13. package/example_applications/form_editor/FormEditor-Example-Application.js +226 -0
  14. package/example_applications/form_editor/html/icon-chooser.html +375 -0
  15. package/example_applications/form_editor/html/index.html +54 -0
  16. package/example_applications/form_editor/package.json +50 -0
  17. package/package.json +55 -0
  18. package/sample_manifests/Complex-Table.json +974 -0
  19. package/sample_manifests/Distill-Example.json +200 -0
  20. package/sample_manifests/Gradebook-Assignment.json +38 -0
  21. package/sample_manifests/Gradebook-Student.json +40 -0
  22. package/sample_manifests/Manyfest-Editor.json +347 -0
  23. package/sample_manifests/Simple-Form.json +232 -0
  24. package/sample_manifests/Simple-Table.json +79 -0
  25. package/source/Pict-Section-FormEditor-DefaultConfiguration.js +3321 -0
  26. package/source/Pict-Section-FormEditor.js +35 -0
  27. package/source/providers/Pict-Provider-ChildPictManager-Application.js +40 -0
  28. package/source/providers/Pict-Provider-ChildPictManager.js +238 -0
  29. package/source/providers/Pict-Provider-FormEditorDocumentation.js +356 -0
  30. package/source/providers/Pict-Provider-FormEditorDragDrop.js +535 -0
  31. package/source/providers/Pict-Provider-FormEditorIconography.js +1002 -0
  32. package/source/providers/Pict-Provider-FormEditorManifestOps.js +1443 -0
  33. package/source/providers/Pict-Provider-FormEditorRendering.js +730 -0
  34. package/source/providers/Pict-Provider-FormEditorUtilities.js +862 -0
  35. package/source/providers/Pict-Provider-PreviewCSS.js +42 -0
  36. package/source/views/PictView-FormEditor-InlineEditing.js +309 -0
  37. package/source/views/PictView-FormEditor-InputTypePicker.js +532 -0
  38. package/source/views/PictView-FormEditor-PropertiesPanel.js +7730 -0
  39. package/source/views/PictView-FormEditor.js +681 -0
  40. package/test/Pict-Section-FormEditor_tests.js +4102 -0
  41. package/user-documentation/.pict_documentation_topics.json +695 -0
  42. package/user-documentation/Getting-Started.md +32 -0
  43. package/user-documentation/Groups.md +52 -0
  44. package/user-documentation/Inputs.md +98 -0
  45. package/user-documentation/Sections.md +36 -0
  46. package/user-documentation/Shortcuts.md +44 -0
  47. package/user-documentation/Solver-Expression-Walkthrough.md +176 -0
  48. package/user-documentation/Solver-Expressions-Advanced.md +344 -0
  49. package/user-documentation/Solver-Functions.md +213 -0
  50. package/user-documentation/Solvers.md +81 -0
  51. package/user-documentation/ToC.md +18 -0
  52. package/user-documentation/solverfunctions/abs.md +84 -0
  53. package/user-documentation/solverfunctions/aggregationhistogram.md +83 -0
  54. package/user-documentation/solverfunctions/aggregationhistogrambyobject.md +64 -0
  55. package/user-documentation/solverfunctions/arrayconcat.md +64 -0
  56. package/user-documentation/solverfunctions/avg.md +81 -0
  57. package/user-documentation/solverfunctions/bucketset.md +69 -0
  58. package/user-documentation/solverfunctions/ceil.md +70 -0
  59. package/user-documentation/solverfunctions/cleanvaluearray.md +66 -0
  60. package/user-documentation/solverfunctions/cleanvalueobject.md +68 -0
  61. package/user-documentation/solverfunctions/colorgroupbackground.md +60 -0
  62. package/user-documentation/solverfunctions/colorinputbackground.md +62 -0
  63. package/user-documentation/solverfunctions/colorinputbackgroundtabular.md +64 -0
  64. package/user-documentation/solverfunctions/colorsectionbackground.md +59 -0
  65. package/user-documentation/solverfunctions/compare.md +72 -0
  66. package/user-documentation/solverfunctions/concat.md +73 -0
  67. package/user-documentation/solverfunctions/concatraw.md +73 -0
  68. package/user-documentation/solverfunctions/cos.md +75 -0
  69. package/user-documentation/solverfunctions/count.md +73 -0
  70. package/user-documentation/solverfunctions/countset.md +65 -0
  71. package/user-documentation/solverfunctions/countsetelements.md +63 -0
  72. package/user-documentation/solverfunctions/createarrayfromabsolutevalues.md +63 -0
  73. package/user-documentation/solverfunctions/createvalueobjectbyhashes.md +69 -0
  74. package/user-documentation/solverfunctions/cumulativesummation.md +96 -0
  75. package/user-documentation/solverfunctions/dateadddays.md +79 -0
  76. package/user-documentation/solverfunctions/dateaddhours.md +74 -0
  77. package/user-documentation/solverfunctions/dateaddmilliseconds.md +65 -0
  78. package/user-documentation/solverfunctions/dateaddminutes.md +72 -0
  79. package/user-documentation/solverfunctions/dateaddmonths.md +74 -0
  80. package/user-documentation/solverfunctions/dateaddseconds.md +66 -0
  81. package/user-documentation/solverfunctions/dateaddweeks.md +73 -0
  82. package/user-documentation/solverfunctions/dateaddyears.md +74 -0
  83. package/user-documentation/solverfunctions/datedaydifference.md +84 -0
  84. package/user-documentation/solverfunctions/datefromparts.md +81 -0
  85. package/user-documentation/solverfunctions/datehourdifference.md +64 -0
  86. package/user-documentation/solverfunctions/datemathadd.md +72 -0
  87. package/user-documentation/solverfunctions/datemilliseconddifference.md +64 -0
  88. package/user-documentation/solverfunctions/dateminutedifference.md +64 -0
  89. package/user-documentation/solverfunctions/datemonthdifference.md +66 -0
  90. package/user-documentation/solverfunctions/dateseconddifference.md +64 -0
  91. package/user-documentation/solverfunctions/dateweekdifference.md +65 -0
  92. package/user-documentation/solverfunctions/dateyeardifference.md +64 -0
  93. package/user-documentation/solverfunctions/differencearrays.md +59 -0
  94. package/user-documentation/solverfunctions/disablesolverordinal.md +58 -0
  95. package/user-documentation/solverfunctions/distributionhistogram.md +96 -0
  96. package/user-documentation/solverfunctions/distributionhistogrambyobject.md +64 -0
  97. package/user-documentation/solverfunctions/enablesolverordinal.md +57 -0
  98. package/user-documentation/solverfunctions/entryinset.md +72 -0
  99. package/user-documentation/solverfunctions/euler.md +77 -0
  100. package/user-documentation/solverfunctions/exp.md +74 -0
  101. package/user-documentation/solverfunctions/findfirstvaluebyexactmatch.md +67 -0
  102. package/user-documentation/solverfunctions/findfirstvaluebystringincludes.md +67 -0
  103. package/user-documentation/solverfunctions/flatten.md +76 -0
  104. package/user-documentation/solverfunctions/floor.md +70 -0
  105. package/user-documentation/solverfunctions/gaussianelimination.md +75 -0
  106. package/user-documentation/solverfunctions/generatearrayofobjectsfromsets.md +70 -0
  107. package/user-documentation/solverfunctions/generatehtmlhexcolor.md +67 -0
  108. package/user-documentation/solverfunctions/getvalue.md +90 -0
  109. package/user-documentation/solverfunctions/getvaluearray.md +64 -0
  110. package/user-documentation/solverfunctions/getvalueobject.md +67 -0
  111. package/user-documentation/solverfunctions/hidesections.md +58 -0
  112. package/user-documentation/solverfunctions/if.md +109 -0
  113. package/user-documentation/solverfunctions/iterativeseries.md +107 -0
  114. package/user-documentation/solverfunctions/join.md +75 -0
  115. package/user-documentation/solverfunctions/joinraw.md +64 -0
  116. package/user-documentation/solverfunctions/largestinset.md +63 -0
  117. package/user-documentation/solverfunctions/leastsquares.md +66 -0
  118. package/user-documentation/solverfunctions/linest.md +58 -0
  119. package/user-documentation/solverfunctions/log.md +74 -0
  120. package/user-documentation/solverfunctions/logvalues.md +65 -0
  121. package/user-documentation/solverfunctions/match.md +71 -0
  122. package/user-documentation/solverfunctions/matrixinverse.md +67 -0
  123. package/user-documentation/solverfunctions/matrixmultiply.md +71 -0
  124. package/user-documentation/solverfunctions/matrixtranspose.md +72 -0
  125. package/user-documentation/solverfunctions/matrixvectormultiply.md +69 -0
  126. package/user-documentation/solverfunctions/max.md +73 -0
  127. package/user-documentation/solverfunctions/mean.md +63 -0
  128. package/user-documentation/solverfunctions/median.md +79 -0
  129. package/user-documentation/solverfunctions/min.md +73 -0
  130. package/user-documentation/solverfunctions/mode.md +66 -0
  131. package/user-documentation/solverfunctions/objectkeystoarray.md +66 -0
  132. package/user-documentation/solverfunctions/objectvaluessortbyexternalobjectarray.md +65 -0
  133. package/user-documentation/solverfunctions/objectvaluestoarray.md +67 -0
  134. package/user-documentation/solverfunctions/percent.md +75 -0
  135. package/user-documentation/solverfunctions/pi.md +77 -0
  136. package/user-documentation/solverfunctions/polynomialregression.md +69 -0
  137. package/user-documentation/solverfunctions/predict.md +71 -0
  138. package/user-documentation/solverfunctions/rad.md +85 -0
  139. package/user-documentation/solverfunctions/randomfloat.md +63 -0
  140. package/user-documentation/solverfunctions/randomfloatbetween.md +72 -0
  141. package/user-documentation/solverfunctions/randomfloatupto.md +65 -0
  142. package/user-documentation/solverfunctions/randominteger.md +56 -0
  143. package/user-documentation/solverfunctions/randomintegerbetween.md +72 -0
  144. package/user-documentation/solverfunctions/randomintegerupto.md +64 -0
  145. package/user-documentation/solverfunctions/refreshtabularsection.md +57 -0
  146. package/user-documentation/solverfunctions/resolvehtmlentities.md +64 -0
  147. package/user-documentation/solverfunctions/round.md +111 -0
  148. package/user-documentation/solverfunctions/runsolvers.md +49 -0
  149. package/user-documentation/solverfunctions/setconcatenate.md +64 -0
  150. package/user-documentation/solverfunctions/setgroupvisibility.md +60 -0
  151. package/user-documentation/solverfunctions/setsectionvisibility.md +59 -0
  152. package/user-documentation/solverfunctions/setsolverordinalenabled.md +59 -0
  153. package/user-documentation/solverfunctions/settabularrowlength.md +57 -0
  154. package/user-documentation/solverfunctions/setvalue.md +65 -0
  155. package/user-documentation/solverfunctions/showsections.md +58 -0
  156. package/user-documentation/solverfunctions/sin.md +83 -0
  157. package/user-documentation/solverfunctions/slice.md +80 -0
  158. package/user-documentation/solverfunctions/smallestinset.md +63 -0
  159. package/user-documentation/solverfunctions/sortarray.md +58 -0
  160. package/user-documentation/solverfunctions/sorthistogram.md +70 -0
  161. package/user-documentation/solverfunctions/sorthistogrambykeys.md +69 -0
  162. package/user-documentation/solverfunctions/sortset.md +75 -0
  163. package/user-documentation/solverfunctions/sqrt.md +85 -0
  164. package/user-documentation/solverfunctions/stdev.md +81 -0
  165. package/user-documentation/solverfunctions/stdeva.md +58 -0
  166. package/user-documentation/solverfunctions/stdevp.md +83 -0
  167. package/user-documentation/solverfunctions/stringcountsegments.md +66 -0
  168. package/user-documentation/solverfunctions/stringgetsegments.md +74 -0
  169. package/user-documentation/solverfunctions/subtractingsummation.md +66 -0
  170. package/user-documentation/solverfunctions/sum.md +78 -0
  171. package/user-documentation/solverfunctions/tan.md +78 -0
  172. package/user-documentation/solverfunctions/tofixed.md +75 -0
  173. package/user-documentation/solverfunctions/unionarrays.md +59 -0
  174. package/user-documentation/solverfunctions/uniquearray.md +58 -0
  175. package/user-documentation/solverfunctions/var.md +67 -0
  176. package/user-documentation/solverfunctions/vara.md +58 -0
  177. package/user-documentation/solverfunctions/varp.md +66 -0
  178. package/user-documentation/solverfunctions/when.md +98 -0
@@ -0,0 +1,4102 @@
1
+ /**
2
+ * Unit tests for Pict Section FormEditor
3
+ *
4
+ * @license MIT
5
+ *
6
+ * @author Steven Velozo <steven@velozo.com>
7
+ */
8
+
9
+ var libPict = require('pict');
10
+
11
+ var Chai = require("chai");
12
+ var Expect = Chai.expect;
13
+
14
+ var libPictSectionFormEditor = require('../source/Pict-Section-FormEditor.js');
15
+
16
+ suite
17
+ (
18
+ 'Pict Section FormEditor',
19
+ function ()
20
+ {
21
+ suite
22
+ (
23
+ 'Module Exports',
24
+ function ()
25
+ {
26
+ test
27
+ (
28
+ 'Module should export the main view class',
29
+ function ()
30
+ {
31
+ Expect(libPictSectionFormEditor).to.be.a('function');
32
+ }
33
+ );
34
+ test
35
+ (
36
+ 'Module should export default configuration',
37
+ function ()
38
+ {
39
+ Expect(libPictSectionFormEditor.default_configuration).to.be.an('object');
40
+ Expect(libPictSectionFormEditor.default_configuration.ViewIdentifier).to.equal('Pict-FormEditor');
41
+ Expect(libPictSectionFormEditor.default_configuration.CSS).to.be.a('string');
42
+ Expect(libPictSectionFormEditor.default_configuration.Templates).to.be.an('array');
43
+ Expect(libPictSectionFormEditor.default_configuration.Renderables).to.be.an('array');
44
+ }
45
+ );
46
+ }
47
+ );
48
+ suite
49
+ (
50
+ 'View Instantiation',
51
+ function ()
52
+ {
53
+ test
54
+ (
55
+ 'Should instantiate a FormEditor view',
56
+ function ()
57
+ {
58
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
59
+ let tmpView = tmpPict.addView('TestFormEditor',
60
+ {
61
+ ViewIdentifier: 'TestFormEditor',
62
+ ManifestDataAddress: 'AppData.FormConfig'
63
+ }, libPictSectionFormEditor);
64
+
65
+ Expect(tmpView).to.be.an('object');
66
+ Expect(tmpView.options.ViewIdentifier).to.equal('TestFormEditor');
67
+ Expect(tmpView.options.ManifestDataAddress).to.equal('AppData.FormConfig');
68
+ }
69
+ );
70
+ test
71
+ (
72
+ 'Should use default ManifestDataAddress when none provided',
73
+ function ()
74
+ {
75
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
76
+ let tmpView = tmpPict.addView('TestFormEditorDefault',
77
+ {
78
+ ViewIdentifier: 'TestFormEditorDefault'
79
+ }, libPictSectionFormEditor);
80
+
81
+ Expect(tmpView.options.ManifestDataAddress).to.equal('FormEditor.Manifest');
82
+ }
83
+ );
84
+ test
85
+ (
86
+ 'Should initialize with an empty manifest when no data exists',
87
+ function ()
88
+ {
89
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
90
+ tmpPict.AppData = {};
91
+
92
+ let tmpView = tmpPict.addView('TestFormEditorInit',
93
+ {
94
+ ViewIdentifier: 'TestFormEditorInit',
95
+ ManifestDataAddress: 'AppData.FormConfig'
96
+ }, libPictSectionFormEditor);
97
+
98
+ tmpView.initialize();
99
+
100
+ Expect(tmpPict.AppData.FormConfig).to.be.an('object');
101
+ Expect(tmpPict.AppData.FormConfig.Scope).to.equal('NewForm');
102
+ Expect(tmpPict.AppData.FormConfig.Sections).to.be.an('array');
103
+ Expect(tmpPict.AppData.FormConfig.Descriptors).to.be.an('object');
104
+ }
105
+ );
106
+ test
107
+ (
108
+ 'Should preserve existing manifest data on initialize',
109
+ function ()
110
+ {
111
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
112
+ tmpPict.AppData = {};
113
+ tmpPict.AppData.FormConfig =
114
+ {
115
+ Scope: 'ExistingForm',
116
+ Sections:
117
+ [
118
+ {
119
+ Hash: 'Section1',
120
+ Name: 'My Section',
121
+ Groups: []
122
+ }
123
+ ],
124
+ Descriptors: {}
125
+ };
126
+
127
+ let tmpView = tmpPict.addView('TestFormEditorPreserve',
128
+ {
129
+ ViewIdentifier: 'TestFormEditorPreserve',
130
+ ManifestDataAddress: 'AppData.FormConfig'
131
+ }, libPictSectionFormEditor);
132
+
133
+ tmpView.initialize();
134
+
135
+ Expect(tmpPict.AppData.FormConfig.Scope).to.equal('ExistingForm');
136
+ Expect(tmpPict.AppData.FormConfig.Sections.length).to.equal(1);
137
+ Expect(tmpPict.AppData.FormConfig.Sections[0].Name).to.equal('My Section');
138
+ }
139
+ );
140
+ test
141
+ (
142
+ 'Should create a code editor child view on initialize',
143
+ function ()
144
+ {
145
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
146
+ tmpPict.AppData = {};
147
+
148
+ let tmpView = tmpPict.addView('TestCodeEditorView',
149
+ {
150
+ ViewIdentifier: 'TestCodeEditorView',
151
+ ManifestDataAddress: 'AppData.FormConfig'
152
+ }, libPictSectionFormEditor);
153
+
154
+ tmpView.initialize();
155
+
156
+ Expect(tmpView._CodeEditorView).to.be.an('object');
157
+ Expect(tmpView._CodeEditorView.options.Language).to.equal('json');
158
+ Expect(tmpView._CodeEditorView.options.LineNumbers).to.equal(true);
159
+ Expect(tmpView._CodeEditorView.options.ReadOnly).to.equal(false);
160
+ }
161
+ );
162
+ test
163
+ (
164
+ 'Should update code editor content via _updateCodeEditor',
165
+ function ()
166
+ {
167
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
168
+ tmpPict.AppData = {};
169
+
170
+ let tmpView = tmpPict.addView('TestUpdateCodeEditor',
171
+ {
172
+ ViewIdentifier: 'TestUpdateCodeEditor',
173
+ ManifestDataAddress: 'AppData.FormConfig'
174
+ }, libPictSectionFormEditor);
175
+
176
+ tmpView.initialize();
177
+
178
+ // Without a DOM, codeJar won't be initialized,
179
+ // so _updateCodeEditor should not throw
180
+ tmpView._UtilitiesProvider._updateCodeEditor();
181
+ }
182
+ );
183
+ }
184
+ );
185
+ suite
186
+ (
187
+ 'Section Operations',
188
+ function ()
189
+ {
190
+ test
191
+ (
192
+ 'Should add a section',
193
+ function ()
194
+ {
195
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
196
+ tmpPict.AppData = {};
197
+
198
+ let tmpView = tmpPict.addView('TestAddSection',
199
+ {
200
+ ViewIdentifier: 'TestAddSection',
201
+ ManifestDataAddress: 'AppData.FormConfig'
202
+ }, libPictSectionFormEditor);
203
+
204
+ tmpView.initialize();
205
+
206
+ tmpView._ManifestOpsProvider.addSection();
207
+ let tmpManifest = tmpPict.AppData.FormConfig;
208
+ Expect(tmpManifest.Sections.length).to.equal(1);
209
+ Expect(tmpManifest.Sections[0].Hash).to.equal('S1');
210
+ Expect(tmpManifest.Sections[0].Name).to.equal('Section 1');
211
+ Expect(tmpManifest.Sections[0].Groups).to.be.an('array');
212
+
213
+ tmpView._ManifestOpsProvider.addSection();
214
+ Expect(tmpManifest.Sections.length).to.equal(2);
215
+ Expect(tmpManifest.Sections[1].Hash).to.equal('S2');
216
+ }
217
+ );
218
+ test
219
+ (
220
+ 'Should remove a section',
221
+ function ()
222
+ {
223
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
224
+ tmpPict.AppData = {};
225
+
226
+ let tmpView = tmpPict.addView('TestRemoveSection',
227
+ {
228
+ ViewIdentifier: 'TestRemoveSection',
229
+ ManifestDataAddress: 'AppData.FormConfig'
230
+ }, libPictSectionFormEditor);
231
+
232
+ tmpView.initialize();
233
+ tmpView._ManifestOpsProvider.addSection();
234
+ tmpView._ManifestOpsProvider.addSection();
235
+ tmpView._ManifestOpsProvider.addSection();
236
+
237
+ Expect(tmpPict.AppData.FormConfig.Sections.length).to.equal(3);
238
+
239
+ tmpView._ManifestOpsProvider.removeSection(1);
240
+ Expect(tmpPict.AppData.FormConfig.Sections.length).to.equal(2);
241
+ Expect(tmpPict.AppData.FormConfig.Sections[0].Hash).to.equal('S1');
242
+ Expect(tmpPict.AppData.FormConfig.Sections[1].Hash).to.equal('S3');
243
+ }
244
+ );
245
+ test
246
+ (
247
+ 'Should move sections up and down',
248
+ function ()
249
+ {
250
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
251
+ tmpPict.AppData = {};
252
+
253
+ let tmpView = tmpPict.addView('TestMoveSection',
254
+ {
255
+ ViewIdentifier: 'TestMoveSection',
256
+ ManifestDataAddress: 'AppData.FormConfig'
257
+ }, libPictSectionFormEditor);
258
+
259
+ tmpView.initialize();
260
+ tmpView._ManifestOpsProvider.addSection();
261
+ tmpView._ManifestOpsProvider.addSection();
262
+ tmpView._ManifestOpsProvider.addSection();
263
+
264
+ // Move section at index 2 up
265
+ tmpView._ManifestOpsProvider.moveSectionUp(2);
266
+ Expect(tmpPict.AppData.FormConfig.Sections[1].Hash).to.equal('S3');
267
+ Expect(tmpPict.AppData.FormConfig.Sections[2].Hash).to.equal('S2');
268
+
269
+ // Move section at index 0 down
270
+ tmpView._ManifestOpsProvider.moveSectionDown(0);
271
+ Expect(tmpPict.AppData.FormConfig.Sections[0].Hash).to.equal('S3');
272
+ Expect(tmpPict.AppData.FormConfig.Sections[1].Hash).to.equal('S1');
273
+ }
274
+ );
275
+ test
276
+ (
277
+ 'Should update section properties',
278
+ function ()
279
+ {
280
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
281
+ tmpPict.AppData = {};
282
+
283
+ let tmpView = tmpPict.addView('TestUpdateSection',
284
+ {
285
+ ViewIdentifier: 'TestUpdateSection',
286
+ ManifestDataAddress: 'AppData.FormConfig'
287
+ }, libPictSectionFormEditor);
288
+
289
+ tmpView.initialize();
290
+ tmpView._ManifestOpsProvider.addSection();
291
+
292
+ tmpView._ManifestOpsProvider.updateSectionProperty(0, 'Name', 'Customer Info');
293
+ tmpView._ManifestOpsProvider.updateSectionProperty(0, 'Hash', 'CustomerInfo');
294
+
295
+ Expect(tmpPict.AppData.FormConfig.Sections[0].Name).to.equal('Customer Info');
296
+ Expect(tmpPict.AppData.FormConfig.Sections[0].Hash).to.equal('CustomerInfo');
297
+ }
298
+ );
299
+ test
300
+ (
301
+ 'Should handle boundary conditions for section operations',
302
+ function ()
303
+ {
304
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
305
+ tmpPict.AppData = {};
306
+
307
+ let tmpView = tmpPict.addView('TestSectionBounds',
308
+ {
309
+ ViewIdentifier: 'TestSectionBounds',
310
+ ManifestDataAddress: 'AppData.FormConfig'
311
+ }, libPictSectionFormEditor);
312
+
313
+ tmpView.initialize();
314
+ tmpView._ManifestOpsProvider.addSection();
315
+
316
+ // Moving up at index 0 should be a no-op
317
+ tmpView._ManifestOpsProvider.moveSectionUp(0);
318
+ Expect(tmpPict.AppData.FormConfig.Sections[0].Hash).to.equal('S1');
319
+
320
+ // Moving down at last index should be a no-op
321
+ tmpView._ManifestOpsProvider.moveSectionDown(0);
322
+ Expect(tmpPict.AppData.FormConfig.Sections[0].Hash).to.equal('S1');
323
+
324
+ // Removing at invalid index should be a no-op
325
+ tmpView._ManifestOpsProvider.removeSection(5);
326
+ Expect(tmpPict.AppData.FormConfig.Sections.length).to.equal(1);
327
+
328
+ tmpView._ManifestOpsProvider.removeSection(-1);
329
+ Expect(tmpPict.AppData.FormConfig.Sections.length).to.equal(1);
330
+ }
331
+ );
332
+ }
333
+ );
334
+ suite
335
+ (
336
+ 'Group Operations',
337
+ function ()
338
+ {
339
+ test
340
+ (
341
+ 'Should add a group to a section',
342
+ function ()
343
+ {
344
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
345
+ tmpPict.AppData = {};
346
+
347
+ let tmpView = tmpPict.addView('TestAddGroup',
348
+ {
349
+ ViewIdentifier: 'TestAddGroup',
350
+ ManifestDataAddress: 'AppData.FormConfig'
351
+ }, libPictSectionFormEditor);
352
+
353
+ tmpView.initialize();
354
+ tmpView._ManifestOpsProvider.addSection();
355
+
356
+ // addSection creates a default group, so we already have one
357
+ let tmpSection = tmpPict.AppData.FormConfig.Sections[0];
358
+ Expect(tmpSection.Groups.length).to.equal(1);
359
+ Expect(tmpSection.Groups[0].Hash).to.equal('S1_G1');
360
+ Expect(tmpSection.Groups[0].Layout).to.equal('Record');
361
+
362
+ tmpView._ManifestOpsProvider.addGroup(0);
363
+ Expect(tmpSection.Groups.length).to.equal(2);
364
+ Expect(tmpSection.Groups[1].Hash).to.equal('S1_G2');
365
+ }
366
+ );
367
+ test
368
+ (
369
+ 'Should remove a group from a section',
370
+ function ()
371
+ {
372
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
373
+ tmpPict.AppData = {};
374
+
375
+ let tmpView = tmpPict.addView('TestRemoveGroup',
376
+ {
377
+ ViewIdentifier: 'TestRemoveGroup',
378
+ ManifestDataAddress: 'AppData.FormConfig'
379
+ }, libPictSectionFormEditor);
380
+
381
+ tmpView.initialize();
382
+ tmpView._ManifestOpsProvider.addSection();
383
+ // addSection creates a default group; add two more
384
+ tmpView._ManifestOpsProvider.addGroup(0);
385
+ tmpView._ManifestOpsProvider.addGroup(0);
386
+
387
+ let tmpSection = tmpPict.AppData.FormConfig.Sections[0];
388
+ Expect(tmpSection.Groups.length).to.equal(3);
389
+
390
+ tmpView._ManifestOpsProvider.removeGroup(0, 1);
391
+ Expect(tmpSection.Groups.length).to.equal(2);
392
+ Expect(tmpSection.Groups[0].Hash).to.equal('S1_G1');
393
+ Expect(tmpSection.Groups[1].Hash).to.equal('S1_G3');
394
+ }
395
+ );
396
+ test
397
+ (
398
+ 'Should move groups up and down',
399
+ function ()
400
+ {
401
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
402
+ tmpPict.AppData = {};
403
+
404
+ let tmpView = tmpPict.addView('TestMoveGroup',
405
+ {
406
+ ViewIdentifier: 'TestMoveGroup',
407
+ ManifestDataAddress: 'AppData.FormConfig'
408
+ }, libPictSectionFormEditor);
409
+
410
+ tmpView.initialize();
411
+ tmpView._ManifestOpsProvider.addSection();
412
+ // addSection creates a default group; add two more
413
+ tmpView._ManifestOpsProvider.addGroup(0);
414
+ tmpView._ManifestOpsProvider.addGroup(0);
415
+
416
+ let tmpSection = tmpPict.AppData.FormConfig.Sections[0];
417
+
418
+ tmpView._ManifestOpsProvider.moveGroupUp(0, 2);
419
+ Expect(tmpSection.Groups[1].Hash).to.equal('S1_G3');
420
+ Expect(tmpSection.Groups[2].Hash).to.equal('S1_G2');
421
+
422
+ tmpView._ManifestOpsProvider.moveGroupDown(0, 0);
423
+ Expect(tmpSection.Groups[0].Hash).to.equal('S1_G3');
424
+ Expect(tmpSection.Groups[1].Hash).to.equal('S1_G1');
425
+ }
426
+ );
427
+ test
428
+ (
429
+ 'Should update group properties',
430
+ function ()
431
+ {
432
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
433
+ tmpPict.AppData = {};
434
+
435
+ let tmpView = tmpPict.addView('TestUpdateGroup',
436
+ {
437
+ ViewIdentifier: 'TestUpdateGroup',
438
+ ManifestDataAddress: 'AppData.FormConfig'
439
+ }, libPictSectionFormEditor);
440
+
441
+ tmpView.initialize();
442
+ tmpView._ManifestOpsProvider.addSection();
443
+ tmpView._ManifestOpsProvider.addGroup(0);
444
+
445
+ tmpView._ManifestOpsProvider.updateGroupProperty(0, 0, 'Name', 'Address Fields');
446
+ tmpView._ManifestOpsProvider.updateGroupProperty(0, 0, 'Hash', 'AddressFields');
447
+ tmpView._ManifestOpsProvider.updateGroupProperty(0, 0, 'Layout', 'Tabular');
448
+
449
+ let tmpGroup = tmpPict.AppData.FormConfig.Sections[0].Groups[0];
450
+ Expect(tmpGroup.Name).to.equal('Address Fields');
451
+ Expect(tmpGroup.Hash).to.equal('AddressFields');
452
+ Expect(tmpGroup.Layout).to.equal('Tabular');
453
+ }
454
+ );
455
+ }
456
+ );
457
+ suite
458
+ (
459
+ 'Sanitize Object Key',
460
+ function ()
461
+ {
462
+ test
463
+ (
464
+ 'Should sanitize strings into valid object keys',
465
+ function ()
466
+ {
467
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
468
+ let tmpView = tmpPict.addView('TestSanitize',
469
+ {
470
+ ViewIdentifier: 'TestSanitize',
471
+ ManifestDataAddress: 'AppData.FormConfig'
472
+ }, libPictSectionFormEditor);
473
+
474
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey('Section 1')).to.equal('Section_1');
475
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey('Hello World')).to.equal('Hello_World');
476
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey('foo--bar baz')).to.equal('foo_bar_baz');
477
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey(' leading spaces ')).to.equal('leading_spaces');
478
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey('AlreadyClean')).to.equal('AlreadyClean');
479
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey('with_underscores')).to.equal('with_underscores');
480
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey('')).to.equal('INVALID');
481
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey(null)).to.equal('INVALID');
482
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey(undefined)).to.equal('INVALID');
483
+ Expect(tmpView._UtilitiesProvider.sanitizeObjectKey(42)).to.equal('INVALID');
484
+ }
485
+ );
486
+ test
487
+ (
488
+ 'Should cascade section hash changes to auto-generated group hashes',
489
+ function ()
490
+ {
491
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
492
+ tmpPict.AppData = {};
493
+
494
+ let tmpView = tmpPict.addView('TestCascade',
495
+ {
496
+ ViewIdentifier: 'TestCascade',
497
+ ManifestDataAddress: 'AppData.FormConfig'
498
+ }, libPictSectionFormEditor);
499
+
500
+ tmpView.initialize();
501
+ tmpView._ManifestOpsProvider.addSection();
502
+ tmpView._ManifestOpsProvider.addGroup(0);
503
+
504
+ let tmpSection = tmpPict.AppData.FormConfig.Sections[0];
505
+ Expect(tmpSection.Groups[0].Hash).to.equal('S1_G1');
506
+ Expect(tmpSection.Groups[1].Hash).to.equal('S1_G2');
507
+
508
+ // Rename the section hash; groups should cascade
509
+ tmpView._ManifestOpsProvider.updateSectionProperty(0, 'Hash', 'CustomerInfo');
510
+ Expect(tmpSection.Groups[0].Hash).to.equal('CustomerInfo_G1');
511
+ Expect(tmpSection.Groups[1].Hash).to.equal('CustomerInfo_G2');
512
+ }
513
+ );
514
+ test
515
+ (
516
+ 'Should not cascade to manually customized group hashes',
517
+ function ()
518
+ {
519
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
520
+ tmpPict.AppData = {};
521
+
522
+ let tmpView = tmpPict.addView('TestNoCascade',
523
+ {
524
+ ViewIdentifier: 'TestNoCascade',
525
+ ManifestDataAddress: 'AppData.FormConfig'
526
+ }, libPictSectionFormEditor);
527
+
528
+ tmpView.initialize();
529
+ tmpView._ManifestOpsProvider.addSection();
530
+ tmpView._ManifestOpsProvider.addGroup(0);
531
+
532
+ let tmpSection = tmpPict.AppData.FormConfig.Sections[0];
533
+
534
+ // Manually customize the second group's hash (no _G prefix)
535
+ tmpSection.Groups[1].Hash = 'MyCustomHash';
536
+
537
+ // Rename the section hash; only auto-generated groups
538
+ // (those with the _G prefix) should cascade
539
+ tmpView._ManifestOpsProvider.updateSectionProperty(0, 'Hash', 'CustomerInfo');
540
+ Expect(tmpSection.Groups[0].Hash).to.equal('CustomerInfo_G1');
541
+ Expect(tmpSection.Groups[1].Hash).to.equal('MyCustomHash');
542
+ }
543
+ );
544
+ test
545
+ (
546
+ 'Should cascade past a customized middle group',
547
+ function ()
548
+ {
549
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
550
+ tmpPict.AppData = {};
551
+
552
+ let tmpView = tmpPict.addView('TestCascadePast',
553
+ {
554
+ ViewIdentifier: 'TestCascadePast',
555
+ ManifestDataAddress: 'AppData.FormConfig'
556
+ }, libPictSectionFormEditor);
557
+
558
+ tmpView.initialize();
559
+ tmpView._ManifestOpsProvider.addSection();
560
+ tmpView._ManifestOpsProvider.addGroup(0);
561
+ tmpView._ManifestOpsProvider.addGroup(0);
562
+
563
+ let tmpSection = tmpPict.AppData.FormConfig.Sections[0];
564
+ Expect(tmpSection.Groups.length).to.equal(3);
565
+
566
+ // Customize the middle group's hash
567
+ tmpSection.Groups[1].Hash = 'CustomMiddle';
568
+
569
+ // Change section hash; groups 0 and 2 should update, group 1 stays
570
+ tmpView._ManifestOpsProvider.updateSectionProperty(0, 'Hash', 'NewSection');
571
+ Expect(tmpSection.Groups[0].Hash).to.equal('NewSection_G1');
572
+ Expect(tmpSection.Groups[1].Hash).to.equal('CustomMiddle');
573
+ Expect(tmpSection.Groups[2].Hash).to.equal('NewSection_G3');
574
+ }
575
+ );
576
+ }
577
+ );
578
+ suite
579
+ (
580
+ 'Row Operations',
581
+ function ()
582
+ {
583
+ test
584
+ (
585
+ 'Should add a row to a group',
586
+ function ()
587
+ {
588
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
589
+ tmpPict.AppData = {};
590
+
591
+ let tmpView = tmpPict.addView('TestAddRow',
592
+ {
593
+ ViewIdentifier: 'TestAddRow',
594
+ ManifestDataAddress: 'AppData.FormConfig'
595
+ }, libPictSectionFormEditor);
596
+
597
+ tmpView.initialize();
598
+ tmpView._ManifestOpsProvider.addSection();
599
+
600
+ let tmpGroup = tmpPict.AppData.FormConfig.Sections[0].Groups[0];
601
+ Expect(tmpGroup.Rows).to.be.undefined;
602
+
603
+ tmpView._ManifestOpsProvider.addRow(0, 0);
604
+ Expect(tmpGroup.Rows).to.be.an('array');
605
+ Expect(tmpGroup.Rows.length).to.equal(1);
606
+ Expect(tmpGroup.Rows[0].Inputs).to.be.an('array');
607
+ Expect(tmpGroup.Rows[0].Inputs.length).to.equal(0);
608
+
609
+ tmpView._ManifestOpsProvider.addRow(0, 0);
610
+ Expect(tmpGroup.Rows.length).to.equal(2);
611
+ }
612
+ );
613
+ test
614
+ (
615
+ 'Should remove a row from a group',
616
+ function ()
617
+ {
618
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
619
+ tmpPict.AppData = {};
620
+
621
+ let tmpView = tmpPict.addView('TestRemoveRow',
622
+ {
623
+ ViewIdentifier: 'TestRemoveRow',
624
+ ManifestDataAddress: 'AppData.FormConfig'
625
+ }, libPictSectionFormEditor);
626
+
627
+ tmpView.initialize();
628
+ tmpView._ManifestOpsProvider.addSection();
629
+ tmpView._ManifestOpsProvider.addRow(0, 0);
630
+ tmpView._ManifestOpsProvider.addRow(0, 0);
631
+ tmpView._ManifestOpsProvider.addRow(0, 0);
632
+
633
+ let tmpGroup = tmpPict.AppData.FormConfig.Sections[0].Groups[0];
634
+ Expect(tmpGroup.Rows.length).to.equal(3);
635
+
636
+ tmpView._ManifestOpsProvider.removeRow(0, 0, 1);
637
+ Expect(tmpGroup.Rows.length).to.equal(2);
638
+ }
639
+ );
640
+ test
641
+ (
642
+ 'Should move rows up and down',
643
+ function ()
644
+ {
645
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
646
+ tmpPict.AppData = {};
647
+
648
+ let tmpView = tmpPict.addView('TestMoveRow',
649
+ {
650
+ ViewIdentifier: 'TestMoveRow',
651
+ ManifestDataAddress: 'AppData.FormConfig'
652
+ }, libPictSectionFormEditor);
653
+
654
+ tmpView.initialize();
655
+ tmpView._ManifestOpsProvider.addSection();
656
+ tmpView._ManifestOpsProvider.addRow(0, 0);
657
+ tmpView._ManifestOpsProvider.addRow(0, 0);
658
+ tmpView._ManifestOpsProvider.addRow(0, 0);
659
+
660
+ let tmpGroup = tmpPict.AppData.FormConfig.Sections[0].Groups[0];
661
+
662
+ // Tag each row so we can track reordering
663
+ tmpGroup.Rows[0]._tag = 'A';
664
+ tmpGroup.Rows[1]._tag = 'B';
665
+ tmpGroup.Rows[2]._tag = 'C';
666
+
667
+ tmpView._ManifestOpsProvider.moveRowUp(0, 0, 2);
668
+ Expect(tmpGroup.Rows[1]._tag).to.equal('C');
669
+ Expect(tmpGroup.Rows[2]._tag).to.equal('B');
670
+
671
+ tmpView._ManifestOpsProvider.moveRowDown(0, 0, 0);
672
+ Expect(tmpGroup.Rows[0]._tag).to.equal('C');
673
+ Expect(tmpGroup.Rows[1]._tag).to.equal('A');
674
+ }
675
+ );
676
+ }
677
+ );
678
+ suite
679
+ (
680
+ 'Input Operations',
681
+ function ()
682
+ {
683
+ test
684
+ (
685
+ 'Should add an input to a row and create a Descriptor',
686
+ function ()
687
+ {
688
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
689
+ tmpPict.AppData = {};
690
+
691
+ let tmpView = tmpPict.addView('TestAddInput',
692
+ {
693
+ ViewIdentifier: 'TestAddInput',
694
+ ManifestDataAddress: 'AppData.FormConfig'
695
+ }, libPictSectionFormEditor);
696
+
697
+ tmpView.initialize();
698
+ tmpView._ManifestOpsProvider.addSection();
699
+ tmpView._ManifestOpsProvider.addRow(0, 0);
700
+
701
+ let tmpManifest = tmpPict.AppData.FormConfig;
702
+ let tmpRow = tmpManifest.Sections[0].Groups[0].Rows[0];
703
+ Expect(tmpRow.Inputs.length).to.equal(0);
704
+
705
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
706
+ Expect(tmpRow.Inputs.length).to.equal(1);
707
+
708
+ // Inputs are now address strings referencing Descriptors
709
+ let tmpAddress1 = tmpRow.Inputs[0];
710
+ Expect(typeof tmpAddress1).to.equal('string');
711
+ Expect(tmpManifest.Descriptors).to.have.property(tmpAddress1);
712
+
713
+ let tmpDescriptor1 = tmpManifest.Descriptors[tmpAddress1];
714
+ Expect(tmpDescriptor1.DataType).to.equal('String');
715
+ Expect(tmpDescriptor1.Name).to.be.a('string');
716
+ Expect(tmpDescriptor1.Hash).to.be.a('string');
717
+ Expect(tmpDescriptor1.PictForm).to.be.an('object');
718
+ Expect(tmpDescriptor1.PictForm.Section).to.equal('S1');
719
+ Expect(tmpDescriptor1.PictForm.Group).to.equal('S1_G1');
720
+ Expect(tmpDescriptor1.PictForm.Row).to.equal(1);
721
+
722
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
723
+ Expect(tmpRow.Inputs.length).to.equal(2);
724
+ let tmpAddress2 = tmpRow.Inputs[1];
725
+ Expect(tmpManifest.Descriptors).to.have.property(tmpAddress2);
726
+ Expect(tmpAddress1).to.not.equal(tmpAddress2);
727
+ }
728
+ );
729
+ test
730
+ (
731
+ 'Should remove an input and its Descriptor',
732
+ function ()
733
+ {
734
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
735
+ tmpPict.AppData = {};
736
+
737
+ let tmpView = tmpPict.addView('TestRemoveInput',
738
+ {
739
+ ViewIdentifier: 'TestRemoveInput',
740
+ ManifestDataAddress: 'AppData.FormConfig'
741
+ }, libPictSectionFormEditor);
742
+
743
+ tmpView.initialize();
744
+ tmpView._ManifestOpsProvider.addSection();
745
+ tmpView._ManifestOpsProvider.addRow(0, 0);
746
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
747
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
748
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
749
+
750
+ let tmpManifest = tmpPict.AppData.FormConfig;
751
+ let tmpRow = tmpManifest.Sections[0].Groups[0].Rows[0];
752
+ Expect(tmpRow.Inputs.length).to.equal(3);
753
+
754
+ let tmpAddress1 = tmpRow.Inputs[0];
755
+ let tmpAddress2 = tmpRow.Inputs[1];
756
+ let tmpAddress3 = tmpRow.Inputs[2];
757
+
758
+ // Remove the middle input
759
+ tmpView._ManifestOpsProvider.removeInput(0, 0, 0, 1);
760
+ Expect(tmpRow.Inputs.length).to.equal(2);
761
+ Expect(tmpRow.Inputs[0]).to.equal(tmpAddress1);
762
+ Expect(tmpRow.Inputs[1]).to.equal(tmpAddress3);
763
+
764
+ // The removed Descriptor should be gone
765
+ Expect(tmpManifest.Descriptors).to.not.have.property(tmpAddress2);
766
+ // The remaining Descriptors should still exist
767
+ Expect(tmpManifest.Descriptors).to.have.property(tmpAddress1);
768
+ Expect(tmpManifest.Descriptors).to.have.property(tmpAddress3);
769
+ }
770
+ );
771
+ test
772
+ (
773
+ 'Should remove Descriptors when removing a row',
774
+ function ()
775
+ {
776
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
777
+ tmpPict.AppData = {};
778
+
779
+ let tmpView = tmpPict.addView('TestRemoveRowDescriptors',
780
+ {
781
+ ViewIdentifier: 'TestRemoveRowDescriptors',
782
+ ManifestDataAddress: 'AppData.FormConfig'
783
+ }, libPictSectionFormEditor);
784
+
785
+ tmpView.initialize();
786
+ tmpView._ManifestOpsProvider.addSection();
787
+ tmpView._ManifestOpsProvider.addRow(0, 0);
788
+ tmpView._ManifestOpsProvider.addRow(0, 0);
789
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
790
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
791
+ tmpView._ManifestOpsProvider.addInput(0, 0, 1);
792
+
793
+ let tmpManifest = tmpPict.AppData.FormConfig;
794
+ let tmpGroup = tmpManifest.Sections[0].Groups[0];
795
+ let tmpRow1Address1 = tmpGroup.Rows[0].Inputs[0];
796
+ let tmpRow1Address2 = tmpGroup.Rows[0].Inputs[1];
797
+ let tmpRow2Address1 = tmpGroup.Rows[1].Inputs[0];
798
+
799
+ Expect(Object.keys(tmpManifest.Descriptors).length).to.equal(3);
800
+
801
+ // Remove row 0 — its two descriptors should be deleted
802
+ tmpView._ManifestOpsProvider.removeRow(0, 0, 0);
803
+ Expect(tmpManifest.Descriptors).to.not.have.property(tmpRow1Address1);
804
+ Expect(tmpManifest.Descriptors).to.not.have.property(tmpRow1Address2);
805
+ Expect(tmpManifest.Descriptors).to.have.property(tmpRow2Address1);
806
+ Expect(Object.keys(tmpManifest.Descriptors).length).to.equal(1);
807
+
808
+ // Row 2 is now Row 1; its Descriptor PictForm.Row should be updated
809
+ Expect(tmpManifest.Descriptors[tmpRow2Address1].PictForm.Row).to.equal(1);
810
+ }
811
+ );
812
+ test
813
+ (
814
+ 'Should remove Descriptors when removing a group',
815
+ function ()
816
+ {
817
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
818
+ tmpPict.AppData = {};
819
+
820
+ let tmpView = tmpPict.addView('TestRemoveGroupDescriptors',
821
+ {
822
+ ViewIdentifier: 'TestRemoveGroupDescriptors',
823
+ ManifestDataAddress: 'AppData.FormConfig'
824
+ }, libPictSectionFormEditor);
825
+
826
+ tmpView.initialize();
827
+ tmpView._ManifestOpsProvider.addSection();
828
+ tmpView._ManifestOpsProvider.addGroup(0);
829
+ tmpView._ManifestOpsProvider.addRow(0, 0);
830
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
831
+ tmpView._ManifestOpsProvider.addRow(0, 1);
832
+ tmpView._ManifestOpsProvider.addInput(0, 1, 0);
833
+
834
+ let tmpManifest = tmpPict.AppData.FormConfig;
835
+ let tmpGroup0Address = tmpManifest.Sections[0].Groups[0].Rows[0].Inputs[0];
836
+ let tmpGroup1Address = tmpManifest.Sections[0].Groups[1].Rows[0].Inputs[0];
837
+
838
+ Expect(Object.keys(tmpManifest.Descriptors).length).to.equal(2);
839
+
840
+ // Remove group 0
841
+ tmpView._ManifestOpsProvider.removeGroup(0, 0);
842
+ Expect(tmpManifest.Descriptors).to.not.have.property(tmpGroup0Address);
843
+ Expect(tmpManifest.Descriptors).to.have.property(tmpGroup1Address);
844
+ Expect(Object.keys(tmpManifest.Descriptors).length).to.equal(1);
845
+ }
846
+ );
847
+ test
848
+ (
849
+ 'Should remove Descriptors when removing a section',
850
+ function ()
851
+ {
852
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
853
+ tmpPict.AppData = {};
854
+
855
+ let tmpView = tmpPict.addView('TestRemoveSectionDescriptors',
856
+ {
857
+ ViewIdentifier: 'TestRemoveSectionDescriptors',
858
+ ManifestDataAddress: 'AppData.FormConfig'
859
+ }, libPictSectionFormEditor);
860
+
861
+ tmpView.initialize();
862
+ tmpView._ManifestOpsProvider.addSection();
863
+ tmpView._ManifestOpsProvider.addSection();
864
+ tmpView._ManifestOpsProvider.addRow(0, 0);
865
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
866
+ tmpView._ManifestOpsProvider.addRow(1, 0);
867
+ tmpView._ManifestOpsProvider.addInput(1, 0, 0);
868
+
869
+ let tmpManifest = tmpPict.AppData.FormConfig;
870
+ let tmpSection0Address = tmpManifest.Sections[0].Groups[0].Rows[0].Inputs[0];
871
+ let tmpSection1Address = tmpManifest.Sections[1].Groups[0].Rows[0].Inputs[0];
872
+
873
+ Expect(Object.keys(tmpManifest.Descriptors).length).to.equal(2);
874
+
875
+ // Remove section 0
876
+ tmpView._ManifestOpsProvider.removeSection(0);
877
+ Expect(tmpManifest.Descriptors).to.not.have.property(tmpSection0Address);
878
+ Expect(tmpManifest.Descriptors).to.have.property(tmpSection1Address);
879
+ Expect(Object.keys(tmpManifest.Descriptors).length).to.equal(1);
880
+ }
881
+ );
882
+ test
883
+ (
884
+ 'Should sync PictForm.Row when rows are reordered',
885
+ function ()
886
+ {
887
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
888
+ tmpPict.AppData = {};
889
+
890
+ let tmpView = tmpPict.addView('TestRowSync',
891
+ {
892
+ ViewIdentifier: 'TestRowSync',
893
+ ManifestDataAddress: 'AppData.FormConfig'
894
+ }, libPictSectionFormEditor);
895
+
896
+ tmpView.initialize();
897
+ tmpView._ManifestOpsProvider.addSection();
898
+ tmpView._ManifestOpsProvider.addRow(0, 0);
899
+ tmpView._ManifestOpsProvider.addRow(0, 0);
900
+ tmpView._ManifestOpsProvider.addRow(0, 0);
901
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
902
+ tmpView._ManifestOpsProvider.addInput(0, 0, 1);
903
+ tmpView._ManifestOpsProvider.addInput(0, 0, 2);
904
+
905
+ let tmpManifest = tmpPict.AppData.FormConfig;
906
+ let tmpGroup = tmpManifest.Sections[0].Groups[0];
907
+ let tmpAddr0 = tmpGroup.Rows[0].Inputs[0];
908
+ let tmpAddr1 = tmpGroup.Rows[1].Inputs[0];
909
+ let tmpAddr2 = tmpGroup.Rows[2].Inputs[0];
910
+
911
+ // Verify initial row assignments
912
+ Expect(tmpManifest.Descriptors[tmpAddr0].PictForm.Row).to.equal(1);
913
+ Expect(tmpManifest.Descriptors[tmpAddr1].PictForm.Row).to.equal(2);
914
+ Expect(tmpManifest.Descriptors[tmpAddr2].PictForm.Row).to.equal(3);
915
+
916
+ // Move row 2 up — swaps row 1 and row 2
917
+ tmpView._ManifestOpsProvider.moveRowUp(0, 0, 2);
918
+ Expect(tmpManifest.Descriptors[tmpAddr0].PictForm.Row).to.equal(1);
919
+ Expect(tmpManifest.Descriptors[tmpAddr2].PictForm.Row).to.equal(2);
920
+ Expect(tmpManifest.Descriptors[tmpAddr1].PictForm.Row).to.equal(3);
921
+
922
+ // Move row 0 down — swaps row 0 and row 1 (which is now addr2)
923
+ tmpView._ManifestOpsProvider.moveRowDown(0, 0, 0);
924
+ Expect(tmpManifest.Descriptors[tmpAddr2].PictForm.Row).to.equal(1);
925
+ Expect(tmpManifest.Descriptors[tmpAddr0].PictForm.Row).to.equal(2);
926
+ Expect(tmpManifest.Descriptors[tmpAddr1].PictForm.Row).to.equal(3);
927
+ }
928
+ );
929
+ test
930
+ (
931
+ 'Should expose the full list of Manyfest DataTypes',
932
+ function ()
933
+ {
934
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
935
+ let tmpView = tmpPict.addView('TestDataTypes',
936
+ {
937
+ ViewIdentifier: 'TestDataTypes',
938
+ ManifestDataAddress: 'AppData.FormConfig'
939
+ }, libPictSectionFormEditor);
940
+
941
+ Expect(tmpView._ManyfestDataTypes).to.be.an('array');
942
+ Expect(tmpView._ManyfestDataTypes).to.include('String');
943
+ Expect(tmpView._ManyfestDataTypes).to.include('Number');
944
+ Expect(tmpView._ManyfestDataTypes).to.include('Float');
945
+ Expect(tmpView._ManyfestDataTypes).to.include('Integer');
946
+ Expect(tmpView._ManyfestDataTypes).to.include('PreciseNumber');
947
+ Expect(tmpView._ManyfestDataTypes).to.include('Boolean');
948
+ Expect(tmpView._ManyfestDataTypes).to.include('Binary');
949
+ Expect(tmpView._ManyfestDataTypes).to.include('DateTime');
950
+ Expect(tmpView._ManyfestDataTypes).to.include('Array');
951
+ Expect(tmpView._ManyfestDataTypes).to.include('Object');
952
+ Expect(tmpView._ManyfestDataTypes).to.include('Null');
953
+ Expect(tmpView._ManyfestDataTypes.length).to.equal(11);
954
+ }
955
+ );
956
+ test
957
+ (
958
+ 'Should expose InputType definitions as rich objects with Hash, Name, Description, Category',
959
+ function ()
960
+ {
961
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
962
+ let tmpView = tmpPict.addView('TestInputTypes',
963
+ {
964
+ ViewIdentifier: 'TestInputTypes',
965
+ ManifestDataAddress: 'AppData.FormConfig'
966
+ }, libPictSectionFormEditor);
967
+
968
+ Expect(tmpView._InputTypeDefinitions).to.be.an('array');
969
+ Expect(tmpView._InputTypeDefinitions.length).to.equal(16);
970
+
971
+ // Check that each entry is a rich object
972
+ for (let i = 0; i < tmpView._InputTypeDefinitions.length; i++)
973
+ {
974
+ let tmpDef = tmpView._InputTypeDefinitions[i];
975
+ Expect(tmpDef).to.be.an('object');
976
+ Expect(tmpDef.Hash).to.be.a('string');
977
+ Expect(tmpDef.Name).to.be.a('string');
978
+ Expect(tmpDef.Description).to.be.a('string');
979
+ Expect(tmpDef.Category).to.be.a('string');
980
+ }
981
+
982
+ // Verify all 16 built-in InputTypes are present by Hash
983
+ let tmpHashes = tmpView._InputTypeDefinitions.map(function(d) { return d.Hash; });
984
+ Expect(tmpHashes).to.include('TextArea');
985
+ Expect(tmpHashes).to.include('Option');
986
+ Expect(tmpHashes).to.include('Boolean');
987
+ Expect(tmpHashes).to.include('Hidden');
988
+ Expect(tmpHashes).to.include('Color');
989
+ Expect(tmpHashes).to.include('DisplayOnly');
990
+ Expect(tmpHashes).to.include('ReadOnly');
991
+ Expect(tmpHashes).to.include('Link');
992
+ Expect(tmpHashes).to.include('Chart');
993
+ Expect(tmpHashes).to.include('Markdown');
994
+ Expect(tmpHashes).to.include('HTML');
995
+ Expect(tmpHashes).to.include('PreciseNumberReadOnly');
996
+ Expect(tmpHashes).to.include('Templated');
997
+ Expect(tmpHashes).to.include('TemplatedEntityLookup');
998
+ Expect(tmpHashes).to.include('TabGroupSelector');
999
+ Expect(tmpHashes).to.include('TabSectionSelector');
1000
+ }
1001
+ );
1002
+ test
1003
+ (
1004
+ 'Should allow embedders to add custom InputType definitions via options',
1005
+ function ()
1006
+ {
1007
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1008
+ let tmpView = tmpPict.addView('TestCustomInputTypes',
1009
+ {
1010
+ ViewIdentifier: 'TestCustomInputTypes',
1011
+ ManifestDataAddress: 'AppData.FormConfig',
1012
+ InputTypeDefinitions:
1013
+ [
1014
+ { Hash: 'CustomWidget', Name: 'Custom Widget', Description: 'A custom widget type', Category: 'Custom' },
1015
+ { Hash: 'CustomChart', Name: 'Custom Chart', Description: 'An extended chart type', Category: 'Custom' }
1016
+ ]
1017
+ }, libPictSectionFormEditor);
1018
+
1019
+ Expect(tmpView._InputTypeDefinitions).to.be.an('array');
1020
+ // 16 defaults + 2 custom = 18
1021
+ Expect(tmpView._InputTypeDefinitions.length).to.equal(18);
1022
+
1023
+ let tmpHashes = tmpView._InputTypeDefinitions.map(function(d) { return d.Hash; });
1024
+ Expect(tmpHashes).to.include('CustomWidget');
1025
+ Expect(tmpHashes).to.include('CustomChart');
1026
+ // Built-in types should still be there
1027
+ Expect(tmpHashes).to.include('TextArea');
1028
+ Expect(tmpHashes).to.include('Boolean');
1029
+ }
1030
+ );
1031
+ test
1032
+ (
1033
+ 'Should allow embedders to override built-in InputType definitions',
1034
+ function ()
1035
+ {
1036
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1037
+ let tmpView = tmpPict.addView('TestOverrideInputTypes',
1038
+ {
1039
+ ViewIdentifier: 'TestOverrideInputTypes',
1040
+ ManifestDataAddress: 'AppData.FormConfig',
1041
+ InputTypeDefinitions:
1042
+ [
1043
+ { Hash: 'Boolean', Name: 'Yes/No Toggle', Description: 'Overridden boolean' }
1044
+ ]
1045
+ }, libPictSectionFormEditor);
1046
+
1047
+ // Count should stay at 16 since we overrode, not appended
1048
+ Expect(tmpView._InputTypeDefinitions.length).to.equal(16);
1049
+
1050
+ // Find the Boolean entry and verify it was overridden
1051
+ let tmpBoolDef = null;
1052
+ for (let i = 0; i < tmpView._InputTypeDefinitions.length; i++)
1053
+ {
1054
+ if (tmpView._InputTypeDefinitions[i].Hash === 'Boolean')
1055
+ {
1056
+ tmpBoolDef = tmpView._InputTypeDefinitions[i];
1057
+ break;
1058
+ }
1059
+ }
1060
+ Expect(tmpBoolDef).to.not.be.null;
1061
+ Expect(tmpBoolDef.Name).to.equal('Yes/No Toggle');
1062
+ Expect(tmpBoolDef.Description).to.equal('Overridden boolean');
1063
+ // Category should be preserved from the default
1064
+ Expect(tmpBoolDef.Category).to.equal('Selection');
1065
+ }
1066
+ );
1067
+ test
1068
+ (
1069
+ 'Should group InputType definitions by category',
1070
+ function ()
1071
+ {
1072
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1073
+ let tmpView = tmpPict.addView('TestCategories',
1074
+ {
1075
+ ViewIdentifier: 'TestCategories',
1076
+ ManifestDataAddress: 'AppData.FormConfig'
1077
+ }, libPictSectionFormEditor);
1078
+
1079
+ let tmpCategories = tmpView._UtilitiesProvider._getInputTypeCategories();
1080
+ Expect(tmpCategories).to.be.an('array');
1081
+ Expect(tmpCategories).to.include('Text & Content');
1082
+ Expect(tmpCategories).to.include('Selection');
1083
+ Expect(tmpCategories).to.include('Display');
1084
+ Expect(tmpCategories).to.include('Navigation');
1085
+ Expect(tmpCategories).to.include('Advanced');
1086
+ }
1087
+ );
1088
+ test
1089
+ (
1090
+ 'Should filter InputType definitions by search query',
1091
+ function ()
1092
+ {
1093
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1094
+ let tmpView = tmpPict.addView('TestFilter',
1095
+ {
1096
+ ViewIdentifier: 'TestFilter',
1097
+ ManifestDataAddress: 'AppData.FormConfig'
1098
+ }, libPictSectionFormEditor);
1099
+
1100
+ // Search for "read" should find ReadOnly and PreciseNumberReadOnly
1101
+ let tmpFiltered = tmpView._UtilitiesProvider._filterInputTypeDefinitions('read');
1102
+ Expect(tmpFiltered).to.be.an('array');
1103
+ Expect(tmpFiltered.length).to.be.at.least(2);
1104
+ let tmpFilteredHashes = tmpFiltered.map(function(d) { return d.Hash; });
1105
+ Expect(tmpFilteredHashes).to.include('ReadOnly');
1106
+ Expect(tmpFilteredHashes).to.include('PreciseNumberReadOnly');
1107
+
1108
+ // Empty query should return all definitions
1109
+ let tmpAll = tmpView._UtilitiesProvider._filterInputTypeDefinitions('');
1110
+ Expect(tmpAll.length).to.equal(16);
1111
+
1112
+ // Non-matching query should return empty
1113
+ let tmpNone = tmpView._UtilitiesProvider._filterInputTypeDefinitions('zzzznonexistent');
1114
+ Expect(tmpNone.length).to.equal(0);
1115
+ }
1116
+ );
1117
+ }
1118
+ );
1119
+ suite
1120
+ (
1121
+ 'Manifest Reconciliation',
1122
+ function ()
1123
+ {
1124
+ test
1125
+ (
1126
+ 'Should build Rows and Inputs from Descriptors on an existing manifest',
1127
+ function ()
1128
+ {
1129
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1130
+ tmpPict.AppData = {};
1131
+ tmpPict.AppData.FormConfig =
1132
+ {
1133
+ Scope: 'TestReconcile',
1134
+ Sections:
1135
+ [
1136
+ {
1137
+ Hash: 'Area',
1138
+ Name: 'Area Calculator',
1139
+ Groups:
1140
+ [
1141
+ {
1142
+ Hash: 'AreaDefault',
1143
+ Name: 'Default',
1144
+ Layout: 'Record'
1145
+ }
1146
+ ]
1147
+ }
1148
+ ],
1149
+ Descriptors:
1150
+ {
1151
+ 'Name':
1152
+ {
1153
+ Name: 'Object Name',
1154
+ Hash: 'Name',
1155
+ DataType: 'String',
1156
+ PictForm: { Section: 'Area', Row: 1 }
1157
+ },
1158
+ 'Width':
1159
+ {
1160
+ Name: 'Width',
1161
+ Hash: 'Width',
1162
+ DataType: 'Number',
1163
+ PictForm: { Section: 'Area', Row: 2 }
1164
+ },
1165
+ 'Height':
1166
+ {
1167
+ Name: 'Height',
1168
+ Hash: 'Height',
1169
+ DataType: 'Number',
1170
+ PictForm: { Section: 'Area', Row: 2 }
1171
+ }
1172
+ }
1173
+ };
1174
+
1175
+ let tmpView = tmpPict.addView('TestReconcile',
1176
+ {
1177
+ ViewIdentifier: 'TestReconcile',
1178
+ ManifestDataAddress: 'AppData.FormConfig'
1179
+ }, libPictSectionFormEditor);
1180
+
1181
+ tmpView.initialize();
1182
+ tmpView._ManifestOpsProvider._reconcileManifestStructure();
1183
+
1184
+ let tmpGroup = tmpPict.AppData.FormConfig.Sections[0].Groups[0];
1185
+ Expect(tmpGroup.Rows).to.be.an('array');
1186
+ Expect(tmpGroup.Rows.length).to.equal(2);
1187
+
1188
+ // Row 1 should have one input: 'Name'
1189
+ Expect(tmpGroup.Rows[0].Inputs).to.be.an('array');
1190
+ Expect(tmpGroup.Rows[0].Inputs.length).to.equal(1);
1191
+ Expect(tmpGroup.Rows[0].Inputs[0]).to.equal('Name');
1192
+
1193
+ // Row 2 should have two inputs: 'Width' and 'Height'
1194
+ Expect(tmpGroup.Rows[1].Inputs).to.be.an('array');
1195
+ Expect(tmpGroup.Rows[1].Inputs.length).to.equal(2);
1196
+ Expect(tmpGroup.Rows[1].Inputs).to.include('Width');
1197
+ Expect(tmpGroup.Rows[1].Inputs).to.include('Height');
1198
+ }
1199
+ );
1200
+ test
1201
+ (
1202
+ 'Should default to first group when PictForm.Group is omitted',
1203
+ function ()
1204
+ {
1205
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1206
+ tmpPict.AppData = {};
1207
+ tmpPict.AppData.FormConfig =
1208
+ {
1209
+ Scope: 'TestDefaultGroup',
1210
+ Sections:
1211
+ [
1212
+ {
1213
+ Hash: 'Info',
1214
+ Name: 'Info Section',
1215
+ Groups:
1216
+ [
1217
+ { Hash: 'Main', Name: 'Main', Layout: 'Record' },
1218
+ { Hash: 'Extra', Name: 'Extra', Layout: 'Record' }
1219
+ ]
1220
+ }
1221
+ ],
1222
+ Descriptors:
1223
+ {
1224
+ 'FirstName':
1225
+ {
1226
+ Name: 'First Name',
1227
+ Hash: 'FirstName',
1228
+ DataType: 'String',
1229
+ PictForm: { Section: 'Info', Row: 1 }
1230
+ }
1231
+ }
1232
+ };
1233
+
1234
+ let tmpView = tmpPict.addView('TestDefaultGroup',
1235
+ {
1236
+ ViewIdentifier: 'TestDefaultGroup',
1237
+ ManifestDataAddress: 'AppData.FormConfig'
1238
+ }, libPictSectionFormEditor);
1239
+
1240
+ tmpView.initialize();
1241
+ tmpView._ManifestOpsProvider._reconcileManifestStructure();
1242
+
1243
+ // Should land in the first group (Main)
1244
+ let tmpMainGroup = tmpPict.AppData.FormConfig.Sections[0].Groups[0];
1245
+ Expect(tmpMainGroup.Rows).to.be.an('array');
1246
+ Expect(tmpMainGroup.Rows[0].Inputs).to.include('FirstName');
1247
+ }
1248
+ );
1249
+ test
1250
+ (
1251
+ 'Should place inputs in the correct named group',
1252
+ function ()
1253
+ {
1254
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1255
+ tmpPict.AppData = {};
1256
+ tmpPict.AppData.FormConfig =
1257
+ {
1258
+ Scope: 'TestNamedGroup',
1259
+ Sections:
1260
+ [
1261
+ {
1262
+ Hash: 'Area',
1263
+ Name: 'Area',
1264
+ Groups:
1265
+ [
1266
+ { Hash: 'Default', Name: 'Default', Layout: 'Record' },
1267
+ { Hash: 'Help', Name: 'Help', Layout: 'Record' }
1268
+ ]
1269
+ }
1270
+ ],
1271
+ Descriptors:
1272
+ {
1273
+ 'Help.Content':
1274
+ {
1275
+ Name: 'Help Content',
1276
+ Hash: 'HelpContent',
1277
+ DataType: 'String',
1278
+ PictForm: { Section: 'Area', Group: 'Help', Row: 1 }
1279
+ }
1280
+ }
1281
+ };
1282
+
1283
+ let tmpView = tmpPict.addView('TestNamedGroup',
1284
+ {
1285
+ ViewIdentifier: 'TestNamedGroup',
1286
+ ManifestDataAddress: 'AppData.FormConfig'
1287
+ }, libPictSectionFormEditor);
1288
+
1289
+ tmpView.initialize();
1290
+ tmpView._ManifestOpsProvider._reconcileManifestStructure();
1291
+
1292
+ // Should be in the Help group, not Default
1293
+ let tmpDefaultGroup = tmpPict.AppData.FormConfig.Sections[0].Groups[0];
1294
+ let tmpHelpGroup = tmpPict.AppData.FormConfig.Sections[0].Groups[1];
1295
+ Expect(tmpDefaultGroup.Rows).to.be.undefined;
1296
+ Expect(tmpHelpGroup.Rows).to.be.an('array');
1297
+ Expect(tmpHelpGroup.Rows[0].Inputs).to.include('Help.Content');
1298
+ }
1299
+ );
1300
+ test
1301
+ (
1302
+ 'Should create a default group when section has no groups',
1303
+ function ()
1304
+ {
1305
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1306
+ tmpPict.AppData = {};
1307
+ tmpPict.AppData.FormConfig =
1308
+ {
1309
+ Scope: 'TestNoGroups',
1310
+ Sections:
1311
+ [
1312
+ {
1313
+ Hash: 'SectionA',
1314
+ Name: 'Section A'
1315
+ }
1316
+ ],
1317
+ Descriptors:
1318
+ {
1319
+ 'SectionA.Amount':
1320
+ {
1321
+ Name: 'Amount in A',
1322
+ Hash: 'SectionAAmount',
1323
+ DataType: 'Number',
1324
+ PictForm: { Section: 'SectionA', Row: 1 }
1325
+ }
1326
+ }
1327
+ };
1328
+
1329
+ let tmpView = tmpPict.addView('TestNoGroups',
1330
+ {
1331
+ ViewIdentifier: 'TestNoGroups',
1332
+ ManifestDataAddress: 'AppData.FormConfig'
1333
+ }, libPictSectionFormEditor);
1334
+
1335
+ tmpView.initialize();
1336
+ tmpView._ManifestOpsProvider._reconcileManifestStructure();
1337
+
1338
+ let tmpSection = tmpPict.AppData.FormConfig.Sections[0];
1339
+ Expect(tmpSection.Groups).to.be.an('array');
1340
+ Expect(tmpSection.Groups.length).to.equal(1);
1341
+ Expect(tmpSection.Groups[0].Rows[0].Inputs).to.include('SectionA.Amount');
1342
+ }
1343
+ );
1344
+ test
1345
+ (
1346
+ 'Should not duplicate inputs on repeated reconciliation calls',
1347
+ function ()
1348
+ {
1349
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1350
+ tmpPict.AppData = {};
1351
+ tmpPict.AppData.FormConfig =
1352
+ {
1353
+ Scope: 'TestIdempotent',
1354
+ Sections:
1355
+ [
1356
+ {
1357
+ Hash: 'Sec',
1358
+ Name: 'Section',
1359
+ Groups:
1360
+ [
1361
+ { Hash: 'Grp', Name: 'Group', Layout: 'Record' }
1362
+ ]
1363
+ }
1364
+ ],
1365
+ Descriptors:
1366
+ {
1367
+ 'Field1':
1368
+ {
1369
+ Name: 'Field One',
1370
+ Hash: 'Field1',
1371
+ DataType: 'String',
1372
+ PictForm: { Section: 'Sec', Row: 1 }
1373
+ }
1374
+ }
1375
+ };
1376
+
1377
+ let tmpView = tmpPict.addView('TestIdempotent',
1378
+ {
1379
+ ViewIdentifier: 'TestIdempotent',
1380
+ ManifestDataAddress: 'AppData.FormConfig'
1381
+ }, libPictSectionFormEditor);
1382
+
1383
+ tmpView.initialize();
1384
+
1385
+ // Call reconcile multiple times
1386
+ tmpView._ManifestOpsProvider._reconcileManifestStructure();
1387
+ tmpView._ManifestOpsProvider._reconcileManifestStructure();
1388
+ tmpView._ManifestOpsProvider._reconcileManifestStructure();
1389
+
1390
+ let tmpGroup = tmpPict.AppData.FormConfig.Sections[0].Groups[0];
1391
+ Expect(tmpGroup.Rows[0].Inputs.length).to.equal(1);
1392
+ Expect(tmpGroup.Rows[0].Inputs[0]).to.equal('Field1');
1393
+ }
1394
+ );
1395
+ }
1396
+ );
1397
+ suite
1398
+ (
1399
+ 'Tab Management',
1400
+ function ()
1401
+ {
1402
+ test
1403
+ (
1404
+ 'Should default to visual tab',
1405
+ function ()
1406
+ {
1407
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1408
+ let tmpView = tmpPict.addView('TestTabDefault',
1409
+ {
1410
+ ViewIdentifier: 'TestTabDefault',
1411
+ ManifestDataAddress: 'AppData.FormConfig'
1412
+ }, libPictSectionFormEditor);
1413
+
1414
+ Expect(tmpView._ActiveTab).to.equal('visual');
1415
+ }
1416
+ );
1417
+ test
1418
+ (
1419
+ 'Should switch tabs',
1420
+ function ()
1421
+ {
1422
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1423
+ tmpPict.AppData = {};
1424
+
1425
+ let tmpView = tmpPict.addView('TestTabSwitch',
1426
+ {
1427
+ ViewIdentifier: 'TestTabSwitch',
1428
+ ManifestDataAddress: 'AppData.FormConfig'
1429
+ }, libPictSectionFormEditor);
1430
+
1431
+ tmpView.initialize();
1432
+
1433
+ tmpView.switchTab('objecteditor');
1434
+ Expect(tmpView._ActiveTab).to.equal('objecteditor');
1435
+
1436
+ tmpView.switchTab('json');
1437
+ Expect(tmpView._ActiveTab).to.equal('json');
1438
+
1439
+ tmpView.switchTab('visual');
1440
+ Expect(tmpView._ActiveTab).to.equal('visual');
1441
+ }
1442
+ );
1443
+ }
1444
+ );
1445
+ suite
1446
+ (
1447
+ 'Utility Methods',
1448
+ function ()
1449
+ {
1450
+ test
1451
+ (
1452
+ 'Should escape HTML correctly',
1453
+ function ()
1454
+ {
1455
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1456
+ let tmpView = tmpPict.addView('TestEscape',
1457
+ {
1458
+ ViewIdentifier: 'TestEscape',
1459
+ ManifestDataAddress: 'AppData.FormConfig'
1460
+ }, libPictSectionFormEditor);
1461
+
1462
+ Expect(tmpView._UtilitiesProvider._escapeHTML('<script>alert("xss")</script>')).to.equal('&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;');
1463
+ Expect(tmpView._UtilitiesProvider._escapeHTML('')).to.equal('');
1464
+ Expect(tmpView._UtilitiesProvider._escapeHTML(null)).to.equal('');
1465
+ Expect(tmpView._UtilitiesProvider._escapeHTML(undefined)).to.equal('');
1466
+ }
1467
+ );
1468
+ test
1469
+ (
1470
+ 'Should escape attributes correctly',
1471
+ function ()
1472
+ {
1473
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1474
+ let tmpView = tmpPict.addView('TestEscapeAttr',
1475
+ {
1476
+ ViewIdentifier: 'TestEscapeAttr',
1477
+ ManifestDataAddress: 'AppData.FormConfig'
1478
+ }, libPictSectionFormEditor);
1479
+
1480
+ Expect(tmpView._UtilitiesProvider._escapeAttr('value with "quotes" and \'apostrophes\'')).to.equal('value with &quot;quotes&quot; and &#39;apostrophes&#39;');
1481
+ }
1482
+ );
1483
+ }
1484
+ );
1485
+ suite
1486
+ (
1487
+ 'Iconography Provider',
1488
+ function ()
1489
+ {
1490
+ test
1491
+ (
1492
+ 'Should expose the iconography provider on the view',
1493
+ function ()
1494
+ {
1495
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1496
+ let tmpView = tmpPict.addView('TestIconProvider',
1497
+ {
1498
+ ViewIdentifier: 'TestIconProvider',
1499
+ ManifestDataAddress: 'AppData.FormConfig'
1500
+ }, libPictSectionFormEditor);
1501
+
1502
+ Expect(tmpView._IconographyProvider).to.be.an('object');
1503
+ }
1504
+ );
1505
+ test
1506
+ (
1507
+ 'Should export the IconographyProvider class from the module',
1508
+ function ()
1509
+ {
1510
+ Expect(libPictSectionFormEditor.IconographyProvider).to.be.a('function');
1511
+ }
1512
+ );
1513
+ test
1514
+ (
1515
+ 'Should register expected variants for each structural category',
1516
+ function ()
1517
+ {
1518
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1519
+ var tmpProvider = new libIconography();
1520
+
1521
+ var tmpSectionVariants = tmpProvider.getVariants('Section');
1522
+ var tmpGroupVariants = tmpProvider.getVariants('Group');
1523
+ var tmpRowVariants = tmpProvider.getVariants('Row');
1524
+ var tmpInputVariants = tmpProvider.getVariants('Input');
1525
+
1526
+ Expect(tmpSectionVariants.length).to.equal(11);
1527
+ Expect(tmpGroupVariants.length).to.equal(11);
1528
+ Expect(tmpRowVariants.length).to.equal(11);
1529
+ Expect(tmpInputVariants.length).to.equal(10);
1530
+ }
1531
+ );
1532
+ test
1533
+ (
1534
+ 'Should return SVG strings for all structural icon variants',
1535
+ function ()
1536
+ {
1537
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1538
+ var tmpProvider = new libIconography();
1539
+
1540
+ var tmpCategories = ['Section', 'Group', 'Row', 'Input'];
1541
+ for (var c = 0; c < tmpCategories.length; c++)
1542
+ {
1543
+ var tmpVariants = tmpProvider.getVariants(tmpCategories[c]);
1544
+ for (var v = 0; v < tmpVariants.length; v++)
1545
+ {
1546
+ var tmpSVG = tmpProvider.getIcon(tmpCategories[c], tmpVariants[v]);
1547
+ Expect(tmpSVG).to.be.a('string');
1548
+ Expect(tmpSVG.indexOf('<svg')).to.equal(0);
1549
+ Expect(tmpSVG.indexOf('</svg>')).to.be.above(0);
1550
+ }
1551
+ }
1552
+ }
1553
+ );
1554
+ test
1555
+ (
1556
+ 'Should return SVG strings at custom sizes',
1557
+ function ()
1558
+ {
1559
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1560
+ var tmpProvider = new libIconography();
1561
+
1562
+ var tmpSVG16 = tmpProvider.getIcon('Section', 'Default', 16);
1563
+ var tmpSVG32 = tmpProvider.getIcon('Section', 'Default', 32);
1564
+
1565
+ Expect(tmpSVG16).to.contain('width="16"');
1566
+ Expect(tmpSVG16).to.contain('height="16"');
1567
+ Expect(tmpSVG32).to.contain('width="32"');
1568
+ Expect(tmpSVG32).to.contain('height="32"');
1569
+ }
1570
+ );
1571
+ test
1572
+ (
1573
+ 'Should fall back to Default variant for unknown variant names',
1574
+ function ()
1575
+ {
1576
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1577
+ var tmpProvider = new libIconography();
1578
+
1579
+ var tmpSVG = tmpProvider.getIcon('Section', 'NonExistentVariant');
1580
+ var tmpDefault = tmpProvider.getIcon('Section', 'Default');
1581
+ Expect(tmpSVG).to.equal(tmpDefault);
1582
+ }
1583
+ );
1584
+ test
1585
+ (
1586
+ 'Should return empty string for unknown category',
1587
+ function ()
1588
+ {
1589
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1590
+ var tmpProvider = new libIconography();
1591
+
1592
+ var tmpSVG = tmpProvider.getIcon('NonExistent', 'Default');
1593
+ Expect(tmpSVG).to.equal('');
1594
+ }
1595
+ );
1596
+ test
1597
+ (
1598
+ 'Should register InputType icons for all built-in types',
1599
+ function ()
1600
+ {
1601
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1602
+ var tmpProvider = new libIconography();
1603
+
1604
+ var tmpHashes = tmpProvider.getInputTypeIconHashes();
1605
+ Expect(tmpHashes).to.be.an('array');
1606
+ Expect(tmpHashes.length).to.be.at.least(16);
1607
+
1608
+ // Check a sampling of known InputType icons
1609
+ Expect(tmpProvider.hasInputTypeIcon('TextArea')).to.be.true;
1610
+ Expect(tmpProvider.hasInputTypeIcon('Boolean')).to.be.true;
1611
+ Expect(tmpProvider.hasInputTypeIcon('DisplayOnly')).to.be.true;
1612
+ Expect(tmpProvider.hasInputTypeIcon('Hidden')).to.be.true;
1613
+ Expect(tmpProvider.hasInputTypeIcon('Chart')).to.be.true;
1614
+ Expect(tmpProvider.hasInputTypeIcon('Link')).to.be.true;
1615
+ Expect(tmpProvider.hasInputTypeIcon('Templated')).to.be.true;
1616
+ }
1617
+ );
1618
+ test
1619
+ (
1620
+ 'Should return SVG strings for InputType icons',
1621
+ function ()
1622
+ {
1623
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1624
+ var tmpProvider = new libIconography();
1625
+
1626
+ var tmpSVG = tmpProvider.getInputTypeIcon('TextArea');
1627
+ Expect(tmpSVG).to.be.a('string');
1628
+ Expect(tmpSVG.indexOf('<svg')).to.equal(0);
1629
+
1630
+ var tmpSVG2 = tmpProvider.getInputTypeIcon('Boolean', 24);
1631
+ Expect(tmpSVG2).to.contain('width="24"');
1632
+ }
1633
+ );
1634
+ test
1635
+ (
1636
+ 'Should return empty string for unknown InputType icon',
1637
+ function ()
1638
+ {
1639
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1640
+ var tmpProvider = new libIconography();
1641
+
1642
+ var tmpSVG = tmpProvider.getInputTypeIcon('CompletelyFakeType');
1643
+ Expect(tmpSVG).to.equal('');
1644
+ }
1645
+ );
1646
+ test
1647
+ (
1648
+ 'Should allow overriding structural icons via setIcon',
1649
+ function ()
1650
+ {
1651
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1652
+ var tmpProvider = new libIconography();
1653
+
1654
+ var tmpCustomFactory = function(pSize, pColors, pSW)
1655
+ {
1656
+ return '<svg class="custom" width="' + pSize + '"></svg>';
1657
+ };
1658
+
1659
+ tmpProvider.setIcon('Section', 'Default', tmpCustomFactory);
1660
+ var tmpSVG = tmpProvider.getIcon('Section', 'Default');
1661
+ Expect(tmpSVG).to.contain('class="custom"');
1662
+ }
1663
+ );
1664
+ test
1665
+ (
1666
+ 'Should allow adding new structural icon variants via setIcon',
1667
+ function ()
1668
+ {
1669
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1670
+ var tmpProvider = new libIconography();
1671
+
1672
+ var tmpCustomFactory = function(pSize, pColors, pSW)
1673
+ {
1674
+ return '<svg class="brand-new" width="' + pSize + '"></svg>';
1675
+ };
1676
+
1677
+ tmpProvider.setIcon('Section', 'MyBrandNew', tmpCustomFactory);
1678
+ Expect(tmpProvider.hasIcon('Section', 'MyBrandNew')).to.be.true;
1679
+ var tmpSVG = tmpProvider.getIcon('Section', 'MyBrandNew');
1680
+ Expect(tmpSVG).to.contain('class="brand-new"');
1681
+ }
1682
+ );
1683
+ test
1684
+ (
1685
+ 'Should allow overriding InputType icons via setInputTypeIcon',
1686
+ function ()
1687
+ {
1688
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1689
+ var tmpProvider = new libIconography();
1690
+
1691
+ var tmpCustomFactory = function(pSize, pColors, pSW)
1692
+ {
1693
+ return '<svg class="custom-boolean" width="' + pSize + '"></svg>';
1694
+ };
1695
+
1696
+ tmpProvider.setInputTypeIcon('Boolean', tmpCustomFactory);
1697
+ var tmpSVG = tmpProvider.getInputTypeIcon('Boolean');
1698
+ Expect(tmpSVG).to.contain('class="custom-boolean"');
1699
+ }
1700
+ );
1701
+ test
1702
+ (
1703
+ 'Should allow adding new InputType icons via setInputTypeIcon',
1704
+ function ()
1705
+ {
1706
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1707
+ var tmpProvider = new libIconography();
1708
+
1709
+ var tmpCustomFactory = function(pSize, pColors, pSW)
1710
+ {
1711
+ return '<svg class="custom-widget" width="' + pSize + '"></svg>';
1712
+ };
1713
+
1714
+ tmpProvider.setInputTypeIcon('CustomWidget', tmpCustomFactory);
1715
+ Expect(tmpProvider.hasInputTypeIcon('CustomWidget')).to.be.true;
1716
+ var tmpSVG = tmpProvider.getInputTypeIcon('CustomWidget');
1717
+ Expect(tmpSVG).to.contain('class="custom-widget"');
1718
+ }
1719
+ );
1720
+ test
1721
+ (
1722
+ 'Should accept icon overrides via constructor options',
1723
+ function ()
1724
+ {
1725
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1726
+ var tmpProvider = new libIconography(
1727
+ {
1728
+ IconOverrides:
1729
+ {
1730
+ Section:
1731
+ {
1732
+ Default: function(pSize, pColors, pSW) { return '<svg class="opts-override" width="' + pSize + '"></svg>'; }
1733
+ }
1734
+ },
1735
+ InputTypeIconOverrides:
1736
+ {
1737
+ TextArea: function(pSize, pColors, pSW) { return '<svg class="opts-textarea" width="' + pSize + '"></svg>'; }
1738
+ }
1739
+ });
1740
+
1741
+ var tmpSVG = tmpProvider.getIcon('Section', 'Default');
1742
+ Expect(tmpSVG).to.contain('class="opts-override"');
1743
+
1744
+ var tmpSVG2 = tmpProvider.getInputTypeIcon('TextArea');
1745
+ Expect(tmpSVG2).to.contain('class="opts-textarea"');
1746
+ }
1747
+ );
1748
+ test
1749
+ (
1750
+ 'Should support custom color tokens',
1751
+ function ()
1752
+ {
1753
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1754
+ var tmpProvider = new libIconography(
1755
+ {
1756
+ Colors:
1757
+ {
1758
+ Primary: '#FF0000',
1759
+ Accent: '#00FF00',
1760
+ Muted: '#0000FF',
1761
+ Fill: '#FFFFFF'
1762
+ }
1763
+ });
1764
+
1765
+ var tmpSVG = tmpProvider.getIcon('Section', 'Default');
1766
+ Expect(tmpSVG).to.contain('#FF0000');
1767
+ Expect(tmpSVG).to.contain('#FFFFFF');
1768
+ }
1769
+ );
1770
+ test
1771
+ (
1772
+ 'Should have hasIcon and hasInputTypeIcon return correct booleans',
1773
+ function ()
1774
+ {
1775
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1776
+ var tmpProvider = new libIconography();
1777
+
1778
+ Expect(tmpProvider.hasIcon('Section', 'Default')).to.be.true;
1779
+ Expect(tmpProvider.hasIcon('Section', 'Layers')).to.be.true;
1780
+ Expect(tmpProvider.hasIcon('Section', 'NonExistent')).to.be.false;
1781
+ Expect(tmpProvider.hasIcon('FakeCategory', 'Default')).to.be.false;
1782
+
1783
+ Expect(tmpProvider.hasInputTypeIcon('TextArea')).to.be.true;
1784
+ Expect(tmpProvider.hasInputTypeIcon('FakeHash')).to.be.false;
1785
+ }
1786
+ );
1787
+ test
1788
+ (
1789
+ 'Should make the iconography provider accessible on the FormEditor view with custom options',
1790
+ function ()
1791
+ {
1792
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1793
+ let tmpView = tmpPict.addView('TestIconProviderOpts',
1794
+ {
1795
+ ViewIdentifier: 'TestIconProviderOpts',
1796
+ ManifestDataAddress: 'AppData.FormConfig',
1797
+ Iconography:
1798
+ {
1799
+ Colors:
1800
+ {
1801
+ Primary: '#112233',
1802
+ Accent: '#445566',
1803
+ Muted: '#778899',
1804
+ Fill: '#AABBCC'
1805
+ }
1806
+ }
1807
+ }, libPictSectionFormEditor);
1808
+
1809
+ var tmpSVG = tmpView._IconographyProvider.getIcon('Section', 'Default');
1810
+ Expect(tmpSVG).to.contain('#112233');
1811
+ Expect(tmpSVG).to.contain('#AABBCC');
1812
+ }
1813
+ );
1814
+ test
1815
+ (
1816
+ 'Should have a DragHandle icon in the Action category',
1817
+ function ()
1818
+ {
1819
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
1820
+ var tmpProvider = new libIconography();
1821
+
1822
+ var tmpActionVariants = tmpProvider.getVariants('Action');
1823
+ Expect(tmpActionVariants).to.be.an('array');
1824
+ Expect(tmpActionVariants.length).to.equal(2);
1825
+ Expect(tmpActionVariants).to.include('Add');
1826
+ Expect(tmpActionVariants).to.include('DragHandle');
1827
+
1828
+ var tmpSVG = tmpProvider.getIcon('Action', 'DragHandle');
1829
+ Expect(tmpSVG).to.be.a('string');
1830
+ Expect(tmpSVG.indexOf('<svg')).to.equal(0);
1831
+ Expect(tmpSVG).to.contain('currentColor');
1832
+ }
1833
+ );
1834
+ test
1835
+ (
1836
+ 'Should have drag-and-drop disabled by default',
1837
+ function ()
1838
+ {
1839
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1840
+ let tmpView = tmpPict.addView('TestDragDropDefault',
1841
+ {
1842
+ ViewIdentifier: 'TestDragDropDefault',
1843
+ ManifestDataAddress: 'AppData.FormConfig'
1844
+ }, libPictSectionFormEditor);
1845
+
1846
+ Expect(tmpView._DragAndDropEnabled).to.equal(false);
1847
+ Expect(tmpView._DragState).to.equal(null);
1848
+ }
1849
+ );
1850
+ test
1851
+ (
1852
+ 'Should enable and disable drag-and-drop via setDragAndDropEnabled',
1853
+ function (fDone)
1854
+ {
1855
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1856
+ tmpPict.AppData.FormConfig =
1857
+ {
1858
+ Scope: 'TestDragDrop',
1859
+ Sections: [],
1860
+ Descriptors: {}
1861
+ };
1862
+
1863
+ let tmpView = tmpPict.addView('TestDragDropToggle',
1864
+ {
1865
+ ViewIdentifier: 'TestDragDropToggle',
1866
+ ManifestDataAddress: 'AppData.FormConfig',
1867
+ DefaultDestinationAddress: '#FormEditor-Container',
1868
+ Renderables:
1869
+ [
1870
+ {
1871
+ RenderableHash: 'FormEditor-Container',
1872
+ TemplateHash: 'FormEditor-Container-Template',
1873
+ DestinationAddress: '#FormEditor-Container',
1874
+ RenderMethod: 'replace'
1875
+ }
1876
+ ]
1877
+ }, libPictSectionFormEditor);
1878
+
1879
+ tmpView.initialize();
1880
+ tmpView.render();
1881
+
1882
+ Expect(tmpView._DragAndDropEnabled).to.equal(false);
1883
+
1884
+ tmpView._DragDropProvider.setDragAndDropEnabled(true);
1885
+ Expect(tmpView._DragAndDropEnabled).to.equal(true);
1886
+
1887
+ tmpView._DragDropProvider.setDragAndDropEnabled(false);
1888
+ Expect(tmpView._DragAndDropEnabled).to.equal(false);
1889
+
1890
+ fDone();
1891
+ }
1892
+ );
1893
+ test
1894
+ (
1895
+ 'Should build drag attributes only when enabled',
1896
+ function ()
1897
+ {
1898
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1899
+ let tmpView = tmpPict.addView('TestDragAttrs',
1900
+ {
1901
+ ViewIdentifier: 'TestDragAttrs',
1902
+ ManifestDataAddress: 'AppData.FormConfig'
1903
+ }, libPictSectionFormEditor);
1904
+
1905
+ // Disabled — should return empty string
1906
+ var tmpAttrs = tmpView._DragDropProvider._buildDragAttributes('section', [0]);
1907
+ Expect(tmpAttrs).to.equal('');
1908
+
1909
+ var tmpHandle = tmpView._DragDropProvider._buildDragHandleHTML(12);
1910
+ Expect(tmpHandle).to.equal('');
1911
+
1912
+ // Enabled — should return drag attribute string
1913
+ tmpView._DragAndDropEnabled = true;
1914
+ tmpAttrs = tmpView._DragDropProvider._buildDragAttributes('section', [0]);
1915
+ Expect(tmpAttrs).to.contain('draggable="true"');
1916
+ Expect(tmpAttrs).to.contain('ondragstart');
1917
+ Expect(tmpAttrs).to.contain('ondrop');
1918
+
1919
+ tmpHandle = tmpView._DragDropProvider._buildDragHandleHTML(12);
1920
+ Expect(tmpHandle).to.contain('pict-fe-drag-handle');
1921
+ Expect(tmpHandle).to.contain('<svg');
1922
+ }
1923
+ );
1924
+ test
1925
+ (
1926
+ 'Should correctly determine if drag indices share a parent',
1927
+ function ()
1928
+ {
1929
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1930
+ let tmpView = tmpPict.addView('TestDragParent',
1931
+ {
1932
+ ViewIdentifier: 'TestDragParent',
1933
+ ManifestDataAddress: 'AppData.FormConfig'
1934
+ }, libPictSectionFormEditor);
1935
+
1936
+ // Sections (1 index) — always share root
1937
+ Expect(tmpView._DragDropProvider._dragIndicesShareParent([0], [1])).to.equal(true);
1938
+ Expect(tmpView._DragDropProvider._dragIndicesShareParent([0], [2])).to.equal(true);
1939
+
1940
+ // Groups (2 indices) — must share section
1941
+ Expect(tmpView._DragDropProvider._dragIndicesShareParent([0, 1], [0, 2])).to.equal(true);
1942
+ Expect(tmpView._DragDropProvider._dragIndicesShareParent([0, 1], [1, 2])).to.equal(false);
1943
+
1944
+ // Rows (3 indices) — must share section + group
1945
+ Expect(tmpView._DragDropProvider._dragIndicesShareParent([0, 0, 1], [0, 0, 3])).to.equal(true);
1946
+ Expect(tmpView._DragDropProvider._dragIndicesShareParent([0, 0, 1], [0, 1, 3])).to.equal(false);
1947
+
1948
+ // Inputs (4 indices) — must share section + group + row
1949
+ Expect(tmpView._DragDropProvider._dragIndicesShareParent([0, 0, 0, 1], [0, 0, 0, 3])).to.equal(true);
1950
+ Expect(tmpView._DragDropProvider._dragIndicesShareParent([0, 0, 0, 1], [0, 0, 1, 3])).to.equal(false);
1951
+
1952
+ // Mismatched lengths
1953
+ Expect(tmpView._DragDropProvider._dragIndicesShareParent([0], [0, 1])).to.equal(false);
1954
+ }
1955
+ );
1956
+ test
1957
+ (
1958
+ 'Should support cross-container drag and drop for groups',
1959
+ function (fDone)
1960
+ {
1961
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
1962
+ tmpPict.AppData.FormConfig =
1963
+ {
1964
+ Scope: 'CrossDragGroup',
1965
+ Sections:
1966
+ [
1967
+ {
1968
+ Name: 'Section A', Hash: 'SectionA',
1969
+ Groups:
1970
+ [
1971
+ { Name: 'Group A1', Hash: 'GroupA1', Rows: [] },
1972
+ { Name: 'Group A2', Hash: 'GroupA2', Rows: [] }
1973
+ ]
1974
+ },
1975
+ {
1976
+ Name: 'Section B', Hash: 'SectionB',
1977
+ Groups:
1978
+ [
1979
+ { Name: 'Group B1', Hash: 'GroupB1', Rows: [] }
1980
+ ]
1981
+ }
1982
+ ],
1983
+ Descriptors: {}
1984
+ };
1985
+
1986
+ let tmpView = tmpPict.addView('TestCrossDragGroup',
1987
+ {
1988
+ ViewIdentifier: 'TestCrossDragGroup',
1989
+ ManifestDataAddress: 'AppData.FormConfig',
1990
+ DefaultDestinationAddress: '#FormEditor-Container',
1991
+ Renderables:
1992
+ [
1993
+ {
1994
+ RenderableHash: 'FormEditor-Container',
1995
+ TemplateHash: 'FormEditor-Container-Template',
1996
+ DestinationAddress: '#FormEditor-Container',
1997
+ RenderMethod: 'replace'
1998
+ }
1999
+ ]
2000
+ }, libPictSectionFormEditor);
2001
+
2002
+ tmpView.initialize();
2003
+ tmpView.render();
2004
+ tmpView._DragAndDropEnabled = true;
2005
+
2006
+ // Move Group A2 from Section A to Section B (drop on Group B1)
2007
+ tmpView._DragState = { Type: 'group', Indices: [0, 1] };
2008
+ tmpView._DragDropProvider.onDrop(
2009
+ { preventDefault: function() {} },
2010
+ 'group', 1, 0
2011
+ );
2012
+
2013
+ var tmpManifest = tmpPict.AppData.FormConfig;
2014
+ // Section A should now have 1 group
2015
+ Expect(tmpManifest.Sections[0].Groups.length).to.equal(1);
2016
+ Expect(tmpManifest.Sections[0].Groups[0].Hash).to.equal('GroupA1');
2017
+ // Section B should now have 2 groups (GroupA2 inserted before GroupB1)
2018
+ Expect(tmpManifest.Sections[1].Groups.length).to.equal(2);
2019
+ Expect(tmpManifest.Sections[1].Groups[0].Hash).to.equal('GroupA2');
2020
+ Expect(tmpManifest.Sections[1].Groups[1].Hash).to.equal('GroupB1');
2021
+
2022
+ fDone();
2023
+ }
2024
+ );
2025
+ test
2026
+ (
2027
+ 'Should support cross-container drag and drop for rows',
2028
+ function (fDone)
2029
+ {
2030
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2031
+ tmpPict.AppData.FormConfig =
2032
+ {
2033
+ Scope: 'CrossDragRow',
2034
+ Sections:
2035
+ [
2036
+ {
2037
+ Name: 'Section A', Hash: 'SectionA',
2038
+ Groups:
2039
+ [
2040
+ {
2041
+ Name: 'Group A1', Hash: 'GroupA1',
2042
+ Rows:
2043
+ [
2044
+ { Inputs: ['addrA'] },
2045
+ { Inputs: ['addrB'] }
2046
+ ]
2047
+ },
2048
+ {
2049
+ Name: 'Group A2', Hash: 'GroupA2',
2050
+ Rows:
2051
+ [
2052
+ { Inputs: ['addrC'] }
2053
+ ]
2054
+ }
2055
+ ]
2056
+ }
2057
+ ],
2058
+ Descriptors:
2059
+ {
2060
+ addrA: { Name: 'InputA', Hash: 'InputA', DataType: 'String', PictForm: { Section: 'SectionA', Group: 'GroupA1', Row: 1 } },
2061
+ addrB: { Name: 'InputB', Hash: 'InputB', DataType: 'String', PictForm: { Section: 'SectionA', Group: 'GroupA1', Row: 2 } },
2062
+ addrC: { Name: 'InputC', Hash: 'InputC', DataType: 'String', PictForm: { Section: 'SectionA', Group: 'GroupA2', Row: 1 } }
2063
+ }
2064
+ };
2065
+
2066
+ let tmpView = tmpPict.addView('TestCrossDragRow',
2067
+ {
2068
+ ViewIdentifier: 'TestCrossDragRow',
2069
+ ManifestDataAddress: 'AppData.FormConfig',
2070
+ DefaultDestinationAddress: '#FormEditor-Container',
2071
+ Renderables:
2072
+ [
2073
+ {
2074
+ RenderableHash: 'FormEditor-Container',
2075
+ TemplateHash: 'FormEditor-Container-Template',
2076
+ DestinationAddress: '#FormEditor-Container',
2077
+ RenderMethod: 'replace'
2078
+ }
2079
+ ]
2080
+ }, libPictSectionFormEditor);
2081
+
2082
+ tmpView.initialize();
2083
+ tmpView.render();
2084
+ tmpView._DragAndDropEnabled = true;
2085
+
2086
+ // Move Row 2 from Group A1 to Group A2 (drop on Row 1 of Group A2)
2087
+ tmpView._DragState = { Type: 'row', Indices: [0, 0, 1] };
2088
+ tmpView._DragDropProvider.onDrop(
2089
+ { preventDefault: function() {} },
2090
+ 'row', 0, 1, 0
2091
+ );
2092
+
2093
+ var tmpManifest = tmpPict.AppData.FormConfig;
2094
+ // Group A1 should have 1 row
2095
+ Expect(tmpManifest.Sections[0].Groups[0].Rows.length).to.equal(1);
2096
+ Expect(tmpManifest.Sections[0].Groups[0].Rows[0].Inputs[0]).to.equal('addrA');
2097
+ // Group A2 should have 2 rows (addrB row inserted before addrC row)
2098
+ Expect(tmpManifest.Sections[0].Groups[1].Rows.length).to.equal(2);
2099
+ Expect(tmpManifest.Sections[0].Groups[1].Rows[0].Inputs[0]).to.equal('addrB');
2100
+ Expect(tmpManifest.Sections[0].Groups[1].Rows[1].Inputs[0]).to.equal('addrC');
2101
+
2102
+ fDone();
2103
+ }
2104
+ );
2105
+ test
2106
+ (
2107
+ 'Should support cross-container drag and drop for inputs and update PictForm metadata',
2108
+ function (fDone)
2109
+ {
2110
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2111
+ tmpPict.AppData.FormConfig =
2112
+ {
2113
+ Scope: 'CrossDragInput',
2114
+ Sections:
2115
+ [
2116
+ {
2117
+ Name: 'Section A', Hash: 'SectionA',
2118
+ Groups:
2119
+ [
2120
+ {
2121
+ Name: 'Group A1', Hash: 'GroupA1',
2122
+ Rows:
2123
+ [
2124
+ { Inputs: ['addrX', 'addrY'] }
2125
+ ]
2126
+ }
2127
+ ]
2128
+ },
2129
+ {
2130
+ Name: 'Section B', Hash: 'SectionB',
2131
+ Groups:
2132
+ [
2133
+ {
2134
+ Name: 'Group B1', Hash: 'GroupB1',
2135
+ Rows:
2136
+ [
2137
+ { Inputs: ['addrZ'] }
2138
+ ]
2139
+ }
2140
+ ]
2141
+ }
2142
+ ],
2143
+ Descriptors:
2144
+ {
2145
+ addrX: { Name: 'InputX', Hash: 'InputX', DataType: 'String', PictForm: { Section: 'SectionA', Group: 'GroupA1', Row: 1 } },
2146
+ addrY: { Name: 'InputY', Hash: 'InputY', DataType: 'String', PictForm: { Section: 'SectionA', Group: 'GroupA1', Row: 1 } },
2147
+ addrZ: { Name: 'InputZ', Hash: 'InputZ', DataType: 'String', PictForm: { Section: 'SectionB', Group: 'GroupB1', Row: 1 } }
2148
+ }
2149
+ };
2150
+
2151
+ let tmpView = tmpPict.addView('TestCrossDragInput',
2152
+ {
2153
+ ViewIdentifier: 'TestCrossDragInput',
2154
+ ManifestDataAddress: 'AppData.FormConfig',
2155
+ DefaultDestinationAddress: '#FormEditor-Container',
2156
+ Renderables:
2157
+ [
2158
+ {
2159
+ RenderableHash: 'FormEditor-Container',
2160
+ TemplateHash: 'FormEditor-Container-Template',
2161
+ DestinationAddress: '#FormEditor-Container',
2162
+ RenderMethod: 'replace'
2163
+ }
2164
+ ]
2165
+ }, libPictSectionFormEditor);
2166
+
2167
+ tmpView.initialize();
2168
+ tmpView.render();
2169
+ tmpView._DragAndDropEnabled = true;
2170
+
2171
+ // Move addrY from Section A / Group A1 / Row 0 to Section B / Group B1 / Row 0 (drop on addrZ position)
2172
+ tmpView._DragState = { Type: 'input', Indices: [0, 0, 0, 1] };
2173
+ tmpView._DragDropProvider.onDrop(
2174
+ { preventDefault: function() {} },
2175
+ 'input', 1, 0, 0, 0
2176
+ );
2177
+
2178
+ var tmpManifest = tmpPict.AppData.FormConfig;
2179
+ // Source row should have 1 input
2180
+ Expect(tmpManifest.Sections[0].Groups[0].Rows[0].Inputs.length).to.equal(1);
2181
+ Expect(tmpManifest.Sections[0].Groups[0].Rows[0].Inputs[0]).to.equal('addrX');
2182
+ // Target row should have 2 inputs (addrY inserted before addrZ)
2183
+ Expect(tmpManifest.Sections[1].Groups[0].Rows[0].Inputs.length).to.equal(2);
2184
+ Expect(tmpManifest.Sections[1].Groups[0].Rows[0].Inputs[0]).to.equal('addrY');
2185
+ Expect(tmpManifest.Sections[1].Groups[0].Rows[0].Inputs[1]).to.equal('addrZ');
2186
+
2187
+ // Verify PictForm metadata was updated
2188
+ Expect(tmpManifest.Descriptors.addrY.PictForm.Section).to.equal('SectionB');
2189
+ Expect(tmpManifest.Descriptors.addrY.PictForm.Group).to.equal('GroupB1');
2190
+ Expect(tmpManifest.Descriptors.addrY.PictForm.Row).to.equal(1);
2191
+
2192
+ fDone();
2193
+ }
2194
+ );
2195
+ }
2196
+ );
2197
+ suite
2198
+ (
2199
+ 'DataType Icons',
2200
+ function ()
2201
+ {
2202
+ test
2203
+ (
2204
+ 'Should register DataType icons for all 11 Manyfest DataTypes',
2205
+ function ()
2206
+ {
2207
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
2208
+ var tmpProvider = new libIconography();
2209
+
2210
+ var tmpExpectedTypes = ['String', 'Number', 'Float', 'Integer', 'PreciseNumber', 'Boolean', 'Binary', 'DateTime', 'Array', 'Object', 'Null'];
2211
+ var tmpHashes = tmpProvider.getDataTypeIconHashes();
2212
+
2213
+ Expect(tmpHashes).to.be.an('array');
2214
+ Expect(tmpHashes.length).to.equal(11);
2215
+
2216
+ for (var i = 0; i < tmpExpectedTypes.length; i++)
2217
+ {
2218
+ Expect(tmpProvider.hasDataTypeIcon(tmpExpectedTypes[i])).to.be.true;
2219
+ }
2220
+ }
2221
+ );
2222
+ test
2223
+ (
2224
+ 'Should return SVG strings for all DataType icons',
2225
+ function ()
2226
+ {
2227
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
2228
+ var tmpProvider = new libIconography();
2229
+
2230
+ var tmpHashes = tmpProvider.getDataTypeIconHashes();
2231
+ for (var i = 0; i < tmpHashes.length; i++)
2232
+ {
2233
+ var tmpSVG = tmpProvider.getDataTypeIcon(tmpHashes[i]);
2234
+ Expect(tmpSVG).to.be.a('string');
2235
+ Expect(tmpSVG.indexOf('<svg')).to.equal(0);
2236
+ Expect(tmpSVG.indexOf('</svg>')).to.be.above(0);
2237
+ }
2238
+ }
2239
+ );
2240
+ test
2241
+ (
2242
+ 'Should return SVG strings at custom sizes',
2243
+ function ()
2244
+ {
2245
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
2246
+ var tmpProvider = new libIconography();
2247
+
2248
+ var tmpSVG12 = tmpProvider.getDataTypeIcon('String', 12);
2249
+ var tmpSVG32 = tmpProvider.getDataTypeIcon('String', 32);
2250
+
2251
+ Expect(tmpSVG12).to.contain('width="12"');
2252
+ Expect(tmpSVG32).to.contain('width="32"');
2253
+ }
2254
+ );
2255
+ test
2256
+ (
2257
+ 'Should return empty string for unknown DataType',
2258
+ function ()
2259
+ {
2260
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
2261
+ var tmpProvider = new libIconography();
2262
+
2263
+ var tmpSVG = tmpProvider.getDataTypeIcon('CompletelyFakeType');
2264
+ Expect(tmpSVG).to.equal('');
2265
+ }
2266
+ );
2267
+ test
2268
+ (
2269
+ 'Should allow overriding DataType icons via setDataTypeIcon',
2270
+ function ()
2271
+ {
2272
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
2273
+ var tmpProvider = new libIconography();
2274
+
2275
+ var tmpCustomFactory = function(pSize, pColors, pSW)
2276
+ {
2277
+ return '<svg class="custom-datatype" width="' + pSize + '"></svg>';
2278
+ };
2279
+
2280
+ tmpProvider.setDataTypeIcon('String', tmpCustomFactory);
2281
+ var tmpSVG = tmpProvider.getDataTypeIcon('String');
2282
+ Expect(tmpSVG).to.contain('class="custom-datatype"');
2283
+ }
2284
+ );
2285
+ test
2286
+ (
2287
+ 'hasDataTypeIcon should return correct booleans',
2288
+ function ()
2289
+ {
2290
+ var libIconography = libPictSectionFormEditor.IconographyProvider;
2291
+ var tmpProvider = new libIconography();
2292
+
2293
+ Expect(tmpProvider.hasDataTypeIcon('String')).to.be.true;
2294
+ Expect(tmpProvider.hasDataTypeIcon('Boolean')).to.be.true;
2295
+ Expect(tmpProvider.hasDataTypeIcon('FakeType')).to.be.false;
2296
+ }
2297
+ );
2298
+ }
2299
+ );
2300
+ suite
2301
+ (
2302
+ 'Input Selection and Display Mode',
2303
+ function ()
2304
+ {
2305
+ test
2306
+ (
2307
+ 'Should have default input display mode of name',
2308
+ function ()
2309
+ {
2310
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2311
+ let tmpView = tmpPict.addView('TestDisplayMode',
2312
+ {
2313
+ ViewIdentifier: 'TestDisplayMode',
2314
+ ManifestDataAddress: 'AppData.FormConfig'
2315
+ }, libPictSectionFormEditor);
2316
+
2317
+ Expect(tmpView._InputDisplayMode).to.equal('name');
2318
+ }
2319
+ );
2320
+ test
2321
+ (
2322
+ 'Should toggle input display mode via setInputDisplayMode',
2323
+ function ()
2324
+ {
2325
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2326
+ tmpPict.AppData.FormConfig =
2327
+ {
2328
+ Scope: 'TestDisplayToggle',
2329
+ Sections: [],
2330
+ Descriptors: {}
2331
+ };
2332
+
2333
+ let tmpView = tmpPict.addView('TestDisplayToggle',
2334
+ {
2335
+ ViewIdentifier: 'TestDisplayToggle',
2336
+ ManifestDataAddress: 'AppData.FormConfig'
2337
+ }, libPictSectionFormEditor);
2338
+
2339
+ Expect(tmpView._InputDisplayMode).to.equal('name');
2340
+
2341
+ tmpView._UtilitiesProvider.setInputDisplayMode('hash');
2342
+ Expect(tmpView._InputDisplayMode).to.equal('hash');
2343
+
2344
+ tmpView._UtilitiesProvider.setInputDisplayMode('name');
2345
+ Expect(tmpView._InputDisplayMode).to.equal('name');
2346
+ }
2347
+ );
2348
+ test
2349
+ (
2350
+ 'Should ignore invalid display mode values',
2351
+ function ()
2352
+ {
2353
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2354
+ let tmpView = tmpPict.addView('TestDisplayModeInvalid',
2355
+ {
2356
+ ViewIdentifier: 'TestDisplayModeInvalid',
2357
+ ManifestDataAddress: 'AppData.FormConfig'
2358
+ }, libPictSectionFormEditor);
2359
+
2360
+ tmpView._UtilitiesProvider.setInputDisplayMode('invalid');
2361
+ Expect(tmpView._InputDisplayMode).to.equal('name');
2362
+ }
2363
+ );
2364
+ test
2365
+ (
2366
+ 'Should have null selected input by default',
2367
+ function ()
2368
+ {
2369
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2370
+ let tmpView = tmpPict.addView('TestSelection',
2371
+ {
2372
+ ViewIdentifier: 'TestSelection',
2373
+ ManifestDataAddress: 'AppData.FormConfig'
2374
+ }, libPictSectionFormEditor);
2375
+
2376
+ Expect(tmpView._SelectedInputIndices).to.equal(null);
2377
+ }
2378
+ );
2379
+ test
2380
+ (
2381
+ 'Should track selected input indices via selectInput',
2382
+ function ()
2383
+ {
2384
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2385
+ tmpPict.AppData.FormConfig =
2386
+ {
2387
+ Scope: 'TestSelect',
2388
+ Sections:
2389
+ [
2390
+ {
2391
+ Hash: 'S1',
2392
+ Name: 'Section 1',
2393
+ Groups:
2394
+ [
2395
+ {
2396
+ Hash: 'G1',
2397
+ Name: 'Group 1',
2398
+ Layout: 'Record',
2399
+ Rows:
2400
+ [
2401
+ { Inputs: ['addr1'] }
2402
+ ]
2403
+ }
2404
+ ]
2405
+ }
2406
+ ],
2407
+ Descriptors:
2408
+ {
2409
+ addr1:
2410
+ {
2411
+ Name: 'Input 1',
2412
+ Hash: 'Input1',
2413
+ DataType: 'String',
2414
+ PictForm: { Section: 'S1', Group: 'G1', Row: 1 }
2415
+ }
2416
+ }
2417
+ };
2418
+
2419
+ let tmpView = tmpPict.addView('TestSelectInput',
2420
+ {
2421
+ ViewIdentifier: 'TestSelectInput',
2422
+ ManifestDataAddress: 'AppData.FormConfig'
2423
+ }, libPictSectionFormEditor);
2424
+
2425
+ tmpView._UtilitiesProvider.selectInput(0, 0, 0, 0);
2426
+ Expect(tmpView._SelectedInputIndices).to.be.an('array');
2427
+ Expect(tmpView._SelectedInputIndices).to.deep.equal([0, 0, 0, 0]);
2428
+ }
2429
+ );
2430
+ test
2431
+ (
2432
+ 'Should clear selection via deselectInput',
2433
+ function ()
2434
+ {
2435
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2436
+ tmpPict.AppData.FormConfig =
2437
+ {
2438
+ Scope: 'TestDeselect',
2439
+ Sections: [],
2440
+ Descriptors: {}
2441
+ };
2442
+
2443
+ let tmpView = tmpPict.addView('TestDeselectInput',
2444
+ {
2445
+ ViewIdentifier: 'TestDeselectInput',
2446
+ ManifestDataAddress: 'AppData.FormConfig'
2447
+ }, libPictSectionFormEditor);
2448
+
2449
+ tmpView._SelectedInputIndices = [0, 0, 0, 0];
2450
+ tmpView._UtilitiesProvider.deselectInput();
2451
+ Expect(tmpView._SelectedInputIndices).to.equal(null);
2452
+ }
2453
+ );
2454
+ }
2455
+ );
2456
+ suite
2457
+ (
2458
+ 'Properties Panel',
2459
+ function ()
2460
+ {
2461
+ test
2462
+ (
2463
+ 'Should export the PropertiesPanel class from the module',
2464
+ function ()
2465
+ {
2466
+ Expect(libPictSectionFormEditor.PropertiesPanel).to.be.a('function');
2467
+ }
2468
+ );
2469
+ test
2470
+ (
2471
+ 'Properties panel should instantiate without errors',
2472
+ function ()
2473
+ {
2474
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2475
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
2476
+
2477
+ let tmpView = tmpPict.addView('TestPropsPanel',
2478
+ {
2479
+ ViewIdentifier: 'TestPropsPanel'
2480
+ }, tmpPropertiesPanel);
2481
+
2482
+ Expect(tmpView).to.be.an('object');
2483
+ Expect(tmpView._SelectedInput).to.equal(null);
2484
+ Expect(tmpView._ParentFormEditor).to.equal(null);
2485
+ }
2486
+ );
2487
+ test
2488
+ (
2489
+ 'Properties panel selectInput should set state',
2490
+ function ()
2491
+ {
2492
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2493
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
2494
+
2495
+ let tmpView = tmpPict.addView('TestPropsPanelSelect',
2496
+ {
2497
+ ViewIdentifier: 'TestPropsPanelSelect'
2498
+ }, tmpPropertiesPanel);
2499
+
2500
+ tmpView.selectInput(1, 2, 3, 4);
2501
+ Expect(tmpView._SelectedInput).to.be.an('object');
2502
+ Expect(tmpView._SelectedInput.SectionIndex).to.equal(1);
2503
+ Expect(tmpView._SelectedInput.GroupIndex).to.equal(2);
2504
+ Expect(tmpView._SelectedInput.RowIndex).to.equal(3);
2505
+ Expect(tmpView._SelectedInput.InputIndex).to.equal(4);
2506
+ }
2507
+ );
2508
+ test
2509
+ (
2510
+ 'Properties panel deselectInput should clear state',
2511
+ function ()
2512
+ {
2513
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2514
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
2515
+
2516
+ let tmpView = tmpPict.addView('TestPropsPanelDeselect',
2517
+ {
2518
+ ViewIdentifier: 'TestPropsPanelDeselect'
2519
+ }, tmpPropertiesPanel);
2520
+
2521
+ tmpView.selectInput(0, 0, 0, 0);
2522
+ Expect(tmpView._SelectedInput).to.not.equal(null);
2523
+
2524
+ tmpView.deselectInput();
2525
+ Expect(tmpView._SelectedInput).to.equal(null);
2526
+ }
2527
+ );
2528
+ }
2529
+ );
2530
+ suite
2531
+ (
2532
+ 'Panel State and Tabs',
2533
+ function ()
2534
+ {
2535
+ test
2536
+ (
2537
+ 'Should default _PanelCollapsed to false and _PanelActiveTab to form',
2538
+ function ()
2539
+ {
2540
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2541
+ let tmpView = tmpPict.addView('TestPanelDefaults',
2542
+ {
2543
+ ViewIdentifier: 'TestPanelDefaults',
2544
+ ManifestDataAddress: 'AppData.PanelDefaults'
2545
+ }, libPictSectionFormEditor);
2546
+
2547
+ Expect(tmpView._PanelCollapsed).to.equal(false);
2548
+ Expect(tmpView._PanelActiveTab).to.equal('form');
2549
+ }
2550
+ );
2551
+ test
2552
+ (
2553
+ 'togglePropertiesPanel should flip _PanelCollapsed',
2554
+ function ()
2555
+ {
2556
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2557
+ let tmpView = tmpPict.addView('TestPanelToggle',
2558
+ {
2559
+ ViewIdentifier: 'TestPanelToggle',
2560
+ ManifestDataAddress: 'AppData.PanelToggle'
2561
+ }, libPictSectionFormEditor);
2562
+ tmpView.initialize();
2563
+
2564
+ Expect(tmpView._PanelCollapsed).to.equal(false);
2565
+
2566
+ tmpView._UtilitiesProvider.togglePropertiesPanel();
2567
+ Expect(tmpView._PanelCollapsed).to.equal(true);
2568
+
2569
+ tmpView._UtilitiesProvider.togglePropertiesPanel();
2570
+ Expect(tmpView._PanelCollapsed).to.equal(false);
2571
+ }
2572
+ );
2573
+ test
2574
+ (
2575
+ 'setPanelTab should switch active tab',
2576
+ function ()
2577
+ {
2578
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2579
+ let tmpView = tmpPict.addView('TestSetPanelTab',
2580
+ {
2581
+ ViewIdentifier: 'TestSetPanelTab',
2582
+ ManifestDataAddress: 'AppData.SetPanelTab'
2583
+ }, libPictSectionFormEditor);
2584
+ tmpView.initialize();
2585
+
2586
+ Expect(tmpView._PanelActiveTab).to.equal('form');
2587
+
2588
+ tmpView._UtilitiesProvider.setPanelTab('properties');
2589
+ Expect(tmpView._PanelActiveTab).to.equal('properties');
2590
+
2591
+ tmpView._UtilitiesProvider.setPanelTab('form');
2592
+ Expect(tmpView._PanelActiveTab).to.equal('form');
2593
+ }
2594
+ );
2595
+ test
2596
+ (
2597
+ 'setPanelTab should ignore invalid tab names',
2598
+ function ()
2599
+ {
2600
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2601
+ let tmpView = tmpPict.addView('TestSetPanelTabInvalid',
2602
+ {
2603
+ ViewIdentifier: 'TestSetPanelTabInvalid',
2604
+ ManifestDataAddress: 'AppData.SetPanelTabInvalid'
2605
+ }, libPictSectionFormEditor);
2606
+ tmpView.initialize();
2607
+
2608
+ tmpView._UtilitiesProvider.setPanelTab('properties');
2609
+ Expect(tmpView._PanelActiveTab).to.equal('properties');
2610
+
2611
+ tmpView._UtilitiesProvider.setPanelTab('invalid');
2612
+ Expect(tmpView._PanelActiveTab).to.equal('properties');
2613
+
2614
+ tmpView._UtilitiesProvider.setPanelTab('');
2615
+ Expect(tmpView._PanelActiveTab).to.equal('properties');
2616
+
2617
+ tmpView._UtilitiesProvider.setPanelTab(null);
2618
+ Expect(tmpView._PanelActiveTab).to.equal('properties');
2619
+ }
2620
+ );
2621
+ test
2622
+ (
2623
+ 'selectInput should auto-switch to properties tab and expand panel',
2624
+ function ()
2625
+ {
2626
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2627
+ let tmpView = tmpPict.addView('TestSelectAutoSwitch',
2628
+ {
2629
+ ViewIdentifier: 'TestSelectAutoSwitch',
2630
+ ManifestDataAddress: 'AppData.SelectAutoSwitch'
2631
+ }, libPictSectionFormEditor);
2632
+ tmpView.initialize();
2633
+
2634
+ // Start with form tab and collapsed panel
2635
+ tmpView._PanelActiveTab = 'form';
2636
+ tmpView._PanelCollapsed = true;
2637
+
2638
+ // Add a section/group/row/input so selectInput has something to select
2639
+ tmpView._ManifestOpsProvider.addSection();
2640
+ tmpView._ManifestOpsProvider.addGroup(0);
2641
+ tmpView._ManifestOpsProvider.addRow(0, 0);
2642
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
2643
+
2644
+ tmpView._UtilitiesProvider.selectInput(0, 0, 0, 0);
2645
+
2646
+ Expect(tmpView._PanelActiveTab).to.equal('properties');
2647
+ Expect(tmpView._PanelCollapsed).to.equal(false);
2648
+ }
2649
+ );
2650
+ test
2651
+ (
2652
+ 'deselectInput should NOT collapse the panel',
2653
+ function ()
2654
+ {
2655
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2656
+ let tmpView = tmpPict.addView('TestDeselectNoCollapse',
2657
+ {
2658
+ ViewIdentifier: 'TestDeselectNoCollapse',
2659
+ ManifestDataAddress: 'AppData.DeselectNoCollapse'
2660
+ }, libPictSectionFormEditor);
2661
+ tmpView.initialize();
2662
+
2663
+ tmpView._PanelCollapsed = false;
2664
+ tmpView._PanelActiveTab = 'properties';
2665
+
2666
+ tmpView._ManifestOpsProvider.addSection();
2667
+ tmpView._ManifestOpsProvider.addGroup(0);
2668
+ tmpView._ManifestOpsProvider.addRow(0, 0);
2669
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
2670
+
2671
+ tmpView._UtilitiesProvider.selectInput(0, 0, 0, 0);
2672
+ tmpView._UtilitiesProvider.deselectInput();
2673
+
2674
+ Expect(tmpView._PanelCollapsed).to.equal(false);
2675
+ Expect(tmpView._SelectedInputIndices).to.equal(null);
2676
+ }
2677
+ );
2678
+ test
2679
+ (
2680
+ 'getFormStats should return correct counts',
2681
+ function ()
2682
+ {
2683
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2684
+ let tmpView = tmpPict.addView('TestFormStats',
2685
+ {
2686
+ ViewIdentifier: 'TestFormStats',
2687
+ ManifestDataAddress: 'AppData.FormStats'
2688
+ }, libPictSectionFormEditor);
2689
+ tmpView.initialize();
2690
+
2691
+ // Empty manifest
2692
+ let tmpStats = tmpView._UtilitiesProvider.getFormStats();
2693
+ Expect(tmpStats.Sections).to.equal(0);
2694
+ Expect(tmpStats.Groups).to.equal(0);
2695
+ Expect(tmpStats.Inputs).to.equal(0);
2696
+ Expect(tmpStats.Descriptors).to.equal(0);
2697
+
2698
+ // Add content
2699
+ tmpView._ManifestOpsProvider.addSection();
2700
+ tmpView._ManifestOpsProvider.addSection();
2701
+ tmpView._ManifestOpsProvider.addGroup(0);
2702
+ tmpView._ManifestOpsProvider.addGroup(1);
2703
+ tmpView._ManifestOpsProvider.addRow(0, 0);
2704
+ tmpView._ManifestOpsProvider.addRow(1, 0);
2705
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
2706
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
2707
+ tmpView._ManifestOpsProvider.addInput(1, 0, 0);
2708
+
2709
+ tmpStats = tmpView._UtilitiesProvider.getFormStats();
2710
+ Expect(tmpStats.Sections).to.equal(2);
2711
+ // Each addSection() creates 1 default group, + addGroup() adds 1 more each = 4 total
2712
+ Expect(tmpStats.Groups).to.equal(4);
2713
+ Expect(tmpStats.Inputs).to.equal(3);
2714
+ Expect(tmpStats.Descriptors).to.equal(3);
2715
+ }
2716
+ );
2717
+ test
2718
+ (
2719
+ 'getFormStats should return zeros for empty manifest',
2720
+ function ()
2721
+ {
2722
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2723
+ let tmpView = tmpPict.addView('TestFormStatsEmpty',
2724
+ {
2725
+ ViewIdentifier: 'TestFormStatsEmpty',
2726
+ ManifestDataAddress: 'AppData.FormStatsEmpty'
2727
+ }, libPictSectionFormEditor);
2728
+ tmpView.initialize();
2729
+
2730
+ // Clear manifest entirely
2731
+ tmpView._setManifestData({});
2732
+
2733
+ let tmpStats = tmpView._UtilitiesProvider.getFormStats();
2734
+ Expect(tmpStats.Sections).to.equal(0);
2735
+ Expect(tmpStats.Groups).to.equal(0);
2736
+ Expect(tmpStats.Inputs).to.equal(0);
2737
+ Expect(tmpStats.Descriptors).to.equal(0);
2738
+ }
2739
+ );
2740
+ test
2741
+ (
2742
+ 'getAllInputEntries should enumerate all inputs with indices',
2743
+ function ()
2744
+ {
2745
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2746
+ let tmpView = tmpPict.addView('TestAllInputEntries',
2747
+ {
2748
+ ViewIdentifier: 'TestAllInputEntries',
2749
+ ManifestDataAddress: 'AppData.AllInputEntries'
2750
+ }, libPictSectionFormEditor);
2751
+ tmpView.initialize();
2752
+
2753
+ // Empty manifest
2754
+ let tmpEntries = tmpView._UtilitiesProvider.getAllInputEntries();
2755
+ Expect(tmpEntries).to.be.an('array');
2756
+ Expect(tmpEntries.length).to.equal(0);
2757
+
2758
+ // Add content
2759
+ tmpView._ManifestOpsProvider.addSection();
2760
+ tmpView._ManifestOpsProvider.addGroup(0);
2761
+ tmpView._ManifestOpsProvider.addRow(0, 0);
2762
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
2763
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
2764
+
2765
+ tmpEntries = tmpView._UtilitiesProvider.getAllInputEntries();
2766
+ Expect(tmpEntries.length).to.equal(2);
2767
+
2768
+ // Check structure of each entry
2769
+ Expect(tmpEntries[0].SectionIndex).to.equal(0);
2770
+ Expect(tmpEntries[0].GroupIndex).to.equal(0);
2771
+ Expect(tmpEntries[0].RowIndex).to.equal(0);
2772
+ Expect(tmpEntries[0].InputIndex).to.equal(0);
2773
+ Expect(tmpEntries[0].Address).to.be.a('string');
2774
+ Expect(tmpEntries[0].Label).to.be.a('string');
2775
+ Expect(tmpEntries[0].SectionName).to.be.a('string');
2776
+
2777
+ Expect(tmpEntries[1].InputIndex).to.equal(1);
2778
+ }
2779
+ );
2780
+ test
2781
+ (
2782
+ 'onInputSelectorChange should forward selection to parent',
2783
+ function ()
2784
+ {
2785
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2786
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
2787
+
2788
+ let tmpFormEditor = tmpPict.addView('TestSelectorParent',
2789
+ {
2790
+ ViewIdentifier: 'TestSelectorParent',
2791
+ ManifestDataAddress: 'AppData.SelectorParent'
2792
+ }, libPictSectionFormEditor);
2793
+ tmpFormEditor.initialize();
2794
+
2795
+ tmpFormEditor._ManifestOpsProvider.addSection();
2796
+ tmpFormEditor._ManifestOpsProvider.addGroup(0);
2797
+ tmpFormEditor._ManifestOpsProvider.addRow(0, 0);
2798
+ tmpFormEditor._ManifestOpsProvider.addInput(0, 0, 0);
2799
+
2800
+ let tmpPanel = tmpPict.addView('TestSelectorPanel',
2801
+ {
2802
+ ViewIdentifier: 'TestSelectorPanel'
2803
+ }, tmpPropertiesPanel);
2804
+ tmpPanel._ParentFormEditor = tmpFormEditor;
2805
+
2806
+ // Select via the selector
2807
+ tmpPanel.onInputSelectorChange('0,0,0,0');
2808
+
2809
+ Expect(tmpFormEditor._SelectedInputIndices).to.be.an('array');
2810
+ Expect(tmpFormEditor._SelectedInputIndices[0]).to.equal(0);
2811
+ Expect(tmpFormEditor._SelectedInputIndices[1]).to.equal(0);
2812
+ Expect(tmpFormEditor._SelectedInputIndices[2]).to.equal(0);
2813
+ Expect(tmpFormEditor._SelectedInputIndices[3]).to.equal(0);
2814
+ }
2815
+ );
2816
+ test
2817
+ (
2818
+ 'onInputSelectorChange with empty value should deselect',
2819
+ function ()
2820
+ {
2821
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2822
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
2823
+
2824
+ let tmpFormEditor = tmpPict.addView('TestSelectorDeselect',
2825
+ {
2826
+ ViewIdentifier: 'TestSelectorDeselect',
2827
+ ManifestDataAddress: 'AppData.SelectorDeselect'
2828
+ }, libPictSectionFormEditor);
2829
+ tmpFormEditor.initialize();
2830
+
2831
+ tmpFormEditor._ManifestOpsProvider.addSection();
2832
+ tmpFormEditor._ManifestOpsProvider.addGroup(0);
2833
+ tmpFormEditor._ManifestOpsProvider.addRow(0, 0);
2834
+ tmpFormEditor._ManifestOpsProvider.addInput(0, 0, 0);
2835
+
2836
+ let tmpPanel = tmpPict.addView('TestSelectorDeselectPanel',
2837
+ {
2838
+ ViewIdentifier: 'TestSelectorDeselectPanel'
2839
+ }, tmpPropertiesPanel);
2840
+ tmpPanel._ParentFormEditor = tmpFormEditor;
2841
+
2842
+ // Select first, then deselect
2843
+ tmpFormEditor._UtilitiesProvider.selectInput(0, 0, 0, 0);
2844
+ Expect(tmpFormEditor._SelectedInputIndices).to.not.equal(null);
2845
+
2846
+ tmpPanel.onInputSelectorChange('');
2847
+ Expect(tmpFormEditor._SelectedInputIndices).to.equal(null);
2848
+ }
2849
+ );
2850
+ test
2851
+ (
2852
+ 'onInputSelectorChange should ignore invalid values',
2853
+ function ()
2854
+ {
2855
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2856
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
2857
+
2858
+ let tmpFormEditor = tmpPict.addView('TestSelectorInvalid',
2859
+ {
2860
+ ViewIdentifier: 'TestSelectorInvalid',
2861
+ ManifestDataAddress: 'AppData.SelectorInvalid'
2862
+ }, libPictSectionFormEditor);
2863
+ tmpFormEditor.initialize();
2864
+
2865
+ let tmpPanel = tmpPict.addView('TestSelectorInvalidPanel',
2866
+ {
2867
+ ViewIdentifier: 'TestSelectorInvalidPanel'
2868
+ }, tmpPropertiesPanel);
2869
+ tmpPanel._ParentFormEditor = tmpFormEditor;
2870
+
2871
+ // These should not throw
2872
+ tmpPanel.onInputSelectorChange('bad');
2873
+ tmpPanel.onInputSelectorChange('1,2');
2874
+ tmpPanel.onInputSelectorChange('a,b,c,d');
2875
+
2876
+ Expect(tmpFormEditor._SelectedInputIndices).to.equal(null);
2877
+ }
2878
+ );
2879
+ test
2880
+ (
2881
+ 'scrollToInput should not throw in node environment',
2882
+ function ()
2883
+ {
2884
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2885
+ let tmpView = tmpPict.addView('TestScrollToInput',
2886
+ {
2887
+ ViewIdentifier: 'TestScrollToInput',
2888
+ ManifestDataAddress: 'AppData.ScrollToInput'
2889
+ }, libPictSectionFormEditor);
2890
+ tmpView.initialize();
2891
+
2892
+ // Should not throw even without a DOM
2893
+ tmpView._UtilitiesProvider.scrollToInput(0, 0, 0, 0);
2894
+ }
2895
+ );
2896
+ }
2897
+ );
2898
+ suite
2899
+ (
2900
+ 'InputType Manifests',
2901
+ function ()
2902
+ {
2903
+ test
2904
+ (
2905
+ 'InputType definitions with manifests should have valid Descriptor structures',
2906
+ function ()
2907
+ {
2908
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2909
+ let tmpView = tmpPict.addView('TestManifestStructure',
2910
+ {
2911
+ ViewIdentifier: 'TestManifestStructure',
2912
+ ManifestDataAddress: 'AppData.FormConfig'
2913
+ }, libPictSectionFormEditor);
2914
+
2915
+ let tmpTypesWithManifests = ['Option', 'PreciseNumberReadOnly', 'Chart', 'TabSectionSelector', 'TabGroupSelector', 'Templated', 'TemplatedEntityLookup'];
2916
+
2917
+ for (let i = 0; i < tmpView._InputTypeDefinitions.length; i++)
2918
+ {
2919
+ let tmpDef = tmpView._InputTypeDefinitions[i];
2920
+ if (tmpDef.Manifest)
2921
+ {
2922
+ Expect(tmpDef.Manifest).to.be.an('object');
2923
+ Expect(tmpDef.Manifest.Descriptors).to.be.an('object');
2924
+
2925
+ let tmpKeys = Object.keys(tmpDef.Manifest.Descriptors);
2926
+ Expect(tmpKeys.length).to.be.greaterThan(0);
2927
+
2928
+ for (let j = 0; j < tmpKeys.length; j++)
2929
+ {
2930
+ let tmpDesc = tmpDef.Manifest.Descriptors[tmpKeys[j]];
2931
+ Expect(tmpDesc).to.be.an('object');
2932
+ Expect(tmpDesc.Name).to.be.a('string');
2933
+ Expect(tmpDesc.Hash).to.be.a('string');
2934
+ Expect(tmpDesc.DataType).to.be.a('string');
2935
+ Expect(tmpDesc.Description).to.be.a('string');
2936
+ }
2937
+ }
2938
+ }
2939
+ }
2940
+ );
2941
+ test
2942
+ (
2943
+ '_getInputTypeManifest should return manifest for types that have one',
2944
+ function ()
2945
+ {
2946
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2947
+ let tmpView = tmpPict.addView('TestGetManifest',
2948
+ {
2949
+ ViewIdentifier: 'TestGetManifest',
2950
+ ManifestDataAddress: 'AppData.FormConfig'
2951
+ }, libPictSectionFormEditor);
2952
+
2953
+ let tmpOptionManifest = tmpView._UtilitiesProvider._getInputTypeManifest('Option');
2954
+ Expect(tmpOptionManifest).to.be.an('object');
2955
+ Expect(tmpOptionManifest.Descriptors).to.be.an('object');
2956
+ Expect(tmpOptionManifest.Descriptors['SelectOptions']).to.be.an('object');
2957
+ Expect(tmpOptionManifest.Descriptors['SelectOptionsPickList']).to.be.an('object');
2958
+
2959
+ let tmpChartManifest = tmpView._UtilitiesProvider._getInputTypeManifest('Chart');
2960
+ Expect(tmpChartManifest).to.be.an('object');
2961
+ Expect(tmpChartManifest.Descriptors['ChartType']).to.be.an('object');
2962
+
2963
+ let tmpPNROManifest = tmpView._UtilitiesProvider._getInputTypeManifest('PreciseNumberReadOnly');
2964
+ Expect(tmpPNROManifest).to.be.an('object');
2965
+ Expect(tmpPNROManifest.Descriptors['DecimalPrecision']).to.be.an('object');
2966
+ Expect(tmpPNROManifest.Descriptors['AddCommas']).to.be.an('object');
2967
+ Expect(tmpPNROManifest.Descriptors['DigitsPrefix']).to.be.an('object');
2968
+ Expect(tmpPNROManifest.Descriptors['DigitsPostfix']).to.be.an('object');
2969
+ }
2970
+ );
2971
+ test
2972
+ (
2973
+ '_getInputTypeManifest should return null for types without a manifest',
2974
+ function ()
2975
+ {
2976
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2977
+ let tmpView = tmpPict.addView('TestGetManifestNull',
2978
+ {
2979
+ ViewIdentifier: 'TestGetManifestNull',
2980
+ ManifestDataAddress: 'AppData.FormConfig'
2981
+ }, libPictSectionFormEditor);
2982
+
2983
+ Expect(tmpView._UtilitiesProvider._getInputTypeManifest('TextArea')).to.equal(null);
2984
+ Expect(tmpView._UtilitiesProvider._getInputTypeManifest('Boolean')).to.equal(null);
2985
+ Expect(tmpView._UtilitiesProvider._getInputTypeManifest('Hidden')).to.equal(null);
2986
+ Expect(tmpView._UtilitiesProvider._getInputTypeManifest('Link')).to.equal(null);
2987
+ Expect(tmpView._UtilitiesProvider._getInputTypeManifest('')).to.equal(null);
2988
+ Expect(tmpView._UtilitiesProvider._getInputTypeManifest(null)).to.equal(null);
2989
+ Expect(tmpView._UtilitiesProvider._getInputTypeManifest('NonExistent')).to.equal(null);
2990
+ }
2991
+ );
2992
+ test
2993
+ (
2994
+ 'commitPictFormChange should update PictForm properties on the Descriptor',
2995
+ function ()
2996
+ {
2997
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
2998
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
2999
+
3000
+ let tmpView = tmpPict.addView('TestCommitPictForm',
3001
+ {
3002
+ ViewIdentifier: 'TestCommitPictForm'
3003
+ }, tmpPropertiesPanel);
3004
+
3005
+ // Create a mock parent form editor
3006
+ let tmpFormEditor = tmpPict.addView('TestCommitPictFormParent',
3007
+ {
3008
+ ViewIdentifier: 'TestCommitPictFormParent',
3009
+ ManifestDataAddress: 'AppData.TestCommitManifest'
3010
+ }, libPictSectionFormEditor);
3011
+ tmpFormEditor.initialize();
3012
+
3013
+ // Set up manifest data with a section/group/row/input
3014
+ tmpFormEditor._ManifestOpsProvider.addSection();
3015
+ tmpFormEditor._ManifestOpsProvider.addGroup(0);
3016
+ tmpFormEditor._ManifestOpsProvider.addRow(0, 0);
3017
+ tmpFormEditor._ManifestOpsProvider.addInput(0, 0, 0);
3018
+
3019
+ // Wire the properties panel to the form editor
3020
+ tmpView._ParentFormEditor = tmpFormEditor;
3021
+ tmpView.selectInput(0, 0, 0, 0);
3022
+
3023
+ // Test String property
3024
+ tmpView.commitPictFormChange('Template', 'Hello {~D:Name~}', 'String');
3025
+ let tmpManifest = tmpFormEditor._resolveManifestData();
3026
+ let tmpAddress = tmpManifest.Sections[0].Groups[0].Rows[0].Inputs[0];
3027
+ let tmpDescriptor = tmpManifest.Descriptors[tmpAddress];
3028
+ Expect(tmpDescriptor.PictForm.Template).to.equal('Hello {~D:Name~}');
3029
+
3030
+ // Test Number property
3031
+ tmpView.commitPictFormChange('DecimalPrecision', '2', 'Number');
3032
+ Expect(tmpDescriptor.PictForm.DecimalPrecision).to.equal(2);
3033
+
3034
+ // Test Boolean property
3035
+ tmpView.commitPictFormChange('AddCommas', true, 'Boolean');
3036
+ Expect(tmpDescriptor.PictForm.AddCommas).to.equal(true);
3037
+
3038
+ // Test empty Number removes property
3039
+ tmpView.commitPictFormChange('DecimalPrecision', '', 'Number');
3040
+ Expect(tmpDescriptor.PictForm.hasOwnProperty('DecimalPrecision')).to.equal(false);
3041
+
3042
+ // Test empty String removes property
3043
+ tmpView.commitPictFormChange('Template', '', 'String');
3044
+ Expect(tmpDescriptor.PictForm.hasOwnProperty('Template')).to.equal(false);
3045
+
3046
+ // Test JSON string auto-parsing
3047
+ tmpView.commitPictFormChange('SelectOptions', '[{"id":"1","text":"One"}]', 'String');
3048
+ Expect(Array.isArray(tmpDescriptor.PictForm.SelectOptions)).to.equal(true);
3049
+ Expect(tmpDescriptor.PictForm.SelectOptions[0].id).to.equal('1');
3050
+ }
3051
+ );
3052
+ test
3053
+ (
3054
+ 'TabGroupSelector and TabSectionSelector should have correct manifest properties',
3055
+ function ()
3056
+ {
3057
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3058
+ let tmpView = tmpPict.addView('TestTabManifests',
3059
+ {
3060
+ ViewIdentifier: 'TestTabManifests',
3061
+ ManifestDataAddress: 'AppData.FormConfig'
3062
+ }, libPictSectionFormEditor);
3063
+
3064
+ let tmpTabGroupManifest = tmpView._UtilitiesProvider._getInputTypeManifest('TabGroupSelector');
3065
+ Expect(tmpTabGroupManifest.Descriptors['TabGroupSet']).to.be.an('object');
3066
+ Expect(tmpTabGroupManifest.Descriptors['DefaultTabGroupHash']).to.be.an('object');
3067
+ Expect(tmpTabGroupManifest.Descriptors['DefaultFromData']).to.be.an('object');
3068
+ Expect(tmpTabGroupManifest.Descriptors['DefaultFromData'].DataType).to.equal('Boolean');
3069
+
3070
+ let tmpTabSectionManifest = tmpView._UtilitiesProvider._getInputTypeManifest('TabSectionSelector');
3071
+ Expect(tmpTabSectionManifest.Descriptors['TabSectionSet']).to.be.an('object');
3072
+ Expect(tmpTabSectionManifest.Descriptors['DefaultTabSectionHash']).to.be.an('object');
3073
+ Expect(tmpTabSectionManifest.Descriptors['DefaultFromData']).to.be.an('object');
3074
+ }
3075
+ );
3076
+ test
3077
+ (
3078
+ 'Custom InputType definitions with manifests should merge correctly',
3079
+ function ()
3080
+ {
3081
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3082
+ let tmpView = tmpPict.addView('TestCustomManifests',
3083
+ {
3084
+ ViewIdentifier: 'TestCustomManifests',
3085
+ ManifestDataAddress: 'AppData.FormConfig',
3086
+ InputTypeDefinitions:
3087
+ [
3088
+ {
3089
+ Hash: 'CustomWidget',
3090
+ Name: 'Custom Widget',
3091
+ Description: 'A custom widget',
3092
+ Category: 'Custom',
3093
+ Manifest:
3094
+ {
3095
+ Descriptors:
3096
+ {
3097
+ 'WidgetColor': { Name: 'Widget Color', Hash: 'WidgetColor', DataType: 'String', Description: 'The color of the widget' }
3098
+ }
3099
+ }
3100
+ }
3101
+ ]
3102
+ }, libPictSectionFormEditor);
3103
+
3104
+ let tmpCustomManifest = tmpView._UtilitiesProvider._getInputTypeManifest('CustomWidget');
3105
+ Expect(tmpCustomManifest).to.be.an('object');
3106
+ Expect(tmpCustomManifest.Descriptors['WidgetColor']).to.be.an('object');
3107
+ Expect(tmpCustomManifest.Descriptors['WidgetColor'].DataType).to.equal('String');
3108
+ }
3109
+ );
3110
+ }
3111
+ );
3112
+ suite
3113
+ (
3114
+ 'ReferenceManifest Management',
3115
+ function ()
3116
+ {
3117
+ test
3118
+ (
3119
+ 'createReferenceManifest should create entry with Scope and empty Descriptors',
3120
+ function ()
3121
+ {
3122
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3123
+ tmpPict.AppData = {};
3124
+
3125
+ let tmpView = tmpPict.addView('TestRefManifestCreate',
3126
+ {
3127
+ ViewIdentifier: 'TestRefManifestCreate',
3128
+ ManifestDataAddress: 'AppData.FormConfig'
3129
+ }, libPictSectionFormEditor);
3130
+
3131
+ tmpView.initialize();
3132
+
3133
+ let tmpName = tmpView._ManifestOpsProvider.createReferenceManifest('MyManifest');
3134
+ Expect(tmpName).to.equal('MyManifest');
3135
+
3136
+ let tmpManifest = tmpView._resolveManifestData();
3137
+ Expect(tmpManifest.ReferenceManifests).to.be.an('object');
3138
+ Expect(tmpManifest.ReferenceManifests['MyManifest']).to.be.an('object');
3139
+ Expect(tmpManifest.ReferenceManifests['MyManifest'].Scope).to.equal('MyManifest');
3140
+ Expect(tmpManifest.ReferenceManifests['MyManifest'].Descriptors).to.be.an('object');
3141
+ Expect(Object.keys(tmpManifest.ReferenceManifests['MyManifest'].Descriptors).length).to.equal(0);
3142
+ }
3143
+ );
3144
+ test
3145
+ (
3146
+ 'createReferenceManifest should generate unique names for duplicates',
3147
+ function ()
3148
+ {
3149
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3150
+ tmpPict.AppData = {};
3151
+
3152
+ let tmpView = tmpPict.addView('TestRefManifestDup',
3153
+ {
3154
+ ViewIdentifier: 'TestRefManifestDup',
3155
+ ManifestDataAddress: 'AppData.FormConfig'
3156
+ }, libPictSectionFormEditor);
3157
+
3158
+ tmpView.initialize();
3159
+
3160
+ let tmpName1 = tmpView._ManifestOpsProvider.createReferenceManifest('TestManifest');
3161
+ let tmpName2 = tmpView._ManifestOpsProvider.createReferenceManifest('TestManifest');
3162
+ let tmpName3 = tmpView._ManifestOpsProvider.createReferenceManifest('TestManifest');
3163
+
3164
+ Expect(tmpName1).to.equal('TestManifest');
3165
+ Expect(tmpName2).to.equal('TestManifest_2');
3166
+ Expect(tmpName3).to.equal('TestManifest_3');
3167
+
3168
+ let tmpNames = tmpView._ManifestOpsProvider.getReferenceManifestNames();
3169
+ Expect(tmpNames.length).to.equal(3);
3170
+ }
3171
+ );
3172
+ test
3173
+ (
3174
+ 'getReferenceManifestNames should return list of manifest names',
3175
+ function ()
3176
+ {
3177
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3178
+ tmpPict.AppData = {};
3179
+
3180
+ let tmpView = tmpPict.addView('TestRefManifestNames',
3181
+ {
3182
+ ViewIdentifier: 'TestRefManifestNames',
3183
+ ManifestDataAddress: 'AppData.FormConfig'
3184
+ }, libPictSectionFormEditor);
3185
+
3186
+ tmpView.initialize();
3187
+
3188
+ let tmpNames = tmpView._ManifestOpsProvider.getReferenceManifestNames();
3189
+ Expect(tmpNames).to.be.an('array');
3190
+ Expect(tmpNames.length).to.equal(0);
3191
+
3192
+ tmpView._ManifestOpsProvider.createReferenceManifest('Alpha');
3193
+ tmpView._ManifestOpsProvider.createReferenceManifest('Beta');
3194
+
3195
+ tmpNames = tmpView._ManifestOpsProvider.getReferenceManifestNames();
3196
+ Expect(tmpNames.length).to.equal(2);
3197
+ Expect(tmpNames).to.include('Alpha');
3198
+ Expect(tmpNames).to.include('Beta');
3199
+ }
3200
+ );
3201
+ test
3202
+ (
3203
+ '_resolveReferenceManifest should return correct object or null',
3204
+ function ()
3205
+ {
3206
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3207
+ tmpPict.AppData = {};
3208
+
3209
+ let tmpView = tmpPict.addView('TestResolveRef',
3210
+ {
3211
+ ViewIdentifier: 'TestResolveRef',
3212
+ ManifestDataAddress: 'AppData.FormConfig'
3213
+ }, libPictSectionFormEditor);
3214
+
3215
+ tmpView.initialize();
3216
+
3217
+ Expect(tmpView._ManifestOpsProvider._resolveReferenceManifest('NonExistent')).to.equal(null);
3218
+
3219
+ tmpView._ManifestOpsProvider.createReferenceManifest('Existing');
3220
+
3221
+ let tmpResolved = tmpView._ManifestOpsProvider._resolveReferenceManifest('Existing');
3222
+ Expect(tmpResolved).to.be.an('object');
3223
+ Expect(tmpResolved.Scope).to.equal('Existing');
3224
+ }
3225
+ );
3226
+ test
3227
+ (
3228
+ 'bindReferenceManifest should set group RecordManifest',
3229
+ function ()
3230
+ {
3231
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3232
+ tmpPict.AppData = {};
3233
+
3234
+ let tmpView = tmpPict.addView('TestBindRef',
3235
+ {
3236
+ ViewIdentifier: 'TestBindRef',
3237
+ ManifestDataAddress: 'AppData.FormConfig'
3238
+ }, libPictSectionFormEditor);
3239
+
3240
+ tmpView.initialize();
3241
+ tmpView._ManifestOpsProvider.addSection();
3242
+ tmpView._ManifestOpsProvider.addGroup(0);
3243
+ tmpView._ManifestOpsProvider.createReferenceManifest('TestBind');
3244
+
3245
+ tmpView._ManifestOpsProvider.bindReferenceManifest(0, 0, 'TestBind');
3246
+
3247
+ let tmpManifest = tmpView._resolveManifestData();
3248
+ Expect(tmpManifest.Sections[0].Groups[0].RecordManifest).to.equal('TestBind');
3249
+ }
3250
+ );
3251
+ test
3252
+ (
3253
+ 'unbindReferenceManifest should clear group RecordManifest',
3254
+ function ()
3255
+ {
3256
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3257
+ tmpPict.AppData = {};
3258
+
3259
+ let tmpView = tmpPict.addView('TestUnbindRef',
3260
+ {
3261
+ ViewIdentifier: 'TestUnbindRef',
3262
+ ManifestDataAddress: 'AppData.FormConfig'
3263
+ }, libPictSectionFormEditor);
3264
+
3265
+ tmpView.initialize();
3266
+ tmpView._ManifestOpsProvider.addSection();
3267
+ tmpView._ManifestOpsProvider.addGroup(0);
3268
+ tmpView._ManifestOpsProvider.createReferenceManifest('TestUnbind');
3269
+ tmpView._ManifestOpsProvider.bindReferenceManifest(0, 0, 'TestUnbind');
3270
+
3271
+ Expect(tmpView._resolveManifestData().Sections[0].Groups[0].RecordManifest).to.equal('TestUnbind');
3272
+
3273
+ tmpView._ManifestOpsProvider.unbindReferenceManifest(0, 0);
3274
+
3275
+ Expect(tmpView._resolveManifestData().Sections[0].Groups[0].hasOwnProperty('RecordManifest')).to.equal(false);
3276
+ }
3277
+ );
3278
+ test
3279
+ (
3280
+ 'createAndBindReferenceManifest should create and bind in one call',
3281
+ function ()
3282
+ {
3283
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3284
+ tmpPict.AppData = {};
3285
+
3286
+ let tmpView = tmpPict.addView('TestCreateBind',
3287
+ {
3288
+ ViewIdentifier: 'TestCreateBind',
3289
+ ManifestDataAddress: 'AppData.FormConfig'
3290
+ }, libPictSectionFormEditor);
3291
+
3292
+ tmpView.initialize();
3293
+ tmpView._ManifestOpsProvider.addSection();
3294
+ tmpView._ManifestOpsProvider.addGroup(0);
3295
+
3296
+ let tmpGroup = tmpView._resolveManifestData().Sections[0].Groups[0];
3297
+ tmpGroup.Hash = 'TestGroup';
3298
+
3299
+ tmpView._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
3300
+
3301
+ let tmpManifest = tmpView._resolveManifestData();
3302
+ Expect(tmpManifest.Sections[0].Groups[0].RecordManifest).to.be.a('string');
3303
+ let tmpBoundName = tmpManifest.Sections[0].Groups[0].RecordManifest;
3304
+ Expect(tmpManifest.ReferenceManifests[tmpBoundName]).to.be.an('object');
3305
+ Expect(tmpManifest.ReferenceManifests[tmpBoundName].Descriptors).to.be.an('object');
3306
+ }
3307
+ );
3308
+ }
3309
+ );
3310
+ suite
3311
+ (
3312
+ 'Submanifest Column Operations',
3313
+ function ()
3314
+ {
3315
+ test
3316
+ (
3317
+ 'addSubmanifestColumn should add a Descriptor to bound ReferenceManifest',
3318
+ function ()
3319
+ {
3320
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3321
+ tmpPict.AppData = {};
3322
+
3323
+ let tmpView = tmpPict.addView('TestAddSubCol',
3324
+ {
3325
+ ViewIdentifier: 'TestAddSubCol',
3326
+ ManifestDataAddress: 'AppData.FormConfig'
3327
+ }, libPictSectionFormEditor);
3328
+
3329
+ tmpView.initialize();
3330
+ tmpView._ManifestOpsProvider.addSection();
3331
+ tmpView._ManifestOpsProvider.addGroup(0);
3332
+ tmpView._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
3333
+
3334
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3335
+
3336
+ let tmpManifest = tmpView._resolveManifestData();
3337
+ let tmpRefName = tmpManifest.Sections[0].Groups[0].RecordManifest;
3338
+ let tmpRef = tmpManifest.ReferenceManifests[tmpRefName];
3339
+ let tmpKeys = Object.keys(tmpRef.Descriptors);
3340
+
3341
+ Expect(tmpKeys.length).to.equal(1);
3342
+
3343
+ let tmpDesc = tmpRef.Descriptors[tmpKeys[0]];
3344
+ Expect(tmpDesc.Name).to.be.a('string');
3345
+ Expect(tmpDesc.Hash).to.be.a('string');
3346
+ Expect(tmpDesc.DataType).to.equal('String');
3347
+ }
3348
+ );
3349
+ test
3350
+ (
3351
+ 'addSubmanifestColumn should add multiple columns with unique addresses',
3352
+ function ()
3353
+ {
3354
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3355
+ tmpPict.AppData = {};
3356
+
3357
+ let tmpView = tmpPict.addView('TestAddMultiSubCol',
3358
+ {
3359
+ ViewIdentifier: 'TestAddMultiSubCol',
3360
+ ManifestDataAddress: 'AppData.FormConfig'
3361
+ }, libPictSectionFormEditor);
3362
+
3363
+ tmpView.initialize();
3364
+ tmpView._ManifestOpsProvider.addSection();
3365
+ tmpView._ManifestOpsProvider.addGroup(0);
3366
+ tmpView._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
3367
+
3368
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3369
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3370
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3371
+
3372
+ let tmpManifest = tmpView._resolveManifestData();
3373
+ let tmpRefName = tmpManifest.Sections[0].Groups[0].RecordManifest;
3374
+ let tmpRef = tmpManifest.ReferenceManifests[tmpRefName];
3375
+ let tmpKeys = Object.keys(tmpRef.Descriptors);
3376
+
3377
+ Expect(tmpKeys.length).to.equal(3);
3378
+ // All keys should be unique
3379
+ let tmpUniqueKeys = new Set(tmpKeys);
3380
+ Expect(tmpUniqueKeys.size).to.equal(3);
3381
+ }
3382
+ );
3383
+ test
3384
+ (
3385
+ 'removeSubmanifestColumn should remove Descriptor by address',
3386
+ function ()
3387
+ {
3388
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3389
+ tmpPict.AppData = {};
3390
+
3391
+ let tmpView = tmpPict.addView('TestRemoveSubCol',
3392
+ {
3393
+ ViewIdentifier: 'TestRemoveSubCol',
3394
+ ManifestDataAddress: 'AppData.FormConfig'
3395
+ }, libPictSectionFormEditor);
3396
+
3397
+ tmpView.initialize();
3398
+ tmpView._ManifestOpsProvider.addSection();
3399
+ tmpView._ManifestOpsProvider.addGroup(0);
3400
+ tmpView._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
3401
+
3402
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3403
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3404
+
3405
+ let tmpManifest = tmpView._resolveManifestData();
3406
+ let tmpRefName = tmpManifest.Sections[0].Groups[0].RecordManifest;
3407
+ let tmpRef = tmpManifest.ReferenceManifests[tmpRefName];
3408
+ let tmpKeys = Object.keys(tmpRef.Descriptors);
3409
+ Expect(tmpKeys.length).to.equal(2);
3410
+
3411
+ let tmpFirstKey = tmpKeys[0];
3412
+ tmpView._ManifestOpsProvider.removeSubmanifestColumn(0, 0, tmpFirstKey);
3413
+
3414
+ tmpRef = tmpView._resolveManifestData().ReferenceManifests[tmpRefName];
3415
+ let tmpRemainingKeys = Object.keys(tmpRef.Descriptors);
3416
+ Expect(tmpRemainingKeys.length).to.equal(1);
3417
+ Expect(tmpRemainingKeys).to.not.include(tmpFirstKey);
3418
+ }
3419
+ );
3420
+ test
3421
+ (
3422
+ 'moveSubmanifestColumnUp and Down should reorder Descriptors',
3423
+ function ()
3424
+ {
3425
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3426
+ tmpPict.AppData = {};
3427
+
3428
+ let tmpView = tmpPict.addView('TestMoveSubCol',
3429
+ {
3430
+ ViewIdentifier: 'TestMoveSubCol',
3431
+ ManifestDataAddress: 'AppData.FormConfig'
3432
+ }, libPictSectionFormEditor);
3433
+
3434
+ tmpView.initialize();
3435
+ tmpView._ManifestOpsProvider.addSection();
3436
+ tmpView._ManifestOpsProvider.addGroup(0);
3437
+ tmpView._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
3438
+
3439
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3440
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3441
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3442
+
3443
+ let tmpManifest = tmpView._resolveManifestData();
3444
+ let tmpRefName = tmpManifest.Sections[0].Groups[0].RecordManifest;
3445
+ let tmpRef = tmpManifest.ReferenceManifests[tmpRefName];
3446
+ let tmpOriginalKeys = Object.keys(tmpRef.Descriptors);
3447
+
3448
+ // Move the last column up — it should swap with the middle
3449
+ tmpView._ManifestOpsProvider.moveSubmanifestColumnUp(0, 0, tmpOriginalKeys[2]);
3450
+
3451
+ tmpRef = tmpView._resolveManifestData().ReferenceManifests[tmpRefName];
3452
+ let tmpNewKeys = Object.keys(tmpRef.Descriptors);
3453
+
3454
+ Expect(tmpNewKeys[0]).to.equal(tmpOriginalKeys[0]);
3455
+ Expect(tmpNewKeys[1]).to.equal(tmpOriginalKeys[2]);
3456
+ Expect(tmpNewKeys[2]).to.equal(tmpOriginalKeys[1]);
3457
+
3458
+ // Move the first column down
3459
+ tmpView._ManifestOpsProvider.moveSubmanifestColumnDown(0, 0, tmpNewKeys[0]);
3460
+
3461
+ tmpRef = tmpView._resolveManifestData().ReferenceManifests[tmpRefName];
3462
+ let tmpFinalKeys = Object.keys(tmpRef.Descriptors);
3463
+
3464
+ Expect(tmpFinalKeys[0]).to.equal(tmpOriginalKeys[2]);
3465
+ Expect(tmpFinalKeys[1]).to.equal(tmpOriginalKeys[0]);
3466
+ }
3467
+ );
3468
+ test
3469
+ (
3470
+ 'selectSubmanifestColumn should set _SelectedTabularColumn and clear _SelectedInputIndices',
3471
+ function ()
3472
+ {
3473
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3474
+ tmpPict.AppData = {};
3475
+
3476
+ let tmpView = tmpPict.addView('TestSelectSubCol',
3477
+ {
3478
+ ViewIdentifier: 'TestSelectSubCol',
3479
+ ManifestDataAddress: 'AppData.FormConfig'
3480
+ }, libPictSectionFormEditor);
3481
+
3482
+ tmpView.initialize();
3483
+ tmpView._ManifestOpsProvider.addSection();
3484
+ tmpView._ManifestOpsProvider.addGroup(0);
3485
+ tmpView._ManifestOpsProvider.addRow(0, 0);
3486
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
3487
+ tmpView._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
3488
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3489
+
3490
+ // First select a record input
3491
+ tmpView._UtilitiesProvider.selectInput(0, 0, 0, 0);
3492
+ Expect(tmpView._SelectedInputIndices).to.not.equal(null);
3493
+
3494
+ // Now select a submanifest column
3495
+ let tmpManifest = tmpView._resolveManifestData();
3496
+ let tmpRefName = tmpManifest.Sections[0].Groups[0].RecordManifest;
3497
+ let tmpRef = tmpManifest.ReferenceManifests[tmpRefName];
3498
+ let tmpAddress = Object.keys(tmpRef.Descriptors)[0];
3499
+
3500
+ tmpView._ManifestOpsProvider.selectSubmanifestColumn(0, 0, tmpAddress);
3501
+
3502
+ Expect(tmpView._SelectedTabularColumn).to.be.an('object');
3503
+ Expect(tmpView._SelectedTabularColumn.SectionIndex).to.equal(0);
3504
+ Expect(tmpView._SelectedTabularColumn.GroupIndex).to.equal(0);
3505
+ Expect(tmpView._SelectedTabularColumn.ColumnAddress).to.equal(tmpAddress);
3506
+ Expect(tmpView._SelectedInputIndices).to.equal(null);
3507
+ }
3508
+ );
3509
+ test
3510
+ (
3511
+ 'addSubmanifestColumn should be a no-op when no ReferenceManifest is bound',
3512
+ function ()
3513
+ {
3514
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3515
+ tmpPict.AppData = {};
3516
+
3517
+ let tmpView = tmpPict.addView('TestAddSubColNoRef',
3518
+ {
3519
+ ViewIdentifier: 'TestAddSubColNoRef',
3520
+ ManifestDataAddress: 'AppData.FormConfig'
3521
+ }, libPictSectionFormEditor);
3522
+
3523
+ tmpView.initialize();
3524
+ tmpView._ManifestOpsProvider.addSection();
3525
+ tmpView._ManifestOpsProvider.addGroup(0);
3526
+
3527
+ // No ReferenceManifest bound — should not throw
3528
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3529
+
3530
+ let tmpManifest = tmpView._resolveManifestData();
3531
+ // Manifest should not have any ReferenceManifests
3532
+ Expect(Object.keys(tmpManifest.ReferenceManifests).length).to.equal(0);
3533
+ }
3534
+ );
3535
+ test
3536
+ (
3537
+ 'Shared ReferenceManifest edits via one group should affect the other',
3538
+ function ()
3539
+ {
3540
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3541
+ tmpPict.AppData = {};
3542
+
3543
+ let tmpView = tmpPict.addView('TestSharedRef',
3544
+ {
3545
+ ViewIdentifier: 'TestSharedRef',
3546
+ ManifestDataAddress: 'AppData.FormConfig'
3547
+ }, libPictSectionFormEditor);
3548
+
3549
+ tmpView.initialize();
3550
+ tmpView._ManifestOpsProvider.addSection();
3551
+ tmpView._ManifestOpsProvider.addGroup(0);
3552
+ tmpView._ManifestOpsProvider.addGroup(0);
3553
+ tmpView._ManifestOpsProvider.createReferenceManifest('SharedManifest');
3554
+ tmpView._ManifestOpsProvider.bindReferenceManifest(0, 0, 'SharedManifest');
3555
+ tmpView._ManifestOpsProvider.bindReferenceManifest(0, 1, 'SharedManifest');
3556
+
3557
+ // Add a column via group 0
3558
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3559
+
3560
+ // Should be visible from group 1 since they share the manifest
3561
+ let tmpManifest = tmpView._resolveManifestData();
3562
+ let tmpRef = tmpManifest.ReferenceManifests['SharedManifest'];
3563
+ Expect(Object.keys(tmpRef.Descriptors).length).to.equal(1);
3564
+ }
3565
+ );
3566
+ }
3567
+ );
3568
+ suite
3569
+ (
3570
+ 'Submanifest Row Helpers',
3571
+ function ()
3572
+ {
3573
+ test
3574
+ (
3575
+ '_getSubmanifestRows should group Descriptors by PictForm.Row',
3576
+ function ()
3577
+ {
3578
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3579
+ tmpPict.AppData = {};
3580
+
3581
+ let tmpView = tmpPict.addView('TestGetSubRows',
3582
+ {
3583
+ ViewIdentifier: 'TestGetSubRows',
3584
+ ManifestDataAddress: 'AppData.FormConfig'
3585
+ }, libPictSectionFormEditor);
3586
+
3587
+ tmpView.initialize();
3588
+
3589
+ let tmpRefManifest =
3590
+ {
3591
+ Scope: 'TestRows',
3592
+ Descriptors:
3593
+ {
3594
+ 'col_a': { Name: 'A', Hash: 'A', DataType: 'String', PictForm: { Row: 1 } },
3595
+ 'col_b': { Name: 'B', Hash: 'B', DataType: 'String', PictForm: { Row: 2 } },
3596
+ 'col_c': { Name: 'C', Hash: 'C', DataType: 'String', PictForm: { Row: 1 } },
3597
+ 'col_d': { Name: 'D', Hash: 'D', DataType: 'String', PictForm: { Row: 2 } }
3598
+ }
3599
+ };
3600
+
3601
+ let tmpRows = tmpView._ManifestOpsProvider._getSubmanifestRows(tmpRefManifest);
3602
+ Expect(tmpRows).to.be.an('array');
3603
+ Expect(tmpRows.length).to.equal(2);
3604
+ Expect(tmpRows[0].Row).to.equal(1);
3605
+ Expect(tmpRows[0].Columns.length).to.equal(2);
3606
+ Expect(tmpRows[0].Columns[0].Address).to.equal('col_a');
3607
+ Expect(tmpRows[0].Columns[1].Address).to.equal('col_c');
3608
+ Expect(tmpRows[1].Row).to.equal(2);
3609
+ Expect(tmpRows[1].Columns.length).to.equal(2);
3610
+ }
3611
+ );
3612
+ test
3613
+ (
3614
+ '_getSubmanifestRows should default Descriptors without PictForm.Row to row 1',
3615
+ function ()
3616
+ {
3617
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3618
+ tmpPict.AppData = {};
3619
+
3620
+ let tmpView = tmpPict.addView('TestSubRowsDefault',
3621
+ {
3622
+ ViewIdentifier: 'TestSubRowsDefault',
3623
+ ManifestDataAddress: 'AppData.FormConfig'
3624
+ }, libPictSectionFormEditor);
3625
+
3626
+ tmpView.initialize();
3627
+
3628
+ let tmpRefManifest =
3629
+ {
3630
+ Scope: 'TestDefault',
3631
+ Descriptors:
3632
+ {
3633
+ 'col_a': { Name: 'A', Hash: 'A', DataType: 'String' },
3634
+ 'col_b': { Name: 'B', Hash: 'B', DataType: 'String', PictForm: {} }
3635
+ }
3636
+ };
3637
+
3638
+ let tmpRows = tmpView._ManifestOpsProvider._getSubmanifestRows(tmpRefManifest);
3639
+ Expect(tmpRows.length).to.equal(1);
3640
+ Expect(tmpRows[0].Row).to.equal(1);
3641
+ Expect(tmpRows[0].Columns.length).to.equal(2);
3642
+ }
3643
+ );
3644
+ test
3645
+ (
3646
+ '_getSubmanifestMaxRow should return the highest row number',
3647
+ function ()
3648
+ {
3649
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3650
+ tmpPict.AppData = {};
3651
+
3652
+ let tmpView = tmpPict.addView('TestMaxRow',
3653
+ {
3654
+ ViewIdentifier: 'TestMaxRow',
3655
+ ManifestDataAddress: 'AppData.FormConfig'
3656
+ }, libPictSectionFormEditor);
3657
+
3658
+ tmpView.initialize();
3659
+
3660
+ let tmpRefManifest =
3661
+ {
3662
+ Scope: 'TestMax',
3663
+ Descriptors:
3664
+ {
3665
+ 'col_a': { Name: 'A', Hash: 'A', DataType: 'String', PictForm: { Row: 1 } },
3666
+ 'col_b': { Name: 'B', Hash: 'B', DataType: 'String', PictForm: { Row: 5 } },
3667
+ 'col_c': { Name: 'C', Hash: 'C', DataType: 'String', PictForm: { Row: 3 } }
3668
+ }
3669
+ };
3670
+
3671
+ Expect(tmpView._ManifestOpsProvider._getSubmanifestMaxRow(tmpRefManifest)).to.equal(5);
3672
+
3673
+ // Empty Descriptors should return 0
3674
+ let tmpEmptyRef = { Scope: 'Empty', Descriptors: {} };
3675
+ Expect(tmpView._ManifestOpsProvider._getSubmanifestMaxRow(tmpEmptyRef)).to.equal(0);
3676
+ }
3677
+ );
3678
+ test
3679
+ (
3680
+ 'addSubmanifestRow should create a column with the next row number',
3681
+ function ()
3682
+ {
3683
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3684
+ tmpPict.AppData = {};
3685
+
3686
+ let tmpView = tmpPict.addView('TestAddSubRow',
3687
+ {
3688
+ ViewIdentifier: 'TestAddSubRow',
3689
+ ManifestDataAddress: 'AppData.FormConfig'
3690
+ }, libPictSectionFormEditor);
3691
+
3692
+ tmpView.initialize();
3693
+ tmpView._ManifestOpsProvider.addSection();
3694
+ tmpView._ManifestOpsProvider.addGroup(0);
3695
+ tmpView._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
3696
+
3697
+ // Add first column (row 1 by default)
3698
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3699
+
3700
+ // Add a new row — should create a column with Row: 2
3701
+ tmpView._ManifestOpsProvider.addSubmanifestRow(0, 0);
3702
+
3703
+ let tmpManifest = tmpView._resolveManifestData();
3704
+ let tmpRefName = tmpManifest.Sections[0].Groups[0].RecordManifest;
3705
+ let tmpRef = tmpManifest.ReferenceManifests[tmpRefName];
3706
+ let tmpKeys = Object.keys(tmpRef.Descriptors);
3707
+
3708
+ Expect(tmpKeys.length).to.equal(2);
3709
+
3710
+ // The second column should have PictForm.Row = 2
3711
+ let tmpSecondDesc = tmpRef.Descriptors[tmpKeys[1]];
3712
+ Expect(tmpSecondDesc.PictForm).to.be.an('object');
3713
+ Expect(tmpSecondDesc.PictForm.Row).to.equal(2);
3714
+ }
3715
+ );
3716
+ }
3717
+ );
3718
+ suite
3719
+ (
3720
+ 'Tabular and RecordSet Rendering',
3721
+ function ()
3722
+ {
3723
+ test
3724
+ (
3725
+ 'Loading Simple-Table manifest should not throw',
3726
+ function ()
3727
+ {
3728
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3729
+ tmpPict.AppData = {};
3730
+
3731
+ let tmpSimpleTable =
3732
+ {
3733
+ "Scope": "SuperSimpleTabularForm",
3734
+ "Sections":
3735
+ [
3736
+ {
3737
+ "Hash": "FruitGrid",
3738
+ "Name": "Fruits of the World",
3739
+ "Groups":
3740
+ [
3741
+ {
3742
+ "Hash": "FruitGrid",
3743
+ "Name": "FruitGrid",
3744
+ "Layout": "Tabular",
3745
+ "RecordSetAddress": "FruitData.FruityVice",
3746
+ "RecordManifest": "FruitEditor"
3747
+ }
3748
+ ]
3749
+ }
3750
+ ],
3751
+ "Descriptors":
3752
+ {
3753
+ "FruitData.FruityVice":
3754
+ {
3755
+ "Name": "Fruits of the Earth",
3756
+ "Hash": "FruitGrid",
3757
+ "DataType": "Array",
3758
+ "Default": [],
3759
+ "PictForm": { "Section": "FruitGrid", "Group": "FruitGrid" }
3760
+ }
3761
+ },
3762
+ "ReferenceManifests":
3763
+ {
3764
+ "FruitEditor":
3765
+ {
3766
+ "Scope": "FruitEditor",
3767
+ "Descriptors":
3768
+ {
3769
+ "name": { "Name": "Fruit Name", "Hash": "Name", "DataType": "String" },
3770
+ "family": { "Name": "Family", "Hash": "Family", "DataType": "String" },
3771
+ "order": { "Name": "Order", "Hash": "Order", "DataType": "String" },
3772
+ "genus": { "Name": "Genus", "Hash": "Genus", "DataType": "String" },
3773
+ "nutritions.calories": { "Name": "Calories", "Hash": "Calories", "DataType": "Number" }
3774
+ }
3775
+ }
3776
+ }
3777
+ };
3778
+
3779
+ let tmpView = tmpPict.addView('TestSimpleTable',
3780
+ {
3781
+ ViewIdentifier: 'TestSimpleTable',
3782
+ ManifestDataAddress: 'AppData.FormConfig'
3783
+ }, libPictSectionFormEditor);
3784
+
3785
+ tmpView.initialize();
3786
+ tmpPict.AppData.FormConfig = tmpSimpleTable;
3787
+
3788
+ // Verify it loaded correctly
3789
+ let tmpManifest = tmpView._resolveManifestData();
3790
+ Expect(tmpManifest.Sections[0].Groups[0].Layout).to.equal('Tabular');
3791
+ Expect(tmpManifest.Sections[0].Groups[0].RecordManifest).to.equal('FruitEditor');
3792
+ Expect(tmpManifest.ReferenceManifests['FruitEditor']).to.be.an('object');
3793
+ }
3794
+ );
3795
+ test
3796
+ (
3797
+ 'getFormStats should include ReferenceManifest and TabularColumn counts',
3798
+ function ()
3799
+ {
3800
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3801
+ tmpPict.AppData = {};
3802
+
3803
+ let tmpView = tmpPict.addView('TestFormStatsTabular',
3804
+ {
3805
+ ViewIdentifier: 'TestFormStatsTabular',
3806
+ ManifestDataAddress: 'AppData.FormConfig'
3807
+ }, libPictSectionFormEditor);
3808
+
3809
+ tmpView.initialize();
3810
+ tmpView._ManifestOpsProvider.addSection();
3811
+ // addSection() already creates one default group, so use group index 0
3812
+ tmpView._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
3813
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3814
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3815
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 0);
3816
+
3817
+ let tmpStats = tmpView._UtilitiesProvider.getFormStats();
3818
+ Expect(tmpStats.ReferenceManifests).to.equal(1);
3819
+ Expect(tmpStats.TabularColumns).to.equal(3);
3820
+ Expect(tmpStats.Sections).to.equal(1);
3821
+ Expect(tmpStats.Groups).to.equal(1);
3822
+ }
3823
+ );
3824
+ test
3825
+ (
3826
+ 'getAllInputEntries should include tabular columns with IsTabular marker',
3827
+ function ()
3828
+ {
3829
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3830
+ tmpPict.AppData = {};
3831
+
3832
+ let tmpView = tmpPict.addView('TestGetAllTabular',
3833
+ {
3834
+ ViewIdentifier: 'TestGetAllTabular',
3835
+ ManifestDataAddress: 'AppData.FormConfig'
3836
+ }, libPictSectionFormEditor);
3837
+
3838
+ tmpView.initialize();
3839
+
3840
+ // Add a Record group with one input
3841
+ tmpView._ManifestOpsProvider.addSection();
3842
+ tmpView._ManifestOpsProvider.addGroup(0);
3843
+ tmpView._ManifestOpsProvider.addRow(0, 0);
3844
+ tmpView._ManifestOpsProvider.addInput(0, 0, 0);
3845
+
3846
+ // Add a Tabular group with columns
3847
+ tmpView._ManifestOpsProvider.addGroup(0);
3848
+ let tmpManifest = tmpView._resolveManifestData();
3849
+ tmpManifest.Sections[0].Groups[1].Layout = 'Tabular';
3850
+ tmpView._ManifestOpsProvider.createAndBindReferenceManifest(0, 1);
3851
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 1);
3852
+ tmpView._ManifestOpsProvider.addSubmanifestColumn(0, 1);
3853
+
3854
+ let tmpEntries = tmpView._UtilitiesProvider.getAllInputEntries();
3855
+ Expect(tmpEntries).to.be.an('array');
3856
+
3857
+ // Should have 1 record input + 2 tabular columns
3858
+ Expect(tmpEntries.length).to.equal(3);
3859
+
3860
+ let tmpTabularEntries = tmpEntries.filter(function(e) { return e.IsTabular; });
3861
+ Expect(tmpTabularEntries.length).to.equal(2);
3862
+
3863
+ let tmpRecordEntries = tmpEntries.filter(function(e) { return !e.IsTabular; });
3864
+ Expect(tmpRecordEntries.length).to.equal(1);
3865
+ }
3866
+ );
3867
+ test
3868
+ (
3869
+ 'Layout dropdown should include RecordSet option',
3870
+ function ()
3871
+ {
3872
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3873
+ tmpPict.AppData = {};
3874
+
3875
+ let tmpView = tmpPict.addView('TestLayoutDropdown',
3876
+ {
3877
+ ViewIdentifier: 'TestLayoutDropdown',
3878
+ ManifestDataAddress: 'AppData.FormConfig'
3879
+ }, libPictSectionFormEditor);
3880
+
3881
+ tmpView.initialize();
3882
+ tmpView._ManifestOpsProvider.addSection();
3883
+ tmpView._ManifestOpsProvider.addGroup(0);
3884
+
3885
+ // The Layout property edit list should include RecordSet
3886
+ // We verify this by checking that the view accepts RecordSet as a valid layout
3887
+ let tmpManifest = tmpView._resolveManifestData();
3888
+ tmpManifest.Sections[0].Groups[0].Layout = 'RecordSet';
3889
+
3890
+ // Should not throw when accessing a RecordSet layout group
3891
+ Expect(tmpManifest.Sections[0].Groups[0].Layout).to.equal('RecordSet');
3892
+ }
3893
+ );
3894
+ test
3895
+ (
3896
+ '_createEmptyManifest should include ReferenceManifests property',
3897
+ function ()
3898
+ {
3899
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3900
+ tmpPict.AppData = {};
3901
+
3902
+ let tmpView = tmpPict.addView('TestEmptyManifest',
3903
+ {
3904
+ ViewIdentifier: 'TestEmptyManifest',
3905
+ ManifestDataAddress: 'AppData.FormConfig'
3906
+ }, libPictSectionFormEditor);
3907
+
3908
+ tmpView.initialize();
3909
+
3910
+ let tmpManifest = tmpView._resolveManifestData();
3911
+ Expect(tmpManifest.ReferenceManifests).to.be.an('object');
3912
+ Expect(Object.keys(tmpManifest.ReferenceManifests).length).to.equal(0);
3913
+ }
3914
+ );
3915
+ }
3916
+ );
3917
+ suite
3918
+ (
3919
+ 'Properties Panel Tabular Column Editing',
3920
+ function ()
3921
+ {
3922
+ test
3923
+ (
3924
+ 'selectTabularColumn should set _SelectedTabularColumn on properties panel',
3925
+ function ()
3926
+ {
3927
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3928
+ tmpPict.AppData = {};
3929
+
3930
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
3931
+
3932
+ let tmpView = tmpPict.addView('TestSelectTabCol',
3933
+ {
3934
+ ViewIdentifier: 'TestSelectTabCol'
3935
+ }, tmpPropertiesPanel);
3936
+
3937
+ let tmpFormEditor = tmpPict.addView('TestSelectTabColParent',
3938
+ {
3939
+ ViewIdentifier: 'TestSelectTabColParent',
3940
+ ManifestDataAddress: 'AppData.TestManifest'
3941
+ }, libPictSectionFormEditor);
3942
+ tmpFormEditor.initialize();
3943
+
3944
+ tmpView._ParentFormEditor = tmpFormEditor;
3945
+
3946
+ tmpView.selectTabularColumn(0, 1, 'my_column');
3947
+
3948
+ Expect(tmpView._SelectedTabularColumn).to.be.an('object');
3949
+ Expect(tmpView._SelectedTabularColumn.SectionIndex).to.equal(0);
3950
+ Expect(tmpView._SelectedTabularColumn.GroupIndex).to.equal(1);
3951
+ Expect(tmpView._SelectedTabularColumn.ColumnAddress).to.equal('my_column');
3952
+ Expect(tmpView._SelectedInput).to.equal(null);
3953
+ }
3954
+ );
3955
+ test
3956
+ (
3957
+ 'deselectInput on properties panel should clear _SelectedTabularColumn',
3958
+ function ()
3959
+ {
3960
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3961
+ tmpPict.AppData = {};
3962
+
3963
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
3964
+
3965
+ let tmpView = tmpPict.addView('TestDeselectTabCol',
3966
+ {
3967
+ ViewIdentifier: 'TestDeselectTabCol'
3968
+ }, tmpPropertiesPanel);
3969
+
3970
+ tmpView._SelectedTabularColumn = { SectionIndex: 0, GroupIndex: 0, ColumnAddress: 'test' };
3971
+
3972
+ tmpView.deselectInput();
3973
+
3974
+ Expect(tmpView._SelectedTabularColumn).to.equal(null);
3975
+ Expect(tmpView._SelectedInput).to.equal(null);
3976
+ }
3977
+ );
3978
+ test
3979
+ (
3980
+ 'commitTabularPropertyChange should update Descriptor fields',
3981
+ function ()
3982
+ {
3983
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
3984
+ tmpPict.AppData = {};
3985
+
3986
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
3987
+
3988
+ let tmpView = tmpPict.addView('TestCommitTabProp',
3989
+ {
3990
+ ViewIdentifier: 'TestCommitTabProp'
3991
+ }, tmpPropertiesPanel);
3992
+
3993
+ let tmpFormEditor = tmpPict.addView('TestCommitTabPropParent',
3994
+ {
3995
+ ViewIdentifier: 'TestCommitTabPropParent',
3996
+ ManifestDataAddress: 'AppData.TestCommitTabManifest'
3997
+ }, libPictSectionFormEditor);
3998
+ tmpFormEditor.initialize();
3999
+
4000
+ tmpFormEditor._ManifestOpsProvider.addSection();
4001
+ tmpFormEditor._ManifestOpsProvider.addGroup(0);
4002
+ tmpFormEditor._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
4003
+ tmpFormEditor._ManifestOpsProvider.addSubmanifestColumn(0, 0);
4004
+
4005
+ let tmpManifest = tmpFormEditor._resolveManifestData();
4006
+ let tmpRefName = tmpManifest.Sections[0].Groups[0].RecordManifest;
4007
+ let tmpRef = tmpManifest.ReferenceManifests[tmpRefName];
4008
+ let tmpAddress = Object.keys(tmpRef.Descriptors)[0];
4009
+
4010
+ tmpView._ParentFormEditor = tmpFormEditor;
4011
+ tmpView.selectTabularColumn(0, 0, tmpAddress);
4012
+
4013
+ tmpView.commitTabularPropertyChange('Name', 'Updated Name');
4014
+
4015
+ let tmpDesc = tmpFormEditor._resolveManifestData().ReferenceManifests[tmpRefName].Descriptors[tmpAddress];
4016
+ Expect(tmpDesc.Name).to.equal('Updated Name');
4017
+
4018
+ tmpView.commitTabularPropertyChange('Hash', 'UpdatedHash');
4019
+ tmpDesc = tmpFormEditor._resolveManifestData().ReferenceManifests[tmpRefName].Descriptors[tmpAddress];
4020
+ Expect(tmpDesc.Hash).to.equal('UpdatedHash');
4021
+
4022
+ tmpView.commitTabularPropertyChange('DataType', 'Number');
4023
+ tmpDesc = tmpFormEditor._resolveManifestData().ReferenceManifests[tmpRefName].Descriptors[tmpAddress];
4024
+ Expect(tmpDesc.DataType).to.equal('Number');
4025
+ }
4026
+ );
4027
+ test
4028
+ (
4029
+ 'onInputSelectorChange should handle T: prefix for tabular columns',
4030
+ function ()
4031
+ {
4032
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
4033
+ tmpPict.AppData = {};
4034
+
4035
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
4036
+
4037
+ let tmpView = tmpPict.addView('TestInputSelectorTab',
4038
+ {
4039
+ ViewIdentifier: 'TestInputSelectorTab'
4040
+ }, tmpPropertiesPanel);
4041
+
4042
+ let tmpFormEditor = tmpPict.addView('TestInputSelectorTabParent',
4043
+ {
4044
+ ViewIdentifier: 'TestInputSelectorTabParent',
4045
+ ManifestDataAddress: 'AppData.TestSelectorManifest'
4046
+ }, libPictSectionFormEditor);
4047
+ tmpFormEditor.initialize();
4048
+
4049
+ tmpFormEditor._ManifestOpsProvider.addSection();
4050
+ tmpFormEditor._ManifestOpsProvider.addGroup(0);
4051
+ tmpFormEditor._ManifestOpsProvider.createAndBindReferenceManifest(0, 0);
4052
+ tmpFormEditor._ManifestOpsProvider.addSubmanifestColumn(0, 0);
4053
+
4054
+ let tmpManifest = tmpFormEditor._resolveManifestData();
4055
+ let tmpRefName = tmpManifest.Sections[0].Groups[0].RecordManifest;
4056
+ let tmpRef = tmpManifest.ReferenceManifests[tmpRefName];
4057
+ let tmpAddress = Object.keys(tmpRef.Descriptors)[0];
4058
+
4059
+ tmpView._ParentFormEditor = tmpFormEditor;
4060
+
4061
+ // Select via T: prefix format
4062
+ tmpView.onInputSelectorChange('T:0,0,' + tmpAddress);
4063
+
4064
+ Expect(tmpFormEditor._SelectedTabularColumn).to.be.an('object');
4065
+ Expect(tmpFormEditor._SelectedTabularColumn.SectionIndex).to.equal(0);
4066
+ Expect(tmpFormEditor._SelectedTabularColumn.GroupIndex).to.equal(0);
4067
+ Expect(tmpFormEditor._SelectedTabularColumn.ColumnAddress).to.equal(tmpAddress);
4068
+ }
4069
+ );
4070
+ test
4071
+ (
4072
+ 'onInputSelectorChange should handle empty value by deselecting',
4073
+ function ()
4074
+ {
4075
+ let tmpPict = new libPict({ Product: 'TestFormEditor' });
4076
+ tmpPict.AppData = {};
4077
+
4078
+ let tmpPropertiesPanel = require('../source/views/PictView-FormEditor-PropertiesPanel.js');
4079
+
4080
+ let tmpView = tmpPict.addView('TestInputSelectorEmpty',
4081
+ {
4082
+ ViewIdentifier: 'TestInputSelectorEmpty'
4083
+ }, tmpPropertiesPanel);
4084
+
4085
+ let tmpFormEditor = tmpPict.addView('TestInputSelectorEmptyParent',
4086
+ {
4087
+ ViewIdentifier: 'TestInputSelectorEmptyParent',
4088
+ ManifestDataAddress: 'AppData.TestSelectorEmptyManifest'
4089
+ }, libPictSectionFormEditor);
4090
+ tmpFormEditor.initialize();
4091
+
4092
+ tmpView._ParentFormEditor = tmpFormEditor;
4093
+
4094
+ // Should not throw when deselecting
4095
+ tmpView.onInputSelectorChange('');
4096
+ tmpView.onInputSelectorChange(null);
4097
+ }
4098
+ );
4099
+ }
4100
+ );
4101
+ }
4102
+ );