-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
do not track attributes value until mounted (#58)
- Loading branch information
Showing
7 changed files
with
173 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { screen } from "@testing-library/dom"; | ||
import userEvent from "@testing-library/user-event"; | ||
|
||
import { | ||
attachComponent, | ||
createElement, | ||
createState, | ||
onUnmount, | ||
} from "../../src"; | ||
|
||
import type { State } from "../../src"; | ||
|
||
describe("createState", () => { | ||
let cleanup: Function | undefined; | ||
|
||
afterEach(() => { | ||
cleanup?.(); | ||
cleanup = undefined; | ||
}); | ||
|
||
test("useAttribute does not re-mount the component", async () => { | ||
const user = userEvent.setup(); | ||
const spyFn = jest.fn(); | ||
function StateComponent() { | ||
onUnmount(spyFn); | ||
const valueState = createState(0); | ||
return createElement("div", { | ||
children: [ | ||
createElement("button", { | ||
"data-testvalue": valueState.useAttribute((value) => String(value)), | ||
"data-testid": "button", | ||
onClick: () => { | ||
valueState.setValue((currentValue) => currentValue + 1); | ||
}, | ||
}), | ||
], | ||
}); | ||
} | ||
|
||
cleanup = attachComponent({ | ||
htmlElement: document.body, | ||
component: createElement(StateComponent), | ||
}); | ||
|
||
const btn = screen.getByTestId("button"); | ||
expect(btn).toHaveAttribute("data-testvalue", "0"); | ||
|
||
await user.click(btn); | ||
expect(btn).toHaveAttribute("data-testvalue", "1"); | ||
|
||
await user.click(btn); | ||
expect(btn).toHaveAttribute("data-testvalue", "2"); | ||
expect(spyFn).not.toHaveBeenCalled(); | ||
}); | ||
|
||
test("does not track updates in useAttribute until mounted", async () => { | ||
const user = userEvent.setup(); | ||
const spyFn = jest.fn(); | ||
|
||
const valueState = createState("initialValue"); | ||
function App() { | ||
const showState = createState(false); | ||
const content = createElement("div", { | ||
"data-testid": "attributeTest", | ||
"data-value": valueState.useAttribute((value) => { | ||
spyFn(); | ||
return value; | ||
}), | ||
}); | ||
return createElement("div", { | ||
children: [ | ||
createElement("div", { | ||
children: "whatever", | ||
}), | ||
createElement("button", { | ||
"data-testid": "button", | ||
onClick: () => showState.setValue((currentValue) => !currentValue), | ||
}), | ||
showState.useValue((shouldShow) => (shouldShow ? content : null)), | ||
], | ||
}); | ||
} | ||
|
||
cleanup = attachComponent({ | ||
htmlElement: document.body, | ||
component: createElement(App), | ||
}); | ||
|
||
expect(spyFn).toHaveBeenCalledTimes(1); | ||
valueState.setValue("newValue1"); | ||
expect(spyFn).toHaveBeenCalledTimes(1); | ||
|
||
await user.click(screen.getByTestId("button")); | ||
expect(spyFn).toHaveBeenCalledTimes(2); | ||
expect(screen.getByTestId("attributeTest").getAttribute("data-value")).toBe( | ||
"newValue1" | ||
); | ||
valueState.setValue("newValue2"); | ||
expect(spyFn).toHaveBeenCalledTimes(3); | ||
expect(screen.getByTestId("attributeTest").getAttribute("data-value")).toBe( | ||
"newValue2" | ||
); | ||
|
||
// remove the element again to see that subscriptions are correctly removed | ||
await user.click(screen.getByTestId("button")); | ||
valueState.setValue("newValue3"); | ||
expect(spyFn).toHaveBeenCalledTimes(3); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import type { TrackingAttribute } from "./types"; | ||
|
||
function updateUseAttributeValue<T>({ | ||
element, | ||
value, | ||
}: { | ||
element: TrackingAttribute; | ||
value: T; | ||
}) { | ||
const { cb, htmlElement, attributeName, attributeValue } = element; | ||
const newAttributeValue = cb ? cb(value) : value; | ||
|
||
if (typeof newAttributeValue === "boolean") { | ||
if (newAttributeValue) { | ||
htmlElement.setAttribute(attributeName, ""); | ||
} else { | ||
htmlElement.removeAttribute(attributeName); | ||
} | ||
} else if (attributeName.startsWith("on")) { | ||
// if the value is the same, it is either not set | ||
// or we received the same event handler | ||
// either way, no need to do anything | ||
if (attributeValue === newAttributeValue) { | ||
return; | ||
} | ||
|
||
const eventName = | ||
attributeName[2].toLocaleLowerCase() + attributeName.slice(3); | ||
if (attributeValue) { | ||
htmlElement.removeEventListener(eventName, attributeValue); | ||
} | ||
if (newAttributeValue && typeof newAttributeValue === "function") { | ||
htmlElement.addEventListener(eventName, newAttributeValue); | ||
} | ||
// not the best approach, but it should work as expected | ||
// basically, update the array value in-place | ||
element.attributeValue = newAttributeValue; | ||
} else { | ||
htmlElement.setAttribute(attributeName, newAttributeValue); | ||
} | ||
} | ||
|
||
export { updateUseAttributeValue }; |