From 04b3fcb479f5aaae06d18b315a8bdc8c298f4eae Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 5 Aug 2025 11:28:41 +0200 Subject: removed clustering --- tests/unit/test_fgla.c | 231 +++++++++++++++++++++++++++++++++ tests/unit/test_glamac_view.c | 243 +++++++++++++++++++++++++++++++++++ tests/unit/test_glamac_view_simple.c | 168 ++++++++++++++++++++++++ tests/unit/test_glass_data.c | 174 +++++++++++++++++++++++++ 4 files changed, 816 insertions(+) create mode 100644 tests/unit/test_fgla.c create mode 100644 tests/unit/test_glamac_view.c create mode 100644 tests/unit/test_glamac_view_simple.c create mode 100644 tests/unit/test_glass_data.c (limited to 'tests/unit') diff --git a/tests/unit/test_fgla.c b/tests/unit/test_fgla.c new file mode 100644 index 0000000..60f4e7b --- /dev/null +++ b/tests/unit/test_fgla.c @@ -0,0 +1,231 @@ +/** + * test_fgla.c - Unit tests for fgla utility functions + * + * 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/glautils/fgla.h" + +// Test string validation +int test_search_term_validation() { + TEST_START("Search Term Validation"); + + // Valid search terms + TEST_ASSERT(fgla_validate_search_term("BK7"), "BK7 should be valid"); + TEST_ASSERT(fgla_validate_search_term("N-SF6"), "N-SF6 should be valid"); + TEST_ASSERT(fgla_validate_search_term("517642"), "517642 should be valid"); + TEST_ASSERT(fgla_validate_search_term("51x64x"), "51x64x should be valid"); + TEST_ASSERT(fgla_validate_search_term("FK51A"), "FK51A should be valid"); + + // Invalid search terms + TEST_ASSERT(!fgla_validate_search_term(NULL), "NULL should be invalid"); + TEST_ASSERT(!fgla_validate_search_term(""), "Empty string should be invalid"); + TEST_ASSERT(!fgla_validate_search_term("test@#$"), "Special characters should be invalid"); + TEST_ASSERT(!fgla_validate_search_term("test|pipe"), "Pipe character should be invalid"); + + // Test boundary conditions + char long_term[300]; + memset(long_term, 'A', sizeof(long_term) - 1); + long_term[sizeof(long_term) - 1] = '\0'; + TEST_ASSERT(!fgla_validate_search_term(long_term), "Overly long term should be invalid"); + + TEST_END(); +} + +// Test glass code pattern recognition +int test_glass_code_pattern() { + TEST_START("Glass Code Pattern Recognition"); + + // Valid glass code patterns + TEST_ASSERT(fgla_is_glass_code_pattern("517642"), "517642 should be glass code pattern"); + TEST_ASSERT(fgla_is_glass_code_pattern("123456"), "123456 should be glass code pattern"); + TEST_ASSERT(fgla_is_glass_code_pattern("51x64x"), "51x64x should be glass code pattern"); + TEST_ASSERT(fgla_is_glass_code_pattern("x12345"), "x12345 should be glass code pattern"); + TEST_ASSERT(fgla_is_glass_code_pattern("xxxxxx"), "xxxxxx should be glass code pattern"); + + // Invalid patterns + TEST_ASSERT(!fgla_is_glass_code_pattern("51764"), "51764 (5 digits) should not be glass code pattern"); + TEST_ASSERT(!fgla_is_glass_code_pattern("5176420"), "5176420 (7 digits) should not be glass code pattern"); + TEST_ASSERT(!fgla_is_glass_code_pattern("BK7123"), "BK7123 (letters) should not be glass code pattern"); + TEST_ASSERT(!fgla_is_glass_code_pattern("51-642"), "51-642 (dash) should not be glass code pattern"); + TEST_ASSERT(!fgla_is_glass_code_pattern(NULL), "NULL should not be glass code pattern"); + + TEST_END(); +} + +// Test glass code pattern matching +int test_glass_code_matching() { + TEST_START("Glass Code Pattern Matching"); + + // Exact matches + TEST_ASSERT(fgla_matches_glass_code_pattern_safe("517642", "517642"), + "Exact match should work"); + + // Wildcard matches + TEST_ASSERT(fgla_matches_glass_code_pattern_safe("517642", "51x64x"), + "Wildcard pattern should match"); + TEST_ASSERT(fgla_matches_glass_code_pattern_safe("517642", "x17642"), + "Leading wildcard should match"); + TEST_ASSERT(fgla_matches_glass_code_pattern_safe("517642", "51764x"), + "Trailing wildcard should match"); + + // Non-matches + TEST_ASSERT(!fgla_matches_glass_code_pattern_safe("517642", "518642"), + "Different digit should not match"); + TEST_ASSERT(!fgla_matches_glass_code_pattern_safe("517642", "51x63x"), + "Wrong wildcard pattern should not match"); + + // Handle longer glass codes (should extract first 6 digits) + TEST_ASSERT(fgla_matches_glass_code_pattern_safe("5176420123", "517642"), + "Should match first 6 digits of longer code"); + + // Edge cases + TEST_ASSERT(!fgla_matches_glass_code_pattern_safe(NULL, "517642"), + "NULL glass code should not match"); + TEST_ASSERT(!fgla_matches_glass_code_pattern_safe("517642", NULL), + "NULL pattern should not match"); + + TEST_END(); +} + +// Test string normalization +int test_string_normalization() { + TEST_START("String Normalization"); + + char output[100]; + + // Test basic normalization + int result = fgla_normalize_string_safe("N-BK7", output, sizeof(output)); + TEST_ASSERT_EQ(0, result, "Normalization should succeed"); + TEST_ASSERT_STR_EQ("nbk7", output, "Should remove dash and convert to lowercase"); + + // Test with multiple dashes + result = fgla_normalize_string_safe("N-SF-6", output, sizeof(output)); + TEST_ASSERT_EQ(0, result, "Multi-dash normalization should succeed"); + TEST_ASSERT_STR_EQ("nsf6", output, "Should remove all dashes"); + + // Test with no dashes + result = fgla_normalize_string_safe("BK7", output, sizeof(output)); + TEST_ASSERT_EQ(0, result, "No-dash normalization should succeed"); + TEST_ASSERT_STR_EQ("bk7", output, "Should just convert to lowercase"); + + // Test error conditions + result = fgla_normalize_string_safe(NULL, output, sizeof(output)); + TEST_ASSERT_EQ(-1, result, "NULL input should return error"); + + result = fgla_normalize_string_safe("test", NULL, sizeof(output)); + TEST_ASSERT_EQ(-1, result, "NULL output should return error"); + + result = fgla_normalize_string_safe("test", output, 0); + TEST_ASSERT_EQ(-1, result, "Zero-size output should return error"); + + TEST_END(); +} + +// Test substring search +int test_substring_search() { + TEST_START("Substring Search"); + + // Case-insensitive search + TEST_ASSERT(fgla_contains_substring_safe("N-BK7", "bk"), + "Should find 'bk' in 'N-BK7' (case insensitive)"); + TEST_ASSERT(fgla_contains_substring_safe("N-BK7", "BK"), + "Should find 'BK' in 'N-BK7'"); + + // Dash-insensitive search + TEST_ASSERT(fgla_contains_substring_safe("N-BK7", "nbk"), + "Should find 'nbk' in 'N-BK7' (dash insensitive)"); + TEST_ASSERT(fgla_contains_substring_safe("NBK7", "n-bk"), + "Should find 'n-bk' in 'NBK7' (dash insensitive)"); + + // Non-matches + TEST_ASSERT(!fgla_contains_substring_safe("N-BK7", "sf"), + "Should not find 'sf' in 'N-BK7'"); + + // Edge cases + TEST_ASSERT(!fgla_contains_substring_safe(NULL, "test"), + "NULL haystack should return false"); + TEST_ASSERT(!fgla_contains_substring_safe("test", NULL), + "NULL needle should return false"); + TEST_ASSERT(!fgla_contains_substring_safe("", "test"), + "Empty haystack should return false"); + TEST_ASSERT(!fgla_contains_substring_safe("test", ""), + "Empty needle should return false"); + + TEST_END(); +} + +// Test catalog matching +int test_catalog_matching() { + TEST_START("Catalog Matching"); + + const char* catalogs[] = {"SCHOTT", "HOYA", "CDGM"}; + + // Should match + TEST_ASSERT(fgla_matches_catalog("SCHOTT", catalogs, 3), + "Should match SCHOTT"); + TEST_ASSERT(fgla_matches_catalog("schott", catalogs, 3), + "Should match schott (case insensitive)"); + TEST_ASSERT(fgla_matches_catalog("HOYA", catalogs, 3), + "Should match HOYA"); + + // Should not match + TEST_ASSERT(!fgla_matches_catalog("Ohara", catalogs, 3), + "Should not match Ohara (not in list)"); + + // Empty catalog list should match all + TEST_ASSERT(fgla_matches_catalog("SCHOTT", NULL, 0), + "Empty catalog list should match anything"); + TEST_ASSERT(fgla_matches_catalog("Unknown", NULL, 0), + "Empty catalog list should match anything"); + + TEST_END(); +} + +// Test lowercase conversion +int test_lowercase_conversion() { + TEST_START("Lowercase Conversion"); + + char test_str[20]; + + // Test normal case + strcpy(test_str, "BK7"); + fgla_to_lowercase_safe(test_str, sizeof(test_str)); + TEST_ASSERT_STR_EQ("bk7", test_str, "Should convert BK7 to bk7"); + + // Test mixed case + strcpy(test_str, "N-SF6"); + fgla_to_lowercase_safe(test_str, sizeof(test_str)); + TEST_ASSERT_STR_EQ("n-sf6", test_str, "Should convert N-SF6 to n-sf6"); + + // Test already lowercase + strcpy(test_str, "already"); + fgla_to_lowercase_safe(test_str, sizeof(test_str)); + TEST_ASSERT_STR_EQ("already", test_str, "Should not change already lowercase"); + + // Test with NULL (should not crash) + fgla_to_lowercase_safe(NULL, 10); // Should not crash + + TEST_END(); +} + +// Main test runner +int main() { + printf(BLUE "=== FGLA Utility Unit Tests ===" RESET "\n\n"); + + RUN_TEST(test_search_term_validation); + RUN_TEST(test_glass_code_pattern); + RUN_TEST(test_glass_code_matching); + RUN_TEST(test_string_normalization); + RUN_TEST(test_substring_search); + RUN_TEST(test_catalog_matching); + RUN_TEST(test_lowercase_conversion); + + TEST_SUMMARY(); +} \ No newline at end of file diff --git a/tests/unit/test_glamac_view.c b/tests/unit/test_glamac_view.c new file mode 100644 index 0000000..2decc3f --- /dev/null +++ b/tests/unit/test_glamac_view.c @@ -0,0 +1,243 @@ +/** + * 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(); +} \ No newline at end of file diff --git a/tests/unit/test_glamac_view_simple.c b/tests/unit/test_glamac_view_simple.c new file mode 100644 index 0000000..b17e4b9 --- /dev/null +++ b/tests/unit/test_glamac_view_simple.c @@ -0,0 +1,168 @@ +/** + * test_glamac_view_simple.c - Simplified 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(); + refresh_view_data_range(&view); + + // Test data to screen coordinate conversion + i32 screenX, screenY; + data_to_screen_coords(60.0f, 1.5f, &view, &screenX, &screenY); + + // Screen coordinates should be within reasonable bounds + TEST_ASSERT(screenX >= -100 && screenX <= view.windowWidth + 100, + "Screen X coordinate should be near window bounds"); + TEST_ASSERT(screenY >= -100 && screenY <= view.windowHeight + 100, + "Screen Y coordinate should be near window bounds"); + + // Test screen to data 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_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 data range functionality +int test_data_range() { + TEST_START("Data Range"); + + ViewState view; + init_view_state(&view, 800, 600); + + initialize_glass_data(); + refresh_view_data_range(&view); + + // After refreshing, we should have valid data ranges + TEST_ASSERT(view.minAbbe < view.maxAbbe, "Min Abbe should be less than max Abbe"); + TEST_ASSERT(view.minRI < view.maxRI, "Min RI should be less than max RI"); + TEST_ASSERT(view.minAbbe > 0.0f, "Min Abbe should be positive"); + TEST_ASSERT(view.maxAbbe < 200.0f, "Max Abbe should be reasonable"); + TEST_ASSERT(view.minRI > 1.0f, "Min RI should be greater than 1.0"); + TEST_ASSERT(view.maxRI < 4.0f, "Max RI should be reasonable"); + + cleanup_glass_data(); + TEST_END(); +} + +// Test visible range calculation +int test_visible_range() { + TEST_START("Visible Range Calculation"); + + ViewState view; + init_view_state(&view, 800, 600); + + initialize_glass_data(); + refresh_view_data_range(&view); + + f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI; + get_visible_data_range(&view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI); + + // Visible range should be valid + TEST_ASSERT(visibleMinAbbe < visibleMaxAbbe, "Visible min Abbe should be less than max"); + TEST_ASSERT(visibleMinRI < visibleMaxRI, "Visible min RI should be less than max"); + + cleanup_glass_data(); + TEST_END(); +} + +// Main test runner +int main() { + printf(BLUE "=== View Management Unit Tests (Simplified) ===" RESET "\n\n"); + + RUN_TEST(test_view_initialization); + RUN_TEST(test_coordinate_transformations); + RUN_TEST(test_zoom_functionality); + RUN_TEST(test_view_reset); + RUN_TEST(test_data_range); + RUN_TEST(test_visible_range); + + TEST_SUMMARY(); +} \ No newline at end of file diff --git a/tests/unit/test_glass_data.c b/tests/unit/test_glass_data.c new file mode 100644 index 0000000..3498747 --- /dev/null +++ b/tests/unit/test_glass_data.c @@ -0,0 +1,174 @@ +/** + * test_glass_data.c - Unit tests for glass data 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/glass_data.h" +#include "../../include/glamac_errors.h" + +// Test glass data initialization +int test_glass_data_initialization() { + TEST_START("Glass Data Initialization"); + + initialize_glass_data(); + + // After initialization, we should have at least the default glasses + u32 count = get_glass_count(); + TEST_ASSERT(count > 0, "Should have at least some default glasses"); + + // Test that we can get the first glass + const Glass* glass = get_glass(0); + TEST_ASSERT_NOT_NULL(glass, "Should be able to get first glass"); + + // Test bounds checking - invalid index should return NULL + const Glass* invalid_glass = get_glass(999999); + TEST_ASSERT_NULL(invalid_glass, "Invalid index should return NULL"); + + TEST_END(); +} + +// Test glass data range calculation +int test_glass_data_range() { + TEST_START("Glass Data Range Calculation"); + + initialize_glass_data(); + + f32 minAbbe, maxAbbe, minRI, maxRI; + find_glass_data_range(&minAbbe, &maxAbbe, &minRI, &maxRI); + + // Validate that ranges make sense + TEST_ASSERT(minAbbe < maxAbbe, "Min Abbe should be less than max Abbe"); + TEST_ASSERT(minRI < maxRI, "Min RI should be less than max RI"); + TEST_ASSERT(minAbbe > 0.0f, "Min Abbe should be positive"); + TEST_ASSERT(maxAbbe < 200.0f, "Max Abbe should be reasonable (< 200)"); + TEST_ASSERT(minRI > 1.0f, "Min RI should be greater than 1.0"); + TEST_ASSERT(maxRI < 4.0f, "Max RI should be reasonable (< 4.0)"); + + TEST_END(); +} + +// Test JSON loading with test data +int test_json_loading() { + TEST_START("JSON Loading"); + + // Try to load test data + b32 result = load_glasses_from_json((const byte*)"tests/data/test_glasses.json", NULL); + + if (result) { + // Verify that we loaded some glasses + u32 count = get_glass_count(); + TEST_ASSERT(count > 0, "Should have loaded glasses from test JSON"); + + // Test catalog functionality + u32 catalog_count = get_catalog_count(); + TEST_ASSERT(catalog_count > 0, "Should have at least one catalog"); + + // Test catalog name retrieval + const char* catalog_name = get_catalog_name(0); + TEST_ASSERT_NOT_NULL(catalog_name, "Should be able to get catalog name"); + + // Test catalog switching + if (catalog_count > 1) { + set_current_catalog(1); + const char* second_catalog = get_current_catalog_name(); + TEST_ASSERT_NOT_NULL(second_catalog, "Should be able to switch catalogs"); + } + } else { + printf(YELLOW " Warning: Could not load test JSON file - this may be expected in some test environments" RESET "\n"); + } + + TEST_END(); +} + +// Test glass name retrieval +int test_glass_name_retrieval() { + TEST_START("Glass Name Retrieval"); + + initialize_glass_data(); + + u32 count = get_glass_count(); + if (count > 0) { + const byte* name = get_glass_name(0); + TEST_ASSERT_NOT_NULL(name, "Should be able to get glass name"); + + // Verify name is not empty + TEST_ASSERT(strlen((const char*)name) > 0, "Glass name should not be empty"); + } + + // Test invalid index + const byte* invalid_name = get_glass_name(999999); + TEST_ASSERT_NULL(invalid_name, "Invalid index should return NULL name"); + + TEST_END(); +} + +// Test catalog cycling +int test_catalog_cycling() { + TEST_START("Catalog Cycling"); + + initialize_glass_data(); + + u32 initial_catalog_count = get_catalog_count(); + + if (initial_catalog_count > 1) { + // Get initial catalog + const char* initial_name = get_current_catalog_name(); + TEST_ASSERT_NOT_NULL(initial_name, "Should have current catalog name"); + + // Cycle forward + cycle_catalog(1); + const char* next_name = get_current_catalog_name(); + TEST_ASSERT_NOT_NULL(next_name, "Should have catalog name after cycling"); + + // Cycle backward + cycle_catalog(-1); + const char* back_name = get_current_catalog_name(); + TEST_ASSERT_STR_EQ(initial_name, back_name, "Should return to initial catalog after cycling back"); + } else { + printf(YELLOW " Info: Only one catalog available - skipping cycling test" RESET "\n"); + } + + TEST_END(); +} + +// Test data validation with invalid data +int test_data_validation() { + TEST_START("Data Validation"); + + // This test would require access to internal validation functions + // For now, we'll test through the public API by trying to load invalid data + + // Try loading from non-existent file + b32 result = load_glasses_from_json((const byte*)"nonexistent_file.json", NULL); + TEST_ASSERT(!result, "Should fail to load non-existent file"); + + // Test with invalid file path (NULL) + b32 null_result = load_glasses_from_json(NULL, NULL); + TEST_ASSERT(!null_result, "Should fail with NULL path"); + + TEST_END(); +} + +// Main test runner +int main() { + printf(BLUE "=== Glass Data Unit Tests ===" RESET "\n\n"); + + RUN_TEST(test_glass_data_initialization); + RUN_TEST(test_glass_data_range); + RUN_TEST(test_json_loading); + RUN_TEST(test_glass_name_retrieval); + RUN_TEST(test_catalog_cycling); + RUN_TEST(test_data_validation); + + // Cleanup + cleanup_glass_data(); + + TEST_SUMMARY(); +} \ No newline at end of file -- cgit v1.2.3