summaryrefslogtreecommitdiff
path: root/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit')
-rw-r--r--tests/unit/test_fgla.c231
-rw-r--r--tests/unit/test_glamac_view.c243
-rw-r--r--tests/unit/test_glamac_view_simple.c168
-rw-r--r--tests/unit/test_glass_data.c174
4 files changed, 816 insertions, 0 deletions
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
Back to https://optics-design.com