Summary
A bug was identified in src/collection/Base.mjs where the move() method failed to correctly swap adjacent items in a collection. This was discovered as a regression issue in src/draggable/grid/header/toolbar/SortZone.mjs after the columnPositions data structure was migrated from a standard Array to a Neo.collection.Base.
The Bug
The root cause was the implementation of the move() method, which used a nested, one-liner splice() call:
items.splice(toIndex, 0, items.splice(fromIndex, 1)[0]);
This common pattern, while concise, can lead to unpredictable side effects. The inner splice() modifies the array, and depending on the JavaScript engine's implementation, this can happen before the toIndex for the outer splice() is correctly resolved. This resulted in the move operation failing, particularly when swapping an item at index n with an item at index n+1.
The Fix
The move() method was refactored to unroll the operation into two distinct and safer steps:
- Remove: The item at
fromIndex is explicitly removed from the array and stored in a constant.
- Insert: The stored item is then inserted at the
toIndex.
const item = items.splice(fromIndex, 1)[0];
items.splice(toIndex, 0, item);
This two-step approach guarantees that the indices are correct for both operations, resolving the bug.
Code Quality Enhancements
- Intent-driven Comment: A detailed comment was added to the
move() method to explain why the two-step approach is used, preventing future developers from "optimizing" it back to the buggy one-liner.
- Unit Tests: A comprehensive test suite was added to
test/siesta/tests/CollectionBase.mjs to cover various move scenarios, including forward, backward, and adjacent swaps, ensuring the fix is robust and preventing future regressions.
This ticket documents the successful resolution of the issue.
Summary
A bug was identified in
src/collection/Base.mjswhere themove()method failed to correctly swap adjacent items in a collection. This was discovered as a regression issue insrc/draggable/grid/header/toolbar/SortZone.mjsafter thecolumnPositionsdata structure was migrated from a standardArrayto aNeo.collection.Base.The Bug
The root cause was the implementation of the
move()method, which used a nested, one-linersplice()call:// old implementation items.splice(toIndex, 0, items.splice(fromIndex, 1)[0]);This common pattern, while concise, can lead to unpredictable side effects. The inner
splice()modifies the array, and depending on the JavaScript engine's implementation, this can happen before thetoIndexfor the outersplice()is correctly resolved. This resulted in the move operation failing, particularly when swapping an item at indexnwith an item at indexn+1.The Fix
The
move()method was refactored to unroll the operation into two distinct and safer steps:fromIndexis explicitly removed from the array and stored in a constant.toIndex.// new implementation const item = items.splice(fromIndex, 1)[0]; items.splice(toIndex, 0, item);This two-step approach guarantees that the indices are correct for both operations, resolving the bug.
Code Quality Enhancements
move()method to explain why the two-step approach is used, preventing future developers from "optimizing" it back to the buggy one-liner.test/siesta/tests/CollectionBase.mjsto cover various move scenarios, including forward, backward, and adjacent swaps, ensuring the fix is robust and preventing future regressions.This ticket documents the successful resolution of the issue.