fix: Make UICollection/EntityCollection match Python list semantics

Breaking change: UICollection.remove() now takes a value (element) instead
of an index, matching Python's list.remove() behavior.

New methods added to both UICollection and EntityCollection:
- pop([index]) -> element: Remove and return element at index (default: last)
- insert(index, element): Insert element at position

Semantic fixes:
- remove(element): Now removes first occurrence of element (was: remove by index)
- All methods now have docstrings documenting behavior

Note on z_index sorting: The collections are sorted by z_index before each
render. Using index-based operations (pop, insert) with non-default z_index
values may produce unexpected results. Use name-based .find() for stable
element access when z_index sorting is in use.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
John McCardle 2025-11-26 08:08:43 -05:00
commit afcb54d9fe
5 changed files with 564 additions and 31 deletions

View file

@ -790,30 +790,151 @@ PyObject* UICollection::extend(PyUICollectionObject* self, PyObject* iterable)
PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o) PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o)
{ {
if (!PyLong_Check(o)) auto vec = self->data.get();
{ if (!vec) {
PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove"); PyErr_SetString(PyExc_RuntimeError, "Collection data is null");
return NULL;
}
// Type checking - must be a UIDrawable subclass
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Drawable"))) {
PyErr_SetString(PyExc_TypeError,
"UICollection.remove requires a UI element (Frame, Caption, Sprite, Grid)");
return NULL;
}
// Get the C++ object from the Python object
std::shared_ptr<UIDrawable> search_drawable = nullptr;
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
search_drawable = ((PyUIFrameObject*)o)->data;
} else if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
search_drawable = ((PyUICaptionObject*)o)->data;
} else if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
search_drawable = ((PyUISpriteObject*)o)->data;
} else if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
search_drawable = ((PyUIGridObject*)o)->data;
}
if (!search_drawable) {
PyErr_SetString(PyExc_TypeError,
"UICollection.remove requires a UI element (Frame, Caption, Sprite, Grid)");
return NULL;
}
// Search for the object and remove first occurrence
for (auto it = vec->begin(); it != vec->end(); ++it) {
if (it->get() == search_drawable.get()) {
vec->erase(it);
McRFPy_API::markSceneNeedsSort();
Py_RETURN_NONE;
}
}
PyErr_SetString(PyExc_ValueError, "element not in UICollection");
return NULL;
}
PyObject* UICollection::pop(PyUICollectionObject* self, PyObject* args)
{
Py_ssize_t index = -1; // Default to last element
if (!PyArg_ParseTuple(args, "|n", &index)) {
return NULL;
}
auto vec = self->data.get();
if (!vec) {
PyErr_SetString(PyExc_RuntimeError, "Collection data is null");
return NULL;
}
if (vec->empty()) {
PyErr_SetString(PyExc_IndexError, "pop from empty UICollection");
return NULL; return NULL;
} }
long index = PyLong_AsLong(o);
// Handle negative indexing // Handle negative indexing
while (index < 0) index += self->data->size(); Py_ssize_t size = static_cast<Py_ssize_t>(vec->size());
if (index < 0) {
index += size;
}
if (index >= self->data->size()) if (index < 0 || index >= size) {
{ PyErr_SetString(PyExc_IndexError, "pop index out of range");
PyErr_SetString(PyExc_ValueError, "Index out of range");
return NULL; return NULL;
} }
// release the shared pointer at self->data[index]; // Get the element before removing
self->data->erase(self->data->begin() + index); std::shared_ptr<UIDrawable> drawable = (*vec)[index];
// Remove from vector
vec->erase(vec->begin() + index);
// Mark scene as needing resort after removing element
McRFPy_API::markSceneNeedsSort(); McRFPy_API::markSceneNeedsSort();
Py_INCREF(Py_None); // Convert to Python object and return
return Py_None; return convertDrawableToPython(drawable);
}
PyObject* UICollection::insert(PyUICollectionObject* self, PyObject* args)
{
Py_ssize_t index;
PyObject* o;
if (!PyArg_ParseTuple(args, "nO", &index, &o)) {
return NULL;
}
auto vec = self->data.get();
if (!vec) {
PyErr_SetString(PyExc_RuntimeError, "Collection data is null");
return NULL;
}
// Type checking - must be a UIDrawable subclass
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Drawable"))) {
PyErr_SetString(PyExc_TypeError,
"UICollection.insert requires a UI element (Frame, Caption, Sprite, Grid)");
return NULL;
}
// Get the C++ object from the Python object
std::shared_ptr<UIDrawable> drawable = nullptr;
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
drawable = ((PyUIFrameObject*)o)->data;
} else if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
drawable = ((PyUICaptionObject*)o)->data;
} else if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
drawable = ((PyUISpriteObject*)o)->data;
} else if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
drawable = ((PyUIGridObject*)o)->data;
}
if (!drawable) {
PyErr_SetString(PyExc_TypeError,
"UICollection.insert requires a UI element (Frame, Caption, Sprite, Grid)");
return NULL;
}
// Handle negative indexing and clamping (Python list.insert behavior)
Py_ssize_t size = static_cast<Py_ssize_t>(vec->size());
if (index < 0) {
index += size;
if (index < 0) {
index = 0;
}
} else if (index > size) {
index = size;
}
// Insert at position
vec->insert(vec->begin() + index, drawable);
McRFPy_API::markSceneNeedsSort();
Py_RETURN_NONE;
} }
PyObject* UICollection::index_method(PyUICollectionObject* self, PyObject* value) { PyObject* UICollection::index_method(PyUICollectionObject* self, PyObject* value) {
@ -1036,15 +1157,30 @@ PyObject* UICollection::find(PyUICollectionObject* self, PyObject* args, PyObjec
PyMethodDef UICollection::methods[] = { PyMethodDef UICollection::methods[] = {
{"append", (PyCFunction)UICollection::append, METH_O, {"append", (PyCFunction)UICollection::append, METH_O,
"Add an element to the end of the collection"}, "append(element)\n\n"
"Add an element to the end of the collection."},
{"extend", (PyCFunction)UICollection::extend, METH_O, {"extend", (PyCFunction)UICollection::extend, METH_O,
"Add all elements from an iterable to the collection"}, "extend(iterable)\n\n"
"Add all elements from an iterable to the collection."},
{"insert", (PyCFunction)UICollection::insert, METH_VARARGS,
"insert(index, element)\n\n"
"Insert element at index. Like list.insert(), indices past the end append.\n\n"
"Note: If using z_index for sorting, insertion order may not persist after\n"
"the next render. Use name-based .find() for stable element access."},
{"remove", (PyCFunction)UICollection::remove, METH_O, {"remove", (PyCFunction)UICollection::remove, METH_O,
"Remove element at the given index"}, "remove(element)\n\n"
"Remove first occurrence of element. Raises ValueError if not found."},
{"pop", (PyCFunction)UICollection::pop, METH_VARARGS,
"pop([index]) -> element\n\n"
"Remove and return element at index (default: last element).\n\n"
"Note: If using z_index for sorting, indices may shift after render.\n"
"Use name-based .find() for stable element access."},
{"index", (PyCFunction)UICollection::index_method, METH_O, {"index", (PyCFunction)UICollection::index_method, METH_O,
"Return the index of an element in the collection"}, "index(element) -> int\n\n"
"Return index of first occurrence of element. Raises ValueError if not found."},
{"count", (PyCFunction)UICollection::count, METH_O, {"count", (PyCFunction)UICollection::count, METH_O,
"Count occurrences of an element in the collection"}, "count(element) -> int\n\n"
"Count occurrences of element in the collection."},
{"find", (PyCFunction)UICollection::find, METH_VARARGS | METH_KEYWORDS, {"find", (PyCFunction)UICollection::find, METH_VARARGS | METH_KEYWORDS,
"find(name, recursive=False) -> element or list\n\n" "find(name, recursive=False) -> element or list\n\n"
"Find elements by name.\n\n" "Find elements by name.\n\n"

View file

@ -30,6 +30,8 @@ public:
static PyObject* append(PyUICollectionObject* self, PyObject* o); static PyObject* append(PyUICollectionObject* self, PyObject* o);
static PyObject* extend(PyUICollectionObject* self, PyObject* iterable); static PyObject* extend(PyUICollectionObject* self, PyObject* iterable);
static PyObject* remove(PyUICollectionObject* self, PyObject* o); static PyObject* remove(PyUICollectionObject* self, PyObject* o);
static PyObject* pop(PyUICollectionObject* self, PyObject* args);
static PyObject* insert(PyUICollectionObject* self, PyObject* args);
static PyObject* index_method(PyUICollectionObject* self, PyObject* value); static PyObject* index_method(PyUICollectionObject* self, PyObject* value);
static PyObject* count(PyUICollectionObject* self, PyObject* value); static PyObject* count(PyUICollectionObject* self, PyObject* value);
static PyObject* find(PyUICollectionObject* self, PyObject* args, PyObject* kwds); static PyObject* find(PyUICollectionObject* self, PyObject* args, PyObject* kwds);

View file

@ -1953,6 +1953,125 @@ PyObject* UIEntityCollection::extend(PyUIEntityCollectionObject* self, PyObject*
return Py_None; return Py_None;
} }
PyObject* UIEntityCollection::pop(PyUIEntityCollectionObject* self, PyObject* args)
{
Py_ssize_t index = -1; // Default to last element
if (!PyArg_ParseTuple(args, "|n", &index)) {
return NULL;
}
auto list = self->data.get();
if (!list) {
PyErr_SetString(PyExc_RuntimeError, "Collection data is null");
return NULL;
}
if (list->empty()) {
PyErr_SetString(PyExc_IndexError, "pop from empty EntityCollection");
return NULL;
}
// Handle negative indexing
Py_ssize_t size = static_cast<Py_ssize_t>(list->size());
if (index < 0) {
index += size;
}
if (index < 0 || index >= size) {
PyErr_SetString(PyExc_IndexError, "pop index out of range");
return NULL;
}
// Navigate to the element (std::list requires iteration)
auto it = list->begin();
std::advance(it, index);
// Get the entity before removing
std::shared_ptr<UIEntity> entity = *it;
// Clear grid reference and remove from list
entity->grid = nullptr;
list->erase(it);
// Create Python object for the entity
PyTypeObject* entityType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
if (!entityType) {
PyErr_SetString(PyExc_RuntimeError, "Could not find Entity type");
return NULL;
}
PyUIEntityObject* py_entity = (PyUIEntityObject*)entityType->tp_alloc(entityType, 0);
Py_DECREF(entityType);
if (!py_entity) {
return NULL;
}
py_entity->data = entity;
py_entity->weakreflist = NULL;
return (PyObject*)py_entity;
}
PyObject* UIEntityCollection::insert(PyUIEntityCollectionObject* self, PyObject* args)
{
Py_ssize_t index;
PyObject* o;
if (!PyArg_ParseTuple(args, "nO", &index, &o)) {
return NULL;
}
auto list = self->data.get();
if (!list) {
PyErr_SetString(PyExc_RuntimeError, "Collection data is null");
return NULL;
}
// Type checking - must be an Entity
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
PyErr_SetString(PyExc_TypeError, "EntityCollection.insert requires an Entity object");
return NULL;
}
PyUIEntityObject* entity = (PyUIEntityObject*)o;
if (!entity->data) {
PyErr_SetString(PyExc_RuntimeError, "Invalid Entity object");
return NULL;
}
// Handle negative indexing and clamping (Python list.insert behavior)
Py_ssize_t size = static_cast<Py_ssize_t>(list->size());
if (index < 0) {
index += size;
if (index < 0) {
index = 0;
}
} else if (index > size) {
index = size;
}
// Navigate to insert position
auto it = list->begin();
std::advance(it, index);
// Insert and set grid reference
list->insert(it, entity->data);
entity->data->grid = self->grid;
// Initialize gridstate if needed
if (entity->data->gridstate.size() == 0 && self->grid) {
entity->data->gridstate.resize(self->grid->grid_x * self->grid->grid_y);
for (auto& state : entity->data->gridstate) {
state.visible = false;
state.discovered = false;
}
}
Py_RETURN_NONE;
}
PyObject* UIEntityCollection::index_method(PyUIEntityCollectionObject* self, PyObject* value) { PyObject* UIEntityCollection::index_method(PyUIEntityCollectionObject* self, PyObject* value) {
auto list = self->data.get(); auto list = self->data.get();
if (!list) { if (!list) {
@ -2317,15 +2436,26 @@ PyObject* UIEntityCollection::find(PyUIEntityCollectionObject* self, PyObject* a
PyMethodDef UIEntityCollection::methods[] = { PyMethodDef UIEntityCollection::methods[] = {
{"append", (PyCFunction)UIEntityCollection::append, METH_O, {"append", (PyCFunction)UIEntityCollection::append, METH_O,
"Add an entity to the collection"}, "append(entity)\n\n"
"Add an entity to the end of the collection."},
{"extend", (PyCFunction)UIEntityCollection::extend, METH_O, {"extend", (PyCFunction)UIEntityCollection::extend, METH_O,
"Add all entities from an iterable"}, "extend(iterable)\n\n"
"Add all entities from an iterable to the collection."},
{"insert", (PyCFunction)UIEntityCollection::insert, METH_VARARGS,
"insert(index, entity)\n\n"
"Insert entity at index. Like list.insert(), indices past the end append."},
{"remove", (PyCFunction)UIEntityCollection::remove, METH_O, {"remove", (PyCFunction)UIEntityCollection::remove, METH_O,
"Remove an entity from the collection"}, "remove(entity)\n\n"
"Remove first occurrence of entity. Raises ValueError if not found."},
{"pop", (PyCFunction)UIEntityCollection::pop, METH_VARARGS,
"pop([index]) -> entity\n\n"
"Remove and return entity at index (default: last entity)."},
{"index", (PyCFunction)UIEntityCollection::index_method, METH_O, {"index", (PyCFunction)UIEntityCollection::index_method, METH_O,
"Return the index of an entity"}, "index(entity) -> int\n\n"
"Return index of first occurrence of entity. Raises ValueError if not found."},
{"count", (PyCFunction)UIEntityCollection::count, METH_O, {"count", (PyCFunction)UIEntityCollection::count, METH_O,
"Count occurrences of an entity"}, "count(entity) -> int\n\n"
"Count occurrences of entity in the collection."},
{"find", (PyCFunction)UIEntityCollection::find, METH_VARARGS | METH_KEYWORDS, {"find", (PyCFunction)UIEntityCollection::find, METH_VARARGS | METH_KEYWORDS,
"find(name) -> entity or list\n\n" "find(name) -> entity or list\n\n"
"Find entities by name.\n\n" "Find entities by name.\n\n"

View file

@ -141,6 +141,8 @@ public:
static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o); static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o); static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o); static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* pop(PyUIEntityCollectionObject* self, PyObject* args);
static PyObject* insert(PyUIEntityCollectionObject* self, PyObject* args);
static PyObject* index_method(PyUIEntityCollectionObject* self, PyObject* value); static PyObject* index_method(PyUIEntityCollectionObject* self, PyObject* value);
static PyObject* count(PyUIEntityCollectionObject* self, PyObject* value); static PyObject* count(PyUIEntityCollectionObject* self, PyObject* value);
static PyObject* find(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds); static PyObject* find(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);

View file

@ -0,0 +1,263 @@
#!/usr/bin/env python3
"""Test for Python list-like methods on UICollection and EntityCollection.
Tests that remove(), pop(), insert(), index(), count() match Python list semantics.
"""
import mcrfpy
import sys
def test_uicollection_remove():
"""Test UICollection.remove() takes a value, not an index."""
print("Testing UICollection.remove()...")
mcrfpy.createScene("test_remove")
ui = mcrfpy.sceneUI("test_remove")
frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
frame2 = mcrfpy.Frame(pos=(100, 0), size=(100, 100))
frame3 = mcrfpy.Frame(pos=(200, 0), size=(100, 100))
ui.append(frame1)
ui.append(frame2)
ui.append(frame3)
assert len(ui) == 3
# Remove by value (like Python list)
ui.remove(frame2)
assert len(ui) == 2
print(" [PASS] remove(element) works")
# Verify frame2 is gone, but frame1 and frame3 remain
assert ui[0] is not None
assert ui[1] is not None
# Try to remove something not in the list
try:
frame4 = mcrfpy.Frame(pos=(300, 0), size=(100, 100))
ui.remove(frame4)
assert False, "Should have raised ValueError"
except ValueError as e:
assert "not in" in str(e).lower()
print(" [PASS] remove() raises ValueError when not found")
# Try to pass an integer (should fail - no longer takes index)
try:
ui.remove(0)
assert False, "Should have raised TypeError"
except TypeError:
print(" [PASS] remove(int) raises TypeError (correct - takes element, not index)")
print("UICollection.remove() tests passed!")
return True
def test_uicollection_pop():
"""Test UICollection.pop() removes and returns element at index."""
print("\nTesting UICollection.pop()...")
mcrfpy.createScene("test_pop")
ui = mcrfpy.sceneUI("test_pop")
frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
frame1.name = "first"
frame2 = mcrfpy.Frame(pos=(100, 0), size=(100, 100))
frame2.name = "second"
frame3 = mcrfpy.Frame(pos=(200, 0), size=(100, 100))
frame3.name = "third"
ui.append(frame1)
ui.append(frame2)
ui.append(frame3)
# pop() with no args removes last
popped = ui.pop()
assert popped.name == "third", f"Expected 'third', got '{popped.name}'"
assert len(ui) == 2
print(" [PASS] pop() removes last element")
# pop(0) removes first
popped = ui.pop(0)
assert popped.name == "first", f"Expected 'first', got '{popped.name}'"
assert len(ui) == 1
print(" [PASS] pop(0) removes first element")
# pop(-1) is same as pop()
ui.append(mcrfpy.Frame(pos=(0, 0), size=(10, 10)))
ui[-1].name = "new_last"
popped = ui.pop(-1)
assert popped.name == "new_last"
print(" [PASS] pop(-1) removes last element")
# pop from empty collection
ui.pop() # Remove last remaining element
try:
ui.pop()
assert False, "Should have raised IndexError"
except IndexError as e:
assert "empty" in str(e).lower()
print(" [PASS] pop() from empty raises IndexError")
print("UICollection.pop() tests passed!")
return True
def test_uicollection_insert():
"""Test UICollection.insert() inserts at given index."""
print("\nTesting UICollection.insert()...")
mcrfpy.createScene("test_insert")
ui = mcrfpy.sceneUI("test_insert")
frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
frame1.name = "first"
frame3 = mcrfpy.Frame(pos=(200, 0), size=(100, 100))
frame3.name = "third"
ui.append(frame1)
ui.append(frame3)
# Insert in middle
frame2 = mcrfpy.Frame(pos=(100, 0), size=(100, 100))
frame2.name = "second"
ui.insert(1, frame2)
assert len(ui) == 3
assert ui[0].name == "first"
assert ui[1].name == "second"
assert ui[2].name == "third"
print(" [PASS] insert(1, element) inserts at index 1")
# Insert at beginning
frame0 = mcrfpy.Frame(pos=(0, 0), size=(50, 50))
frame0.name = "zero"
ui.insert(0, frame0)
assert ui[0].name == "zero"
print(" [PASS] insert(0, element) inserts at beginning")
# Insert at end (index > len)
frame_end = mcrfpy.Frame(pos=(0, 0), size=(50, 50))
frame_end.name = "end"
ui.insert(100, frame_end) # Way past end
assert ui[-1].name == "end"
print(" [PASS] insert(100, element) appends when index > len")
# Negative index
frame_neg = mcrfpy.Frame(pos=(0, 0), size=(50, 50))
frame_neg.name = "negative"
current_len = len(ui)
ui.insert(-1, frame_neg) # Insert before last
assert ui[-2].name == "negative"
print(" [PASS] insert(-1, element) inserts before last")
print("UICollection.insert() tests passed!")
return True
def test_entitycollection_pop_insert():
"""Test EntityCollection.pop() and insert()."""
print("\nTesting EntityCollection.pop() and insert()...")
mcrfpy.createScene("test_entity_pop")
ui = mcrfpy.sceneUI("test_entity_pop")
grid = mcrfpy.Grid(grid_size=(10, 10), pos=(0, 0), size=(400, 400))
ui.append(grid)
e1 = mcrfpy.Entity(grid_pos=(1, 1))
e1.name = "first"
e2 = mcrfpy.Entity(grid_pos=(2, 2))
e2.name = "second"
e3 = mcrfpy.Entity(grid_pos=(3, 3))
e3.name = "third"
grid.entities.append(e1)
grid.entities.append(e2)
grid.entities.append(e3)
# Test pop()
popped = grid.entities.pop()
assert popped.name == "third"
assert len(grid.entities) == 2
print(" [PASS] EntityCollection.pop() works")
# Test pop(0)
popped = grid.entities.pop(0)
assert popped.name == "first"
assert len(grid.entities) == 1
print(" [PASS] EntityCollection.pop(0) works")
# Test insert
e_new = mcrfpy.Entity(grid_pos=(5, 5))
e_new.name = "new"
grid.entities.insert(0, e_new)
assert grid.entities[0].name == "new"
print(" [PASS] EntityCollection.insert() works")
print("EntityCollection pop/insert tests passed!")
return True
def test_index_and_count():
"""Test index() and count() methods."""
print("\nTesting index() and count()...")
mcrfpy.createScene("test_index_count")
ui = mcrfpy.sceneUI("test_index_count")
frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
frame2 = mcrfpy.Frame(pos=(100, 0), size=(100, 100))
ui.append(frame1)
ui.append(frame2)
# index() returns integer
idx = ui.index(frame1)
assert idx == 0, f"Expected 0, got {idx}"
assert isinstance(idx, int)
print(" [PASS] index() returns integer")
idx = ui.index(frame2)
assert idx == 1
print(" [PASS] index() finds correct position")
# count() returns integer
cnt = ui.count(frame1)
assert cnt == 1
assert isinstance(cnt, int)
print(" [PASS] count() returns integer")
# count of element not in collection
frame3 = mcrfpy.Frame(pos=(200, 0), size=(100, 100))
cnt = ui.count(frame3)
assert cnt == 0
print(" [PASS] count() returns 0 for element not in collection")
print("index() and count() tests passed!")
return True
if __name__ == "__main__":
try:
all_passed = True
all_passed &= test_uicollection_remove()
all_passed &= test_uicollection_pop()
all_passed &= test_uicollection_insert()
all_passed &= test_entitycollection_pop_insert()
all_passed &= test_index_and_count()
if all_passed:
print("\n" + "="*50)
print("All list-like method tests PASSED!")
print("="*50)
sys.exit(0)
else:
print("\nSome tests FAILED!")
sys.exit(1)
except Exception as e:
print(f"\nTest failed with exception: {e}")
import traceback
traceback.print_exc()
sys.exit(1)