245 lines
12 KiB
Odin
245 lines
12 KiB
Odin
package tests
|
|
|
|
import "core:fmt"
|
|
import "core:strings"
|
|
import "core:testing"
|
|
import "../src/core"
|
|
import "../src/ui"
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Phase 3: Character Parser + Progress Tracking + Genre Layouts
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
// ── Character Parser Tests ──
|
|
|
|
@test
|
|
core_parse_age_numeric :: proc(t: ^testing.T) {
|
|
testing.expect(t, core.extract_age("25-year-old female") == "25", "should extract numeric age")
|
|
testing.expect(t, core.extract_age("30 years old man") == "30", "should extract age with 'years old'")
|
|
testing.expect(t, core.extract_age("A 42-year-old wizard") == "42", "should extract age from middle of string")
|
|
}
|
|
|
|
@test
|
|
core_parse_age_word :: proc(t: ^testing.T) {
|
|
testing.expect(t, core.extract_age("young boy") == "young", "should extract 'young'")
|
|
testing.expect(t, core.extract_age("elderly woman") == "elderly", "should extract 'elderly'")
|
|
testing.expect(t, core.extract_age("middle-aged detective") == "middle-aged", "should extract 'middle-aged'")
|
|
}
|
|
|
|
@test
|
|
core_parse_gender :: proc(t: ^testing.T) {
|
|
testing.expect(t, core.extract_gender("male warrior") == "male", "should extract male")
|
|
testing.expect(t, core.extract_gender("female knight") == "female", "should extract female")
|
|
testing.expect(t, core.extract_gender("non-binary hero") == "non-binary", "should extract non-binary")
|
|
testing.expect(t, core.extract_gender("she wears a dress") == "female", "should detect 'she' as female")
|
|
testing.expect(t, core.extract_gender("he carries a sword") == "male", "should detect 'he' as male")
|
|
}
|
|
|
|
@test
|
|
core_parse_hair :: proc(t: ^testing.T) {
|
|
testing.expect(t, core.extract_hair_color("black long hair") == "black", "should extract black hair color")
|
|
testing.expect(t, core.extract_hair_color("blonde ponytail") == "blonde", "should extract blonde")
|
|
testing.expect(t, core.extract_hair_style("long curly hair") == "long", "should extract long style")
|
|
testing.expect(t, core.extract_hair_style("short spiky hair") == "short", "should extract short style")
|
|
}
|
|
|
|
@test
|
|
core_parse_eyes :: proc(t: ^testing.T) {
|
|
testing.expect(t, core.extract_eye_color("blue eyes") == "blue", "should extract blue eyes")
|
|
testing.expect(t, core.extract_eye_color("piercing green gaze") == "green", "should extract green eyes")
|
|
}
|
|
|
|
@test
|
|
core_parse_skin_body :: proc(t: ^testing.T) {
|
|
testing.expect(t, core.extract_skin_tone("fair skin") == "fair", "should extract fair skin")
|
|
testing.expect(t, core.extract_skin_tone("olive complexion") == "olive", "should extract olive skin")
|
|
testing.expect(t, core.extract_body_type("slim build") == "slim", "should extract slim body type")
|
|
testing.expect(t, core.extract_body_type("muscular frame") == "muscular", "should extract muscular")
|
|
}
|
|
|
|
@test
|
|
core_parse_outfit :: proc(t: ^testing.T) {
|
|
testing.expect(t, core.extract_outfit("wearing a red dress") == "a red dress", "should extract outfit after 'wearing'")
|
|
testing.expect(t, core.extract_outfit("dressed in black armor, holding sword") == "black armor", "should extract outfit after 'dressed in'")
|
|
}
|
|
|
|
@test
|
|
core_parse_accessories :: proc(t: ^testing.T) {
|
|
testing.expect(t, strings.contains(core.extract_accessories("wearing glasses and a hat"), "glasses"), "should detect glasses")
|
|
testing.expect(t, strings.contains(core.extract_accessories("wearing glasses and a hat"), "hat"), "should detect hat")
|
|
testing.expect(t, core.extract_accessories("no accessories here") == "", "should return empty for no accessories")
|
|
}
|
|
|
|
@test
|
|
core_parse_distinguishing_features :: proc(t: ^testing.T) {
|
|
testing.expect(t, strings.contains(core.extract_distinguishing_features("scar on cheek"), "scar"), "should detect scar")
|
|
testing.expect(t, strings.contains(core.extract_distinguishing_features("tattoo on arm"), "tattoo"), "should detect tattoo")
|
|
testing.expect(t, core.extract_distinguishing_features("nothing special") == "", "should return empty for no features")
|
|
}
|
|
|
|
@test
|
|
core_parse_full_description_to_template :: proc(t: ^testing.T) {
|
|
desc := "25-year-old female, black long hair, blue eyes, fair skin, slim build, wearing a red dress, glasses, scar on cheek"
|
|
tmpl := core.parse_description_to_template(desc)
|
|
|
|
testing.expect(t, tmpl.age == "25", fmt.tprintf("age should be '25', got %q", tmpl.age))
|
|
testing.expect(t, tmpl.gender == "female", fmt.tprintf("gender should be 'female', got %q", tmpl.gender))
|
|
testing.expect(t, tmpl.hair_color == "black", fmt.tprintf("hair_color should be 'black', got %q", tmpl.hair_color))
|
|
testing.expect(t, tmpl.hair_style == "long", fmt.tprintf("hair_style should be 'long', got %q", tmpl.hair_style))
|
|
testing.expect(t, tmpl.eye_color == "blue", fmt.tprintf("eye_color should be 'blue', got %q", tmpl.eye_color))
|
|
testing.expect(t, tmpl.skin_tone == "fair", fmt.tprintf("skin_tone should be 'fair', got %q", tmpl.skin_tone))
|
|
testing.expect(t, tmpl.body_type == "slim", fmt.tprintf("body_type should be 'slim', got %q", tmpl.body_type))
|
|
testing.expect(t, strings.contains(tmpl.outfit, "red dress"), fmt.tprintf("outfit should contain 'red dress', got %q", tmpl.outfit))
|
|
}
|
|
|
|
@test
|
|
core_extract_color_palette :: proc(t: ^testing.T) {
|
|
desc := "black hair, blue eyes, fair skin, wearing red"
|
|
palette := core.extract_color_palette(desc)
|
|
|
|
testing.expect(t, palette.hair == "black", "palette hair should be black")
|
|
testing.expect(t, palette.eyes == "blue", "palette eyes should be blue")
|
|
testing.expect(t, palette.skin == "fair", "palette skin should be fair")
|
|
}
|
|
|
|
@test
|
|
core_template_to_string_roundtrip :: proc(t: ^testing.T) {
|
|
tmpl := core.Character_Prompt_Template{
|
|
age = "30",
|
|
gender = "male",
|
|
hair_color = "brown",
|
|
hair_style = "short",
|
|
eye_color = "green",
|
|
skin_tone = "tan",
|
|
body_type = "athletic",
|
|
outfit = "leather jacket",
|
|
accessories = "sunglasses",
|
|
distinguishing_features = "scar",
|
|
}
|
|
result := core.template_to_string(tmpl)
|
|
testing.expect(t, strings.contains(result, "30-year-old"), "should contain age")
|
|
testing.expect(t, strings.contains(result, "male"), "should contain gender")
|
|
testing.expect(t, strings.contains(result, "brown short hair"), "should contain hair")
|
|
testing.expect(t, strings.contains(result, "green eyes"), "should contain eye color")
|
|
testing.expect(t, strings.contains(result, "tan skin"), "should contain skin tone")
|
|
testing.expect(t, strings.contains(result, "athletic build"), "should contain body type")
|
|
testing.expect(t, strings.contains(result, "leather jacket"), "should contain outfit")
|
|
testing.expect(t, strings.contains(result, "sunglasses"), "should contain accessories")
|
|
testing.expect(t, strings.contains(result, "scar"), "should contain features")
|
|
}
|
|
|
|
// ── Progress Tracking Tests ──
|
|
|
|
@test
|
|
ui_background_job_has_progress :: proc(t: ^testing.T) {
|
|
job := ui.Background_Job{
|
|
id = 1,
|
|
status = .Running,
|
|
progress = 50.0,
|
|
}
|
|
testing.expect(t, job.progress == 50.0, "progress should be 50.0")
|
|
}
|
|
|
|
@test
|
|
ui_update_job_progress :: proc(t: ^testing.T) {
|
|
state := core.new_initial_state()
|
|
defer core.dispose_state(&state)
|
|
controller := ui.new_controller(state)
|
|
defer ui.dispose_job_manager(&controller.jobs)
|
|
|
|
job_id := ui.submit_job(&controller.jobs, .Generate_Panel, "Testing progress")
|
|
_ = ui.update_job_progress(&controller.jobs, job_id, 25.0)
|
|
|
|
idx := ui.job_index_by_id(&controller.jobs, job_id)
|
|
testing.expect(t, idx >= 0, "job should exist")
|
|
if idx >= 0 {
|
|
job := &controller.jobs.jobs[idx]
|
|
testing.expect(t, job.progress == 25.0, fmt.tprintf("progress should be 25.0, got %.1f", job.progress))
|
|
}
|
|
}
|
|
|
|
@test
|
|
ui_progress_percentage_calculation :: proc(t: ^testing.T) {
|
|
// progress is stored as f32 percentage (0-100)
|
|
pct_1 := f32(3) / f32(12) * 100.0
|
|
testing.expect(t, pct_1 == 25.0, fmt.tprintf("3/12 should be 25%%, got %.1f%%", pct_1))
|
|
|
|
pct_2 := f32(6) / f32(12) * 100.0
|
|
testing.expect(t, pct_2 == 50.0, fmt.tprintf("6/12 should be 50%%, got %.1f%%", pct_2))
|
|
}
|
|
|
|
// ── Genre-Based Layout Pattern Tests ──
|
|
|
|
@test
|
|
core_pattern_matches_genre :: proc(t: ^testing.T) {
|
|
testing.expect(t, core.pattern_matches_genre("grid-2x2", "any") == true, "grid should match any genre")
|
|
testing.expect(t, core.pattern_matches_genre("manga-3-tier", "manga") == true, "manga pattern should match manga genre")
|
|
testing.expect(t, core.pattern_matches_genre("manga-3-tier", "action") == true, "manga pattern should match action genre")
|
|
testing.expect(t, core.pattern_matches_genre("manga-3-tier", "romance") == false, "manga pattern should not match romance")
|
|
testing.expect(t, core.pattern_matches_genre("action-dynamic", "superhero") == true, "action pattern should match superhero")
|
|
testing.expect(t, core.pattern_matches_genre("dialogue-heavy", "romance") == true, "dialogue should match romance")
|
|
testing.expect(t, core.pattern_matches_genre("cinematic-widescreen", "noir") == true, "cinematic should match noir")
|
|
testing.expect(t, core.pattern_matches_genre("webtoon-scroll", "slice-of-life") == true, "webtoon should match slice-of-life")
|
|
}
|
|
|
|
@test
|
|
core_pattern_matches_genre_empty :: proc(t: ^testing.T) {
|
|
testing.expect(t, core.pattern_matches_genre("manga-3-tier", "") == true, "empty genre should match all patterns")
|
|
testing.expect(t, core.pattern_matches_genre("action-dynamic", "") == true, "empty genre should match action pattern")
|
|
}
|
|
|
|
@test
|
|
core_select_best_pattern_genre_filtering :: proc(t: ^testing.T) {
|
|
// Action genre: grid-2x2 (4 panels) is tighter fit than action-dynamic (5 panels) for 4 panels
|
|
pattern := core.select_best_pattern(4, "action", "")
|
|
testing.expect(t, pattern.id == "grid-2x2", fmt.tprintf("action genre with 4 panels should pick grid-2x2 (tightest), got %s", pattern.id))
|
|
|
|
// For 5 panels action, action-dynamic (5) is tighter than manga-3-tier (6)
|
|
pattern2 := core.select_best_pattern(5, "action", "")
|
|
testing.expect(t, pattern2.id == "action-dynamic", fmt.tprintf("action genre with 5 panels should pick action-dynamic, got %s", pattern2.id))
|
|
|
|
// Manga genre with 5 panels should pick manga-3-tier
|
|
pattern3 := core.select_best_pattern(5, "manga", "")
|
|
testing.expect(t, pattern3.id == "manga-3-tier", fmt.tprintf("manga genre with 5 panels should pick manga-3-tier, got %s", pattern3.id))
|
|
|
|
// Romance should prefer dialogue-heavy for 6+ panels
|
|
pattern4 := core.select_best_pattern(6, "romance", "")
|
|
testing.expect(t, pattern4.id == "dialogue-heavy", fmt.tprintf("romance genre with 6 panels should pick dialogue-heavy, got %s", pattern4.id))
|
|
}
|
|
|
|
@test
|
|
core_select_best_pattern_tightest_fit :: proc(t: ^testing.T) {
|
|
// With no genre, should pick smallest pattern that fits
|
|
pattern := core.select_best_pattern(3, "", "")
|
|
testing.expect(t, pattern.max_panels >= 3, "pattern should fit 3 panels")
|
|
testing.expect(t, pattern.max_panels <= 4, "should pick tightest fit for 3 panels")
|
|
|
|
pattern2 := core.select_best_pattern(1, "", "")
|
|
testing.expect(t, pattern2.id == "splash-page", "1 panel should pick splash-page")
|
|
}
|
|
|
|
@test
|
|
core_select_best_pattern_preference_override :: proc(t: ^testing.T) {
|
|
// Preference should override genre filtering if it fits
|
|
pattern := core.select_best_pattern(3, "action", "grid-2x2")
|
|
testing.expect(t, pattern.id == "grid-2x2", fmt.tprintf("preference should override, got %s", pattern.id))
|
|
}
|
|
|
|
@test
|
|
core_auto_layout_pages_uses_genre :: proc(t: ^testing.T) {
|
|
panels: [dynamic]core.Panel
|
|
for i in 0..<5 {
|
|
append(&panels, core.Panel{
|
|
panel_id = fmt.tprintf("panel_%d", i),
|
|
panel_number = i + 1,
|
|
})
|
|
}
|
|
|
|
pages := core.auto_layout_pages(panels[:], .A4, "action", "")
|
|
testing.expect(t, len(pages) > 0, "should produce at least one page")
|
|
if len(pages) > 0 {
|
|
testing.expect(t, pages[0].pattern_id == "action-dynamic", fmt.tprintf("action genre should use action-dynamic pattern, got %s", pages[0].pattern_id))
|
|
}
|
|
defer delete(panels)
|
|
}
|