xastir/tests/test_cad_objects.c
simonbleher a6f4134bba Reset CAD polygon draw state when deleting in-progress object
Fixes #4.

While drawing a CAD polygon, each new object is pushed onto the head
of CAD_list_head and further clicks append vertices to whatever is
currently at the head (Draw_CAD_Objects_mode / the click handler in
main.c). polygon_last_x/y track whether we're mid-draw.

CAD_object_delete() correctly unlinked and freed the deleted object,
but didn't touch polygon_last_x/y. Deleting the polygon currently
being drawn (which is always the list head, e.g. via
Draw_CAD_Objects_erase_selected() on an unclosed, unlabeled object)
left the draw state pointing at a "mid-draw" position, so the next
click on a new polygon appended its first vertice onto whatever
object was left at the head instead of starting fresh.

Now deleting the object at the head while mid-draw resets
polygon_last_x/y, forcing the next click to start a new object.

Adds a unit test suite (test_cad_objects) covering: deleting the
in-progress polygon resets the draw state and the next polygon
starts clean; deleting an unrelated object leaves an in-progress
draw untouched; deleting a closed polygon while idle is a no-op on
the draw state.
2026-07-26 23:31:27 +02:00

221 lines
6.5 KiB
C

/*
*
* XASTIR, Amateur Station Tracking and Information Reporting
* Copyright (C) 2025-2026 The Xastir Group
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Look at the README for more information on the program.
*/
/*
* Test program for CAD object deletion in cad_objects.c
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <Xm/XmAll.h>
#include "tests/test_framework.h"
#include "database.h"
#include "db_funcs.h"
#include "cad_objects.h"
// Not part of a public header, only used internally by
// Draw_CAD_Objects_erase_selected(). Declared here so we can test
// it directly.
extern void CAD_object_delete(CADRow *object);
static int object_count(void)
{
int n = 0;
CADRow *p = CAD_list_head;
while (p != NULL)
{
n++;
p = p->next;
}
return(n);
}
static int vertice_count(CADRow *object)
{
int n = 0;
VerticeRow *v = object->start;
while (v != NULL)
{
n++;
v = v->next;
}
return(n);
}
static void reset_state(void)
{
while (CAD_list_head != NULL)
{
CAD_object_delete(CAD_list_head);
}
polygon_last_x = -1;
polygon_last_y = -1;
}
// Reproduces the bug in issue #4: draw a polygon and close it,
// start a second polygon but leave it open, then delete that
// in-progress polygon and start a third one. Before the fix, the
// third polygon's first vertice was appended onto the first
// (closed) polygon instead of starting a new object.
int test_delete_in_progress_polygon_resets_draw_state(void)
{
reset_state();
// Draw and close polygon one.
CAD_object_allocate(1, 1);
CAD_vertice_allocate(2, 2);
CAD_vertice_allocate(3, 3);
polygon_last_x = -1; // Mimics Draw_CAD_Objects_close_polygon()
polygon_last_y = -1;
TEST_ASSERT(object_count() == 1, "one closed polygon should exist");
// Start polygon two, but leave it open (still mid-draw).
CAD_object_allocate(10, 10);
polygon_last_x = 100;
polygon_last_y = 100;
CAD_vertice_allocate(20, 20);
TEST_ASSERT(object_count() == 2, "second, in-progress polygon should exist");
TEST_ASSERT(vertice_count(CAD_list_head) == 2, "in-progress polygon should have two vertices");
// Delete the in-progress polygon (matches
// Draw_CAD_Objects_erase_selected() deleting the unlabeled,
// still-open object from the CAD object list).
CAD_object_delete(CAD_list_head);
TEST_ASSERT(object_count() == 1, "in-progress polygon should be gone");
TEST_ASSERT(polygon_last_x == -1, "draw state x should be reset after deleting in-progress polygon");
TEST_ASSERT(polygon_last_y == -1, "draw state y should be reset after deleting in-progress polygon");
// Start polygon three. Since polygon_last_x/y are back to -1,
// this must go through CAD_object_allocate() and create a new
// object rather than appending to polygon one via
// CAD_vertice_allocate().
CAD_object_allocate(30, 30);
polygon_last_x = 300;
polygon_last_y = 300;
TEST_ASSERT(object_count() == 2, "third polygon should be its own object");
TEST_ASSERT(vertice_count(CAD_list_head) == 1, "third polygon should start with a single vertice");
TEST_ASSERT(vertice_count(CAD_list_head->next) == 3, "first polygon's vertices should be untouched");
reset_state();
TEST_PASS("CAD_object_delete: deleting the in-progress polygon resets draw state");
}
// Deleting an object other than the one currently being drawn must
// not disturb the in-progress polygon's draw state.
int test_delete_other_object_does_not_reset_draw_state(void)
{
reset_state();
// Polygon one, closed.
CAD_object_allocate(1, 1);
CAD_vertice_allocate(2, 2);
polygon_last_x = -1;
polygon_last_y = -1;
// Polygon two, still open (this is the one at the head).
CAD_object_allocate(10, 10);
polygon_last_x = 100;
polygon_last_y = 100;
CAD_vertice_allocate(20, 20);
TEST_ASSERT(object_count() == 2, "two objects should exist");
// Delete polygon one (not the head, not the one being drawn).
CAD_object_delete(CAD_list_head->next);
TEST_ASSERT(object_count() == 1, "unrelated polygon should be gone");
TEST_ASSERT(polygon_last_x == 100, "draw state x should be untouched");
TEST_ASSERT(polygon_last_y == 100, "draw state y should be untouched");
TEST_ASSERT(vertice_count(CAD_list_head) == 2, "in-progress polygon should be unaffected");
reset_state();
TEST_PASS("CAD_object_delete: deleting an unrelated object leaves draw state alone");
}
// Deleting a closed polygon while nothing is being drawn should
// leave the (already inactive) draw state alone.
int test_delete_closed_polygon_while_idle(void)
{
reset_state();
CAD_object_allocate(1, 1);
CAD_vertice_allocate(2, 2);
polygon_last_x = -1;
polygon_last_y = -1;
CAD_object_delete(CAD_list_head);
TEST_ASSERT(object_count() == 0, "list should be empty");
TEST_ASSERT(polygon_last_x == -1, "draw state x should remain idle");
TEST_ASSERT(polygon_last_y == -1, "draw state y should remain idle");
TEST_PASS("CAD_object_delete: deleting a closed polygon while idle is a no-op on draw state");
}
typedef struct
{
const char *name;
int (*func)(void);
} test_case_t;
int main(int argc, char *argv[])
{
test_case_t tests[] =
{
{"delete_in_progress_polygon_resets_draw_state", test_delete_in_progress_polygon_resets_draw_state},
{"delete_other_object_does_not_reset_draw_state", test_delete_other_object_does_not_reset_draw_state},
{"delete_closed_polygon_while_idle", test_delete_closed_polygon_while_idle},
{NULL, NULL}
};
if (argc < 2)
{
fprintf(stderr, "Usage: %s <test name>\n", argv[0]);
fprintf(stderr, "Available tests: \n");
for (int i = 0; tests[i].name != NULL; i++)
{
fprintf(stderr, " %s\n", tests[i].name);
}
return 1;
}
const char *test_name = argv[1];
for (int i = 0; tests[i].name != NULL; i++)
{
if (strcmp(test_name, tests[i].name) == 0)
{
return tests[i].func();
}
}
fprintf(stderr, "Unknown test: %s\n", test_name);
return 1;
}