// The catalog's pieces on their own (app/catalog.jai, app/viewer.jai): the search query's parsing and // matching, the RTF stripper on Snagit's annotations, and the grid's order by date. #scope_file checks_run := 0; checks_failed := 0; MAX_GROUPS :: 8; group_names : [MAX_GROUPS] string; group_checks : [MAX_GROUPS] int; group_fails : [MAX_GROUPS] int; group_count := 0; output_builder: *String_Builder; emit :: (format_string: string, args: ..Any) { if output_builder print_to_builder(output_builder, format_string, ..args); else print(format_string, ..args); } @PrintLike group :: (name: string) { assert(group_count < MAX_GROUPS, "raise MAX_GROUPS"); group_names[group_count] = name; group_count += 1; emit("\n-- % --\n", name); } expect :: (condition: bool, format: string, args: ..Any, loc := #caller_location) { checks_run += 1; if group_count group_checks[group_count-1] += 1; if condition return; checks_failed += 1; if group_count group_fails[group_count-1] += 1; emit(" FAIL [%:%] %\n", group_names[max(group_count-1, 0)], loc.line_number, tprint(format, ..args)); } report :: () -> bool { emit("\n"); for 0..group_count-1 { if group_fails[it] emit(" % -- % checks, % FAILED\n", group_names[it], group_checks[it], group_fails[it]); else emit(" % -- % checks\n", group_names[it], group_checks[it]); } if checks_failed emit("\n% OF % CATALOG CHECKS FAILED\n", checks_failed, checks_run); else emit("\nALL CATALOG TESTS PASSED (% checks over % groups)\n", checks_run, group_count); return checks_failed == 0; } query_tests :: () { group("query"); terms := search_parse(" KiCad \"Footprint Assignments\" url:digikey note:late http://x.y "); expect(terms.count == 5, "five terms (got %)", terms.count); if terms.count != 5 return; expect(terms[0].field == .ANY && terms[0].text == "kicad", "a word, lowercased: %", terms[0]); expect(terms[1].field == .ANY && terms[1].text == "footprint assignments", "a phrase: %", terms[1]); expect(terms[2].field == .URL && terms[2].text == "digikey", "a field prefix: %", terms[2]); expect(terms[3].field == .NOTE && terms[3].text == "late", "note: %", terms[3]); expect(terms[4].field == .ANY && terms[4].text == "http://x.y", "an unknown prefix stays part of the word: %", terms[4]); labels := search_parse("label:schematic like:\"a circuit board\""); expect(labels.count == 2 && labels[0].field == .LABEL && labels[0].text == "schematic" && labels[1].field == .LIKE && labels[1].text == "a circuit board", "label: and like: prefixes: %", labels); phrase := search_parse("title:\"two words\""); expect(phrase.count == 1 && phrase[0].field == .TITLE && phrase[0].text == "two words", "a prefixed phrase: %", phrase); expect(search_parse(" ").count == 0, "blank is no terms"); } match_tests :: () { group("matching"); entry: Catalog_Entry; entry.find_title = "assign footprints - kicad"; entry.find_url = "https://www.digikey.com/x"; entry.find_app = "kicad eda 7.0.2"; entry.find_notes = "late"; entry.find_ocr = "0.1uf : capacitor_smd:c_0201_0603metric\nc1 -\n"; expect(search_matches(*entry, search_parse("capacitor kicad")), "words from different fields all match"); expect(!search_matches(*entry, search_parse("capacitor resistor")), "every word must match"); expect(search_matches(*entry, search_parse("url:digikey")), "a URL word"); expect(!search_matches(*entry, search_parse("title:digikey")), "a prefix limits the word to its field"); expect(search_matches(*entry, search_parse("ocr:\"c1 -\"")), "an OCR phrase"); expect(search_matches(*entry, search_parse("note:LATE")), "matching ignores case"); expect(search_matches(*entry, search_parse("")), "no terms match everything"); expect(!search_matches(null, search_parse("x")), "an entry not loaded matches no search"); entry.find_labels = "schematic\npcb layout\n"; expect(search_matches(*entry, search_parse("label:schematic")), "a label word"); expect(search_matches(*entry, search_parse("schematic")), "a plain word matches labels too"); expect(!search_matches(*entry, search_parse("ocr:schematic")), "an OCR word is not a label's"); expect(!search_matches(*entry, search_parse("like:anything")), "like: needs an image vector"); half := u16.[0x3C00]; entry.embedding = half; expect(search_matches(*entry, search_parse("like:anything")), "like: takes a labelled capture (the ranking is the background's)"); entry.embedding = .[]; // has: a segment's class, the whole name (find_segments, as catalog_load makes it). entry.find_segments = "\nperson\ndog\ndining table\n"; expect(search_matches(*entry, search_parse("has:dog")), "has: a class"); expect(search_matches(*entry, search_parse("has:DOG has:person")), "has: twice, ignoring case"); expect(search_matches(*entry, search_parse("has:\"dining table\"")), "has: a quoted name with a space"); expect(!search_matches(*entry, search_parse("has:table")), "has: is the whole name: table is not dining table"); expect(!search_matches(*entry, search_parse("has:do")), "nor part of a name"); expect(!search_matches(*entry, search_parse("has:cat")), "a class it does not hold"); expect(!search_matches(*entry, search_parse("dog")), "a plain word does not match segments"); expect(search_matches(*entry, search_parse("has:dog kicad")), "has: with other words"); terms := search_parse("has:dog"); expect(terms.count == 1 && terms[0].field == .HAS, "the has: prefix is read"); entry.find_segments = ""; expect(!search_matches(*entry, search_parse("has:dog")), "nothing segmented: no has: matches"); expect(search_line_matches("0.1uF : Capacitor_SMD", search_parse("capacitor")), "a line holding a word"); expect(!search_line_matches("0.1uF : Capacitor_SMD", search_parse("title:capacitor")), "a title word is not an OCR line's"); } rtf_tests :: () { group("rtf"); // A Snagit text annotation, as stored (base64 RTF), whose text is "late". encoded :: "e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcZGVmZjBcZGVmbGFuZzEwMzN7XGZvbnR0Ymx7XGYwXGZzd2lzc1xmcHJxMlxmY2hhcnNldDAgQXJpYWw7fX0NCntcY29sb3J0YmwgO1xyZWQyNTJcZ3JlZW42NlxibHVlNjY7fQ0KXHZpZXdraW5kNFx1YzFccGFyZFxxY1x0eDcyMFxjZjFcZjBcZnMyNCBsYXRlXHBhcg0KfQ0K"; rtf, decoded := base64_decode(encoded,, temp); expect(decoded, "the sample decodes"); text := rtf_to_text(rtf); expect(text == "late", "a Snagit annotation reads as its text: \"%\"", text); text = rtf_to_text("{\\rtf1{\\fonttbl{\\f0 Arial;}}{\\*\\generator Riched20;}one\\par two\\tab three caf\\'e9 \\u8364? \\{x\\}}"); expect(text == "one\ntwo\tthree caf\xC3\xA9 \xE2\x82\xAC {x}", "breaks, tabs, \\'hh, \\uN and escaped braces: \"%\"", text); expect(rtf_to_text("{\\rtf1\\ansi\\'93quoted\\'94}") == "\xE2\x80\x9Cquoted\xE2\x80\x9D", "Windows-1252 quotes"); } order_tests :: () { group("order"); expect(viewer_display_order("2026-10-07 08:00:00", "a", "2025-01-01 00:00:00", "b") < 0, "newer first"); expect(viewer_display_order("", "a", "2025-01-01 00:00:00", "b") > 0, "undated after dated"); expect(viewer_display_order("2025-01-01 00:00:00", "a", "", "b") < 0, "dated before undated"); expect(viewer_display_order("2025-01-01 00:00:00", "b", "2025-01-01 00:00:00", "a") < 0, "the same date: by name, newest first"); } #scope_export run_catalog_tests :: (builder: *String_Builder = null) -> bool { output_builder = builder; emit("=== catalog test suite ===\n"); push_allocator(context.default_allocator); query_tests(); match_tests(); rtf_tests(); order_tests(); return report(); }