#import "Basic"()( MEMORY_DEBUGGER = MEMORY_DEBUGGER_ENABLED // Enable it via `jai build.jai - memory` ); #import "htmltemplate"; #import "stringpad"; // Not needed for the lib. Only for unit tests /** Omit logs from individual unit tests */ DONT_YAP :: false; test_cases_count: int; tests_passed: bool = true; tests_failed: [..]int; Test_Case :: struct(T: Type, R: Type) { id: string; values: T; template: string; expected: R; } main :: () { #if MEMORY_DEBUGGER_ENABLED defer report_memory_leaks(); defer this_allocation_is_not_a_leak(tests_failed.data); /** Replace a single value */ { test: Test_Case(string, string); test.id = "foo"; test.values = "test123"; test.template = "

{{ :foo: }}

"; test.expected = "

test123

"; test_run_and_store_result(test_html); } /** If only one value is provided, but more placeholders, populate the same value. Note: the final html string is trimmed from both sides! */ { test: Test_Case(string, string); test.id = "foo"; test.values = "test123"; test.template = "

{{ :foo: % and % }}

"; test.expected = "

test123 and test123

"; test_run_and_store_result(test_html); } /** Explicit positional placeholders. You can also omit whitespaces. */ { test: Test_Case([]string, string); test.id = "foo"; test.values = .[ "test123", "abcdef" ]; test.template = "

{{:foo:%1 and %2}}

"; test.expected = "

test123 and abcdef

"; test_run_and_store_result(test_html); } /** Mixing numerated and not-numerated placeholders. This works, as long there aren't too many of it. See one test below what happens. */ { test: Test_Case([]string, string); test.id = "foo"; test.values = .[ "test123", "abcdef" ]; test.template = "

{{ :foo: %1 and %2 \nand % and % }}

"; test.expected = "

test123 and abcdef \nand test123 and abcdef

"; test_run_and_store_result(test_html); } /** More placeholders than values */ { test: Test_Case([]string, bool); test.id = "foo"; test.values = .[ "test123", "abcdef" ]; test.template = "

{{ :foo: %1 and %2 and % and % and % }}

"; test.expected = false; test_run_and_store_result(test_success); } /** If you want a plain `%` just escape it -> `\%`. Keep in mind to escape the `\` too if using it in Jai strings */ { test: Test_Case([]string, string); test.id = "foo"; test.values = .[ "test123", "abcdef" ]; test.template = "

{{ :foo: % but escape this -> \\% }}

"; test.expected = "

test123 but escape this -> %

"; test_run_and_store_result(test_html); } /** You don't have to use all values. In this case we don't use `123123`. Also note how we mix numerated and not-numerated placeholders: 1 2 3 4 values: [ "test123", "abcdef", "123", "123123" ] 1 2 3 | | | template:

{{ :foo: %, %1, %1, %2, %, % }}

| | | | | | rendered:

test123, test123, test123, abcdef, abcdef, 123

*/ { test: Test_Case([]string, string); test.id = "foo"; test.values = .[ "test123", "abcdef", "123", "123123" ]; test.template = "

{{ :foo: %, %1, %1, %2, %, % \\%\\% \\%}}

"; test.expected = "

test123, test123, test123, abcdef, abcdef, 123 %% %

"; test_run_and_store_result(test_html); } /** Loops are following the same template behavior as above, but they additionally repeat the template. The amount of loops is defined by how big the outer array of the values is. */ { test: Test_Case([][]string, string); test.id = "foo"; test.values = .[ .[ "#link1", "target=\"_blank\"", "My Link 1" ], .[ "#link2", "", "My Link 2" ], ]; test.template = "{{ loop:foo:% }}"; test.expected = #string STR_END My Link 1 My Link 2 STR_END; test.expected.count -= 1; test_run_and_store_result(test_html); } /** Reusing identifier will silently fail and only populate the last placeholder. Maybe I'll support reusing identifiers in the future. TODO: @Reminder */ { test: Test_Case(string, string); test.id = "foo1"; test.values = "testABC"; test.template = "

{{ :foo1: }}

{{ :foo1: }}

{{ :foo1: }}

"; test.expected = "

{{ :foo1: }}

{{ :foo1: }}

testABC

"; test_run_and_store_result(test_html); } /** Missing postfix `:` */ { test: Test_Case(string, bool); test.id = "foo"; test.values = "test123"; test.template = "

{{ :foo }}

"; test.expected = false; test_run_and_store_result(test_success); } /** Missing prefix `:` */ { test: Test_Case(string, bool); test.id = "foo"; test.values = "test123"; test.template = "

{{ foo: }}

"; test.expected = false; test_run_and_store_result(test_success); } /** Empty identifier */ { test: Test_Case(string, bool); test.id = "foo"; test.values = "test123"; test.template = "

{{ :: }}

"; test.expected = false; test_run_and_store_result(test_success); } /** Incomplete terminator */ { test: Test_Case(string, bool); test.id = "foo"; test.values = "test123"; test.template = "

{{ :foo: }

"; test.expected = false; test_run_and_store_result(test_success); } /** Incomplete definition. Not an error. */ { test: Test_Case(string, bool); test.id = "foo"; test.values = "test123"; test.template = "

{ :foo: }}

"; test.expected = true; test_run_and_store_result(test_success); } /** Test for many newlines and dangling placeholders */ { test: Test_Case([]string, string); test.id = "foo"; test.values = .[ "test123", "abcdef" ]; test.template = "

{{ :foo: %1\n\n%1\n\n\n% 1 \n\n }}

"; test.expected = "

test123\n\ntest123\n\n\ntest123 1

"; test_run_and_store_result(test_html); } /** Test if numbered placeholders getting correctly stripped */ { test: Test_Case([][]string, string); test.id = "foo"; test.values = .[ .["A", "a", "aa", "aaa"], .["B"], .["C", "c", "cc", "ccc"], .["D"], ]; test.template = "{{loop:foo:

%1_%2_%3_%4_\\%_%4_%3_%2_%1

}}"; test.expected = #string STR_END

A_a_aa_aaa_%_aaa_aa_a_A

B_B_B_B_%_B_B_B_B

C_c_cc_ccc_%_ccc_cc_c_C

D_D_D_D_%_D_D_D_D

STR_END; test.expected.count -= 1; test_run_and_store_result(test_html); } /** If user declared a loop, but supplied a single string */ { test_run_and_store_result(test_case_loop_wrong_overload_1); } /** If user declared a loop, but supplied a 1D string array */ { test_run_and_store_result(test_case_loop_wrong_overload_2); } /** If user declared a replace, but supplied a 2D string array */ { test_run_and_store_result(test_case_replace_wrong_overload); } log("\n-- Test Run Result ------------------------"); if tests_passed { log("All % tests passed.", test_cases_count); #if !MEMORY_DEBUGGER_ENABLED exit(0); else return; } log_error("% tests failed:", tests_failed.count); for tests_failed log_error(" - %", it); log_error("\n"); #if !MEMORY_DEBUGGER_ENABLED exit(1); else return; } // ------------------------------------ // Individual test cases // --------------------- test_case_loop_wrong_overload_1 :: () -> success: bool { queue_action: [..]Action; defer this_allocation_is_not_a_leak(queue_action.data); commit("foo_loop", "bar"); template := "{{ loop:foo_loop: %1 %2 %3 }}"; success, _, exit_code, error_message := generate( queue_action, template, .STRING ,, temp); if !success { dbg("\nMessage from lib:"); dbg("(Exit Code: %) - %", exit_code, error_message); } if success == false then return true; return false; } test_case_loop_wrong_overload_2 :: () -> success: bool { queue_action: [..]Action; defer this_allocation_is_not_a_leak(queue_action.data); commit("foo_loop", .["string"]); template := "{{ loop:foo_loop: %1 %2 %3 }}"; success, _, exit_code, error_message := generate( queue_action, template, .STRING ,, temp); if !success { dbg("\nMessage from lib:"); dbg("(Exit Code: %) - %", exit_code, error_message); } if success == false then return true; return false; } test_case_replace_wrong_overload :: () -> success: bool { queue_action: [..]Action; defer this_allocation_is_not_a_leak(queue_action.data); commit("foo", .[.["foo"]]); template := "{{ :foo: %1 %2 %3 }}"; success, _, exit_code, error_message := generate( queue_action, template, .STRING ,, temp); if !success { dbg("\nMessage from lib:"); dbg("(Exit Code: %) - %", exit_code, error_message); } if success == false then return true; return false; } // ------------------------------------ // Unit test internals // ------------------- __HEADER :: #string STR_END test_cases_count += 1; count := string_pad_left(tprint("%", test_cases_count), 2, "0",, temp); dbg("-- Test % --------------------------------", count); STR_END; __FOOTER :: #string STR_END tests_passed &= success; if !success array_add(*tests_failed, test_cases_count); result := ifx success then "Test passed |" else "Test failed |"; dbg("\n%\n\n", string_pad_left(result, 56 - result.count,, temp)); STR_END; test_run_and_store_result :: ($proc: () -> bool) #expand { #insert __HEADER; success := proc(); #insert __FOOTER; } test_run_and_store_result :: ($proc: (Test_Case) -> bool) #expand { #insert __HEADER; success := proc(`test); #insert __FOOTER; } test_html :: (tc: Test_Case) -> success: bool { queue_action: [..]Action; defer this_allocation_is_not_a_leak(queue_action.data); commit(tc.id, tc.values); success, html_new, exit_code, error_message := generate( queue_action, tc.template, .STRING ,, temp); dbg("Values: %", tc.values); dbg("Template: %\n\n", tc.template); dbg("GIVEN: »%«", html_new); dbg("EXPECTED: »%«", tc.expected); if !success { dbg("\nMessage from lib:"); dbg("(Exit Code: %) - %", exit_code, error_message); return false; } if html_new == tc.expected then return true; return false; } test_success :: (tc: Test_Case) -> success: bool { queue_action: [..]Action; defer this_allocation_is_not_a_leak(queue_action.data); commit(tc.id, tc.values); success, html_new, exit_code, error_message := generate( queue_action, tc.template, .STRING ,, temp); dbg("Values: %", tc.values); dbg("Template: %\n\n", tc.template); dbg("Given: %", success); dbg("Expected: %", tc.expected); if !success { dbg("\nMessage from lib:"); dbg("(Exit Code: %) - %", exit_code, error_message); } if success == tc.expected then return true; return false; } dbg :: (s: string, a: ..Any) { #if !DONT_YAP { log(s, ..a); } }