0#import "Basic"()(
1 MEMORY_DEBUGGER = MEMORY_DEBUGGER_ENABLED // Enable it via `jai build.jai - memory`
2);
3#import "htmltemplate";
4#import "stringpad"; // Not needed for the lib. Only for unit tests
7/** Omit logs from individual unit tests */
8DONT_YAP :: false;
11test_cases_count: int;
12tests_passed: bool = true;
13tests_failed: [..]int;
16Test_Case :: struct(T: Type, R: Type) {
17 id: string;
18 values: T;
19 template: string;
20 expected: R;
21}
24main :: () {
25 #if MEMORY_DEBUGGER_ENABLED defer report_memory_leaks();
27 defer this_allocation_is_not_a_leak(tests_failed.data);
30 /** Replace a single value */
31 {
32 test: Test_Case(string, string);
33 test.id = "foo";
34 test.values = "test123";
35 test.template = "<h1>{{ :foo: }}</h1>";
36 test.expected = "<h1>test123</h1>";
37 test_run_and_store_result(test_html);
38 }
41 /** If only one value is provided, but more placeholders,
42 populate the same value.
44 Note: the final html string is trimmed from both sides! */
45 {
46 test: Test_Case(string, string);
47 test.id = "foo";
48 test.values = "test123";
49 test.template = "<h1>{{ :foo: % and % }}</h1>";
50 test.expected = "<h1>test123 and test123</h1>";
51 test_run_and_store_result(test_html);
52 }
55 /** Explicit positional placeholders.
56 You can also omit whitespaces. */
57 {
58 test: Test_Case([]string, string);
59 test.id = "foo";
60 test.values = .[ "test123", "abcdef" ];
61 test.template = "<h1>{{:foo:%1 and %2}}</h1>";
62 test.expected = "<h1>test123 and abcdef</h1>";
63 test_run_and_store_result(test_html);
64 }
67 /** Mixing numerated and not-numerated placeholders.
68 This works, as long there aren't too many of it.
70 See one test below what happens. */
71 {
72 test: Test_Case([]string, string);
73 test.id = "foo";
74 test.values = .[ "test123", "abcdef" ];
75 test.template = "<h1>{{ :foo: %1 and %2 \nand % and % }}</h1>";
76 test.expected = "<h1>test123 and abcdef \nand test123 and abcdef</h1>";
77 test_run_and_store_result(test_html);
78 }
81 /** More placeholders than values */
82 {
83 test: Test_Case([]string, bool);
84 test.id = "foo";
85 test.values = .[ "test123", "abcdef" ];
86 test.template = "<h1>{{ :foo: %1 and %2 and % and % and % }}</h1>";
87 test.expected = false;
88 test_run_and_store_result(test_success);
89 }
92 /** If you want a plain `%` just escape it -> `\%`.
93 Keep in mind to escape the `\` too if using it in Jai strings */
94 {
95 test: Test_Case([]string, string);
96 test.id = "foo";
97 test.values = .[ "test123", "abcdef" ];
98 test.template = "<h1>{{ :foo: % but escape this -> \\% }}</h1>";
99 test.expected = "<h1>test123 but escape this -> %</h1>";
100 test_run_and_store_result(test_html);
101 }
104 /** You don't have to use all values. In this case we don't use `123123`.
106 Also note how we mix numerated and not-numerated placeholders:
109 1 2 3 4
110 values: [ "test123", "abcdef", "123", "123123" ]
112 1 2 3
113 | | |
114 template: <h1>{{ :foo: %, %1, %1, %2, %, % }} </h1>
115 | | | | | |
116 rendered: <h1> test123, test123, test123, abcdef, abcdef, 123 </h1> */
118 {
119 test: Test_Case([]string, string);
120 test.id = "foo";
121 test.values = .[ "test123", "abcdef", "123", "123123" ];
122 test.template = "<h1>{{ :foo: %, %1, %1, %2, %, % \\%\\% \\%}}</h1>";
123 test.expected = "<h1>test123, test123, test123, abcdef, abcdef, 123 %% %</h1>";
124 test_run_and_store_result(test_html);
125 }
128 /** Loops are following the same template behavior as above,
129 but they additionally repeat the template.
131 The amount of loops is defined by how big the
132 outer array of the values is. */
133 {
134 test: Test_Case([][]string, string);
135 test.id = "foo";
136 test.values = .[
137 .[ "#link1", "target=\"_blank\"", "My Link 1" ],
138 .[ "#link2", "", "My Link 2" ],
139 ];
140 test.template = "{{ loop:foo:<a href=\"%\" %>%</a> }}";
141 test.expected = #string STR_END
142<a href="#link1" target="_blank">My Link 1</a>
143<a href="#link2" >My Link 2</a>
144 STR_END;
145 test.expected.count -= 1;
146 test_run_and_store_result(test_html);
147 }
151 /** Reusing identifier will silently fail and only
152 populate the last placeholder.
154 Maybe I'll support reusing identifiers in the future.
156 TODO: @Reminder */
157 {
158 test: Test_Case(string, string);
159 test.id = "foo1";
160 test.values = "testABC";
161 test.template = "<h1>{{ :foo1: }}</h1><h1>{{ :foo1: }}</h1><h1>{{ :foo1: }}</h1>";
162 test.expected = "<h1>{{ :foo1: }}</h1><h1>{{ :foo1: }}</h1><h1>testABC</h1>";
163 test_run_and_store_result(test_html);
164 }
166 /** Missing postfix `:` */
167 {
168 test: Test_Case(string, bool);
169 test.id = "foo";
170 test.values = "test123";
171 test.template = "<h1>{{ :foo }}</h1>";
172 test.expected = false;
173 test_run_and_store_result(test_success);
174 }
176 /** Missing prefix `:` */
177 {
178 test: Test_Case(string, bool);
179 test.id = "foo";
180 test.values = "test123";
181 test.template = "<h1>{{ foo: }}</h1>";
182 test.expected = false;
183 test_run_and_store_result(test_success);
184 }
186 /** Empty identifier */
187 {
188 test: Test_Case(string, bool);
189 test.id = "foo";
190 test.values = "test123";
191 test.template = "<h1>{{ :: }}</h1>";
192 test.expected = false;
193 test_run_and_store_result(test_success);
194 }
196 /** Incomplete terminator */
197 {
198 test: Test_Case(string, bool);
199 test.id = "foo";
200 test.values = "test123";
201 test.template = "<h1>{{ :foo: }</h1>";
202 test.expected = false;
203 test_run_and_store_result(test_success);
204 }
206 /** Incomplete definition. Not an error. */
207 {
208 test: Test_Case(string, bool);
209 test.id = "foo";
210 test.values = "test123";
211 test.template = "<h1>{ :foo: }}</h1>";
212 test.expected = true;
213 test_run_and_store_result(test_success);
214 }
216 /** Test for many newlines and dangling placeholders */
217 {
218 test: Test_Case([]string, string);
219 test.id = "foo";
220 test.values = .[ "test123", "abcdef" ];
221 test.template = "<h1>{{ :foo: %1\n\n%1\n\n\n% 1 \n\n }}</h1>";
222 test.expected = "<h1>test123\n\ntest123\n\n\ntest123 1</h1>";
223 test_run_and_store_result(test_html);
224 }
227 /** Test if numbered placeholders getting correctly stripped */
228 {
229 test: Test_Case([][]string, string);
230 test.id = "foo";
231 test.values = .[
232 .["A", "a", "aa", "aaa"],
233 .["B"],
234 .["C", "c", "cc", "ccc"],
235 .["D"],
236 ];
238 test.template = "{{loop:foo:<h1><span><span class=\"%1\">%1_%2_%3_%4_\\%_%4_%3_%2_%1</span></span></h1>}}";
239 test.expected = #string STR_END
240<h1><span><span class="A">A_a_aa_aaa_%_aaa_aa_a_A</span></span></h1>
241<h1><span><span class="B">B_B_B_B_%_B_B_B_B</span></span></h1>
242<h1><span><span class="C">C_c_cc_ccc_%_ccc_cc_c_C</span></span></h1>
243<h1><span><span class="D">D_D_D_D_%_D_D_D_D</span></span></h1>
244 STR_END;
246 test.expected.count -= 1;
248 test_run_and_store_result(test_html);
249 }
251 /** If user declared a loop, but supplied a single string */
252 {
253 test_run_and_store_result(test_case_loop_wrong_overload_1);
254 }
256 /** If user declared a loop, but supplied a 1D string array */
257 {
258 test_run_and_store_result(test_case_loop_wrong_overload_2);
259 }
261 /** If user declared a replace, but supplied a 2D string array */
262 {
263 test_run_and_store_result(test_case_replace_wrong_overload);
264 }
267 log("\n-- Test Run Result ------------------------");
269 if tests_passed {
270 log("All % tests passed.", test_cases_count);
271 #if !MEMORY_DEBUGGER_ENABLED exit(0); else return;
272 }
274 log_error("% tests failed:", tests_failed.count);
275 for tests_failed log_error(" - %", it);
276 log_error("\n");
277 #if !MEMORY_DEBUGGER_ENABLED exit(1); else return;
278}
281// ------------------------------------
282// Individual test cases
283// ---------------------
286test_case_loop_wrong_overload_1 :: () -> success: bool {
287 queue_action: [..]Action;
288 defer this_allocation_is_not_a_leak(queue_action.data);
290 commit("foo_loop", "bar");
291 template := "{{ loop:foo_loop: %1 %2 %3 }}";
293 success, _, exit_code, error_message := generate(
294 queue_action, template, .STRING
295 ,, temp);
297 if !success {
298 dbg("\nMessage from lib:");
299 dbg("(Exit Code: %) - %", exit_code, error_message);
300 }
302 if success == false then return true;
303 return false;
304}
306test_case_loop_wrong_overload_2 :: () -> success: bool {
307 queue_action: [..]Action;
308 defer this_allocation_is_not_a_leak(queue_action.data);
310 commit("foo_loop", .["string"]);
311 template := "{{ loop:foo_loop: %1 %2 %3 }}";
313 success, _, exit_code, error_message := generate(
314 queue_action, template, .STRING
315 ,, temp);
317 if !success {
318 dbg("\nMessage from lib:");
319 dbg("(Exit Code: %) - %", exit_code, error_message);
320 }
322 if success == false then return true;
323 return false;
324}
326test_case_replace_wrong_overload :: () -> success: bool {
327 queue_action: [..]Action;
328 defer this_allocation_is_not_a_leak(queue_action.data);
330 commit("foo", .[.["foo"]]);
331 template := "{{ :foo: %1 %2 %3 }}";
333 success, _, exit_code, error_message := generate(
334 queue_action, template, .STRING
335 ,, temp);
337 if !success {
338 dbg("\nMessage from lib:");
339 dbg("(Exit Code: %) - %", exit_code, error_message);
340 }
342 if success == false then return true;
343 return false;
344}
347// ------------------------------------
348// Unit test internals
349// -------------------
352__HEADER :: #string STR_END
353 test_cases_count += 1;
354 count := string_pad_left(tprint("%", test_cases_count), 2, "0",, temp);
355 dbg("-- Test % --------------------------------", count);
356 STR_END;
358__FOOTER :: #string STR_END
359 tests_passed &= success;
361 if !success array_add(*tests_failed, test_cases_count);
363 result := ifx success then "Test passed |" else "Test failed |";
364 dbg("\n%\n\n", string_pad_left(result, 56 - result.count,, temp));
365 STR_END;
367test_run_and_store_result :: ($proc: () -> bool) #expand {
368 #insert __HEADER;
369 success := proc();
370 #insert __FOOTER;
371}
373test_run_and_store_result :: ($proc: (Test_Case) -> bool) #expand {
374 #insert __HEADER;
375 success := proc(`test);
376 #insert __FOOTER;
377}
379test_html :: (tc: Test_Case) -> success: bool {
380 queue_action: [..]Action;
381 defer this_allocation_is_not_a_leak(queue_action.data);
383 commit(tc.id, tc.values);
385 success, html_new, exit_code, error_message := generate(
386 queue_action, tc.template, .STRING
387 ,, temp);
389 dbg("Values: %", tc.values);
390 dbg("Template: %\n\n", tc.template);
391 dbg("GIVEN: »%«", html_new);
392 dbg("EXPECTED: »%«", tc.expected);
394 if !success {
395 dbg("\nMessage from lib:");
396 dbg("(Exit Code: %) - %", exit_code, error_message);
397 return false;
398 }
400 if html_new == tc.expected then return true;
401 return false;
402}
404test_success :: (tc: Test_Case) -> success: bool {
405 queue_action: [..]Action;
406 defer this_allocation_is_not_a_leak(queue_action.data);
408 commit(tc.id, tc.values);
410 success, html_new, exit_code, error_message := generate(
411 queue_action, tc.template, .STRING
412 ,, temp);
414 dbg("Values: %", tc.values);
415 dbg("Template: %\n\n", tc.template);
416 dbg("Given: %", success);
417 dbg("Expected: %", tc.expected);
419 if !success {
420 dbg("\nMessage from lib:");
421 dbg("(Exit Code: %) - %", exit_code, error_message);
422 }
424 if success == tc.expected then return true;
425 return false;
426}
428dbg :: (s: string, a: ..Any) {
429 #if !DONT_YAP {
430 log(s, ..a);
431 }
432}
index : htmltemplate
---