/** * test_glamac_view.c - Unit tests for view management * * Copyright (C) 2025 https://optics-design.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. */ #include "../test_framework.h" #include "../../include/glamac_view.h" #include "../../include/glass_data.h" // Test view initialization int test_view_initialization() { TEST_START("View Initialization"); ViewState view; init_view_state(&view, 800, 600); // Check initial values TEST_ASSERT_FLOAT_EQ(0.0f, view.offsetX, 0.001f, "Initial X offset should be 0"); TEST_ASSERT_FLOAT_EQ(0.0f, view.offsetY, 0.001f, "Initial Y offset should be 0"); TEST_ASSERT_FLOAT_EQ(1.0f, view.zoomLevel, 0.001f, "Initial zoom should be 1.0"); TEST_ASSERT_EQ(800, view.windowWidth, "Window width should be set correctly"); TEST_ASSERT_EQ(600, view.windowHeight, "Window height should be set correctly"); TEST_END(); } // Test coordinate transformations int test_coordinate_transformations() { TEST_START("Coordinate Transformations"); ViewState view; init_view_state(&view, 800, 600); // Initialize glass data for proper coordinate system initialize_glass_data(); // Test glass to screen coordinate conversion i32 screenX, screenY; data_to_screen_coords(60.0f, 1.5f, &view, &screenX, &screenY); // Screen coordinates should be within window bounds for typical glass values TEST_ASSERT(screenX >= 0 && screenX <= view.windowWidth, "Screen X coordinate should be within window bounds"); TEST_ASSERT(screenY >= 0 && screenY <= view.windowHeight, "Screen Y coordinate should be within window bounds"); // Test screen to glass coordinate conversion (inverse operation) f32 abbeNumber, refractiveIndex; screen_to_data_coords(screenX, screenY, &view, &abbeNumber, &refractiveIndex); // The inverse transformation should approximately recover original values TEST_ASSERT_FLOAT_EQ(60.0f, abbeNumber, 1.0f, "Inverse transformation should recover Abbe number"); TEST_ASSERT_FLOAT_EQ(1.5f, refractiveIndex, 0.01f, "Inverse transformation should recover refractive index"); cleanup_glass_data(); TEST_END(); } // Test zoom functionality int test_zoom_functionality() { TEST_START("Zoom Functionality"); ViewState view; init_view_state(&view, 800, 600); // Test zoom in f32 initial_zoom = view.zoomLevel; handle_mouse_wheel_zoom(1, 400, 300, &view); // Zoom in at center TEST_ASSERT(view.zoomLevel > initial_zoom, "Zoom should increase"); TEST_ASSERT(view.zoomLevel <= MAX_ZOOM, "Zoom should not exceed maximum"); // Test zoom out f32 zoomed_in = view.zoomLevel; handle_mouse_wheel_zoom(-1, 400, 300, &view); // Zoom out at center TEST_ASSERT(view.zoomLevel < zoomed_in, "Zoom should decrease"); TEST_ASSERT(view.zoomLevel >= MIN_ZOOM, "Zoom should not go below minimum"); // Test zoom limits for (int i = 0; i < 20; i++) { handle_mouse_wheel_zoom(1, 400, 300, &view); // Zoom in a lot } TEST_ASSERT_FLOAT_EQ(MAX_ZOOM, view.zoomLevel, 0.001f, "Should be clamped to max zoom"); for (int i = 0; i < 20; i++) { handle_mouse_wheel_zoom(-1, 400, 300, &view); // Zoom out a lot } TEST_ASSERT_FLOAT_EQ(MIN_ZOOM, view.zoomLevel, 0.001f, "Should be clamped to min zoom"); TEST_END(); } // Test panning functionality int test_panning_functionality() { TEST_START("Panning Functionality"); ViewState view; init_view_state(&view, 800, 600); // Test panning f32 initial_x = view.offsetX; f32 initial_y = view.offsetY; pan_view(&view, 100, 50); // Pan right and up TEST_ASSERT(view.offsetX != initial_x, "X offset should change with panning"); TEST_ASSERT(view.offsetY != initial_y, "Y offset should change with panning"); // Test panning in opposite direction pan_view(&view, -100, -50); // Pan left and down TEST_ASSERT_FLOAT_EQ(initial_x, view.offsetX, 0.1f, "Should return to approximately initial X after opposite panning"); TEST_ASSERT_FLOAT_EQ(initial_y, view.offsetY, 0.1f, "Should return to approximately initial Y after opposite panning"); TEST_END(); } // Test view fitting int test_view_fitting() { TEST_START("View Fitting"); ViewState view; init_view_state(&view, 800, 600); // Initialize glass data for fitting initialize_glass_data(); // Test fit to data fit_view_to_data(&view); // After fitting, the view should be adjusted to show all data // The exact values depend on the glass data, but zoom and offsets should be reasonable TEST_ASSERT(view.zoomLevel > 0.0f, "Zoom should be positive after fitting"); TEST_ASSERT(view.zoomLevel <= MAX_ZOOM, "Zoom should not exceed maximum after fitting"); // Offsets should be set to center the data TEST_ASSERT(view.offsetX != 0.0f || view.offsetY != 0.0f, "At least one offset should be non-zero after fitting (unless data is perfectly centered)"); cleanup_glass_data(); TEST_END(); } // Test clustering functionality int test_clustering() { TEST_START("Clustering"); ViewState view; init_view_state(&view, 800, 600); // Initialize glass data for clustering initialize_glass_data(); // Update clustering update_clustering(&view); // Check that clustering was performed TEST_ASSERT(view.clusterCount >= 0, "Cluster count should be non-negative"); TEST_ASSERT(view.clusterCount <= MAX_CLUSTERS, "Cluster count should not exceed maximum"); // If we have clusters, test their properties for (u32 i = 0; i < view.clusterCount; i++) { const GlassCluster* cluster = &view.clusters[i]; TEST_ASSERT(cluster->count > 0, "Each cluster should have at least one glass"); TEST_ASSERT(cluster->count <= MAX_CLUSTER_SIZE, "Cluster size should not exceed maximum"); // Center coordinates should be reasonable TEST_ASSERT(cluster->centerX >= 0 && cluster->centerX < view.windowWidth, "Cluster center X should be within window bounds"); TEST_ASSERT(cluster->centerY >= 0 && cluster->centerY < view.windowHeight, "Cluster center Y should be within window bounds"); } cleanup_glass_data(); TEST_END(); } // Test view reset int test_view_reset() { TEST_START("View Reset"); ViewState view; init_view_state(&view, 800, 600); // Modify the view view.offsetX = 100.0f; view.offsetY = 50.0f; view.zoomLevel = 2.0f; // Reset view reset_view(&view); // Check that view is back to initial state TEST_ASSERT_FLOAT_EQ(0.0f, view.offsetX, 0.001f, "X offset should be reset to 0"); TEST_ASSERT_FLOAT_EQ(0.0f, view.offsetY, 0.001f, "Y offset should be reset to 0"); TEST_ASSERT_FLOAT_EQ(1.0f, view.zoomLevel, 0.001f, "Zoom should be reset to 1.0"); TEST_END(); } // Test window resize handling int test_window_resize() { TEST_START("Window Resize"); ViewState view; init_view_state(&view, 800, 600); // Test resize resize_view(&view, 1024, 768); TEST_ASSERT_EQ(1024, view.windowWidth, "Window width should be updated"); TEST_ASSERT_EQ(768, view.windowHeight, "Window height should be updated"); // Test with zero dimensions (should be handled gracefully) resize_view(&view, 0, 0); TEST_ASSERT(view.windowWidth > 0, "Window width should remain positive"); TEST_ASSERT(view.windowHeight > 0, "Window height should remain positive"); TEST_END(); } // Main test runner int main() { printf(BLUE "=== View Management Unit Tests ===" RESET "\n\n"); RUN_TEST(test_view_initialization); RUN_TEST(test_coordinate_transformations); RUN_TEST(test_zoom_functionality); RUN_TEST(test_panning_functionality); RUN_TEST(test_view_fitting); RUN_TEST(test_clustering); RUN_TEST(test_view_reset); RUN_TEST(test_window_resize); TEST_SUMMARY(); }