#include "app_core/brush_ui.h" #include "test_harness.h" #include #include #include namespace { class FakeBrushUiServices final : public pp::app::BrushUiServices { public: void set_tip_color(float r, float g, float b, float a) override { color_sets += 1; last_r = r; last_g = g; last_b = b; last_a = a; call_order += "color;"; } void set_texture( pp::app::BrushUiTextureSlot slot, std::string_view path, std::string_view thumbnail_path) override { texture_sets += 1; last_slot = slot; last_path = std::string(path); last_thumbnail_path = std::string(thumbnail_path); call_order += "texture;"; } void replace_brush_from_preset(bool preserve_existing_color, bool load_resources) override { preset_replacements += 1; preserved_color = preserve_existing_color; loaded_resources = load_resources; call_order += "preset;"; } void refresh_brush_ui(bool update_color_ui, bool update_brush_ui) override { refreshes += 1; last_update_color_ui = update_color_ui; last_update_brush_ui = update_brush_ui; call_order += "refresh;"; } int color_sets = 0; int texture_sets = 0; int preset_replacements = 0; int refreshes = 0; float last_r = 0.0F; float last_g = 0.0F; float last_b = 0.0F; float last_a = 0.0F; pp::app::BrushUiTextureSlot last_slot = pp::app::BrushUiTextureSlot::tip; std::string last_path; std::string last_thumbnail_path; bool preserved_color = false; bool loaded_resources = false; bool last_update_color_ui = false; bool last_update_brush_ui = false; std::string call_order; }; void color_plan_validates_all_channels(pp::tests::Harness& harness) { const auto plan = pp::app::plan_brush_ui_color(0.25F, 0.5F, 0.75F, 1.0F); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, plan.value().operation == pp::app::BrushUiOperation::set_tip_color); PP_EXPECT(harness, plan.value().r == 0.25F); PP_EXPECT(harness, plan.value().g == 0.5F); PP_EXPECT(harness, plan.value().b == 0.75F); PP_EXPECT(harness, plan.value().a == 1.0F); PP_EXPECT(harness, plan.value().mutates_brush); PP_EXPECT(harness, plan.value().update_color_ui); PP_EXPECT(harness, !plan.value().update_brush_ui); } PP_EXPECT(harness, !pp::app::plan_brush_ui_color(-0.01F, 0.5F, 0.5F, 1.0F)); PP_EXPECT(harness, !pp::app::plan_brush_ui_color(0.5F, 1.01F, 0.5F, 1.0F)); PP_EXPECT(harness, !pp::app::plan_brush_ui_color(0.5F, 0.5F, std::nanf(""), 1.0F)); } void texture_plan_validates_path_and_slot(pp::tests::Harness& harness) { const auto plan = pp::app::plan_brush_ui_texture( pp::app::BrushUiTextureSlot::pattern, "data/patterns/noise.png", "data/patterns/thumbs/noise.png"); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, plan.value().operation == pp::app::BrushUiOperation::set_texture); PP_EXPECT(harness, plan.value().texture_slot == pp::app::BrushUiTextureSlot::pattern); PP_EXPECT(harness, plan.value().path == "data/patterns/noise.png"); PP_EXPECT(harness, plan.value().thumbnail_path == "data/patterns/thumbs/noise.png"); PP_EXPECT(harness, plan.value().loads_brush_resources); PP_EXPECT(harness, plan.value().update_color_ui); PP_EXPECT(harness, plan.value().update_brush_ui); } PP_EXPECT( harness, !pp::app::plan_brush_ui_texture(pp::app::BrushUiTextureSlot::tip, "", "thumb.png")); } void preset_plan_preserves_color_and_requires_brush(pp::tests::Harness& harness) { const auto plan = pp::app::plan_brush_ui_preset_replace(true); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, plan.value().operation == pp::app::BrushUiOperation::replace_brush_from_preset); PP_EXPECT(harness, plan.value().preserves_existing_color); PP_EXPECT(harness, plan.value().loads_brush_resources); PP_EXPECT(harness, plan.value().update_color_ui); PP_EXPECT(harness, plan.value().update_brush_ui); } PP_EXPECT(harness, !pp::app::plan_brush_ui_preset_replace(false)); } void stroke_settings_plan_updates_brush_preview(pp::tests::Harness& harness) { const auto plan = pp::app::plan_brush_ui_stroke_settings_changed(); PP_EXPECT(harness, plan.operation == pp::app::BrushUiOperation::stroke_settings_changed); PP_EXPECT(harness, plan.mutates_brush); PP_EXPECT(harness, plan.update_color_ui); PP_EXPECT(harness, plan.update_brush_ui); PP_EXPECT(harness, !plan.loads_brush_resources); } void executor_dispatches_color_and_refresh(pp::tests::Harness& harness) { FakeBrushUiServices services; const auto plan = pp::app::plan_brush_ui_color(0.25F, 0.5F, 0.75F, 1.0F); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, pp::app::execute_brush_ui_plan(plan.value(), services).ok()); } PP_EXPECT(harness, services.color_sets == 1); PP_EXPECT(harness, services.last_r == 0.25F); PP_EXPECT(harness, services.last_g == 0.5F); PP_EXPECT(harness, services.last_b == 0.75F); PP_EXPECT(harness, services.last_a == 1.0F); PP_EXPECT(harness, services.refreshes == 1); PP_EXPECT(harness, services.last_update_color_ui); PP_EXPECT(harness, !services.last_update_brush_ui); PP_EXPECT(harness, services.call_order == "color;refresh;"); } void executor_dispatches_texture_and_preset(pp::tests::Harness& harness) { FakeBrushUiServices services; const auto texture = pp::app::plan_brush_ui_texture( pp::app::BrushUiTextureSlot::dual, "data/brushes/dual.png", "data/brushes/thumbs/dual.png"); PP_EXPECT(harness, texture); if (texture) { PP_EXPECT(harness, pp::app::execute_brush_ui_plan(texture.value(), services).ok()); } const auto preset = pp::app::plan_brush_ui_preset_replace(true); PP_EXPECT(harness, preset); if (preset) { PP_EXPECT(harness, pp::app::execute_brush_ui_plan(preset.value(), services).ok()); } PP_EXPECT(harness, services.texture_sets == 1); PP_EXPECT(harness, services.last_slot == pp::app::BrushUiTextureSlot::dual); PP_EXPECT(harness, services.last_path == "data/brushes/dual.png"); PP_EXPECT(harness, services.last_thumbnail_path == "data/brushes/thumbs/dual.png"); PP_EXPECT(harness, services.preset_replacements == 1); PP_EXPECT(harness, services.preserved_color); PP_EXPECT(harness, services.loaded_resources); PP_EXPECT(harness, services.refreshes == 2); PP_EXPECT(harness, services.call_order == "texture;refresh;preset;refresh;"); } void executor_dispatches_stroke_refresh_only(pp::tests::Harness& harness) { FakeBrushUiServices services; const auto plan = pp::app::plan_brush_ui_stroke_settings_changed(); PP_EXPECT(harness, pp::app::execute_brush_ui_plan(plan, services).ok()); PP_EXPECT(harness, services.color_sets == 0); PP_EXPECT(harness, services.texture_sets == 0); PP_EXPECT(harness, services.preset_replacements == 0); PP_EXPECT(harness, services.refreshes == 1); PP_EXPECT(harness, services.last_update_color_ui); PP_EXPECT(harness, services.last_update_brush_ui); PP_EXPECT(harness, services.call_order == "refresh;"); } void executor_rejects_invalid_plan_payloads(pp::tests::Harness& harness) { FakeBrushUiServices services; pp::app::BrushUiPlan color; color.operation = pp::app::BrushUiOperation::set_tip_color; color.r = std::nanf(""); color.g = 0.0F; color.b = 0.0F; color.a = 1.0F; PP_EXPECT(harness, !pp::app::execute_brush_ui_plan(color, services).ok()); pp::app::BrushUiPlan texture; texture.operation = pp::app::BrushUiOperation::set_texture; texture.texture_slot = pp::app::BrushUiTextureSlot::tip; texture.path.clear(); PP_EXPECT(harness, !pp::app::execute_brush_ui_plan(texture, services).ok()); PP_EXPECT(harness, services.color_sets == 0); PP_EXPECT(harness, services.texture_sets == 0); PP_EXPECT(harness, services.refreshes == 0); } } // namespace int main() { pp::tests::Harness harness; harness.run("color plan validates all channels", color_plan_validates_all_channels); harness.run("texture plan validates path and slot", texture_plan_validates_path_and_slot); harness.run("preset plan preserves color and requires brush", preset_plan_preserves_color_and_requires_brush); harness.run("stroke settings plan updates brush preview", stroke_settings_plan_updates_brush_preview); harness.run("executor dispatches color and refresh", executor_dispatches_color_and_refresh); harness.run("executor dispatches texture and preset", executor_dispatches_texture_and_preset); harness.run("executor dispatches stroke refresh only", executor_dispatches_stroke_refresh_only); harness.run("executor rejects invalid plan payloads", executor_rejects_invalid_plan_payloads); return harness.finish(); }