diff --git a/integration-tests/create-state/use-value-iterator.test.ts b/integration-tests/create-state/use-value-iterator.test.ts index e4db765..76e8d5a 100644 --- a/integration-tests/create-state/use-value-iterator.test.ts +++ b/integration-tests/create-state/use-value-iterator.test.ts @@ -187,4 +187,72 @@ describe("createState", () => { expect(listElement.childNodes.length).toBe(4); expect(unmountSpy).toHaveBeenCalledTimes(1); }); + + test("value iterator correctly updates indices after changing order", async () => { + const user = userEvent.setup(); + type Item = { id: number; text: string }; + const item1: Item = { id: 1, text: "first item" }; + const item2: Item = { id: 2, text: "second item" }; + const item3: Item = { id: 3, text: "third item" }; + const item4: Item = { id: 4, text: "fourth item" }; + const item5: Item = { id: 5, text: "fifth item" }; + let items = [item1, item2, item3, item4, item5]; + function App() { + const itemsState = createState(items); + + return createElement("div", { + children: [ + createElement("button", { + "data-testid": "button", + onClick: () => itemsState.setValue(items), + }), + createElement("div", { + "data-testid": "container", + children: [ + itemsState.useValueIterator( + { key: "id" }, + ({ elementState, indexState }) => + createElement("div", { + children: [ + createElement("div", { + children: indexState.useValue(), + }), + ".", + createElement("div", { + children: elementState.useValueSelector( + (item) => item.text + ), + }), + ], + }) + ), + ], + }), + ], + }); + } + + cleanup = attachComponent({ + htmlElement: document.body, + component: createElement(App), + }); + + const container = screen.getByTestId("container"); + const children = container.childNodes; + expect(children.length).toBe(5); + expect(children[0].textContent).toBe("0.first item"); + expect(children[1].textContent).toBe("1.second item"); + expect(children[2].textContent).toBe("2.third item"); + expect(children[3].textContent).toBe("3.fourth item"); + expect(children[4].textContent).toBe("4.fifth item"); + + items = [item5, item3, item1, item4, item2]; + await user.click(screen.getByTestId("button")); + + expect(children[0].textContent).toBe("0.fifth item"); + expect(children[1].textContent).toBe("1.third item"); + expect(children[2].textContent).toBe("2.first item"); + expect(children[3].textContent).toBe("3.fourth item"); + expect(children[4].textContent).toBe("4.second item"); + }); }); diff --git a/src/hooks/create-state.ts b/src/hooks/create-state.ts index f327ea3..c53d6d2 100644 --- a/src/hooks/create-state.ts +++ b/src/hooks/create-state.ts @@ -60,7 +60,7 @@ export type State = { }, cb: (props: { elementState: State; - index: number; + indexState: State; }) => VelesElement | VelesComponent ): VelesComponent | VelesElement | null; getValue(): ValueType; @@ -73,7 +73,7 @@ export type State = { type TrackingParams = { cb: (props: { elementState: State; - index: number; + indexState: State; }) => VelesElement | VelesComponent; selector?: (value: unknown) => any[]; renderedElements: [VelesElement | VelesComponent, string, State][]; @@ -81,7 +81,8 @@ type TrackingParams = { elementsByKey: { [key: string]: { elementState: State; - index: number; + indexState: State; + indexValue: number; node: VelesElement | VelesComponent; }; }; @@ -360,7 +361,8 @@ function createState( const newElementsByKey: { [key: string]: { elementState: State; - index: number; + indexState: State; + indexValue: number; node: VelesElement | VelesComponent; }; } = {}; @@ -394,7 +396,11 @@ function createState( renderedExistingElements[calculatedKey] = true; const currentValue = existingElement.elementState.getValue(); if (currentValue !== element) { - existingElement.elementState.setValue(() => element); + existingElement.elementState.setValue(element); + } + const currentIndex = existingElement.indexState.getValue(); + if (currentIndex !== index) { + existingElement.indexState.setValue(index); } newRenderedElements.push([ @@ -404,17 +410,20 @@ function createState( ]); newElementsByKey[calculatedKey] = { elementState: existingElement.elementState, - index, + indexState: existingElement.indexState, + indexValue: index, node: existingElement.node, }; } else { const elementState = createState(element); - const node = cb({ elementState, index }); + const indexState = createState(index); + const node = cb({ elementState, indexState }); newRenderedElements.push([node, calculatedKey, elementState]); newElementsByKey[calculatedKey] = { elementState, - index, + indexState, + indexValue: index, node, }; } @@ -460,17 +469,17 @@ function createState( const { velesElementNode: existingElementNode } = getComponentVelesNode(existingElement.node); // the element is in the same relative position - if (existingElement.index + offset === index) { + if (existingElement.indexValue + offset === index) { currentElement = existingElementNode.html; return; } - if (existingElement.index + offset > index) { + if (existingElement.indexValue + offset > index) { if (currentElement) { currentElement.after(existingElementNode.html); // we adjust the offset of the item right after the one // we repositioned - positioningOffset[existingElement.index + 1] = -1; + positioningOffset[existingElement.indexValue + 1] = -1; } else { // this means we at position 0 const firstRenderedElement = renderedElements[0]?.[0]; @@ -488,7 +497,7 @@ function createState( } else { if (currentElement) { currentElement.after(existingElementNode.html); - positioningOffset[existingElement.index + 1] = 1; + positioningOffset[existingElement.indexValue + 1] = 1; } else { // this means we at position 0 const firstRenderedElement = renderedElements[0]?.[0]; @@ -653,7 +662,7 @@ function createState( }, cb: (props: { elementState: State; - index: number; + indexState: State; }) => VelesElement | VelesComponent ) { const children: [ @@ -664,7 +673,8 @@ function createState( const elementsByKey: { [key: string]: { elementState: State; - index: number; + indexState: State; + indexValue: number; node: VelesElement | VelesComponent; }; } = {}; @@ -694,16 +704,18 @@ function createState( } const elementState = createState(element); + const indexState = createState(index); if (!calculatedKey) { return; } - let node = cb({ elementState, index }); + let node = cb({ elementState, indexState }); elementsByKey[calculatedKey] = { node, - index, + indexState, + indexValue: index, elementState, };