Skip to content

Commit

Permalink
add Context support to useValueIterator
Browse files Browse the repository at this point in the history
  • Loading branch information
Bloomca committed Jun 11, 2024
1 parent 30a735a commit 65e76c6
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 2 deletions.
155 changes: 154 additions & 1 deletion integration-tests/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
attachComponent,
createElement,
createState,
createRef,
createContext,
type State,
} from "../src";

describe("Context", () => {
Expand Down Expand Up @@ -82,4 +82,157 @@ describe("Context", () => {
"context value is 5"
);
});

test("conditionally rendered components have access to Context", async () => {
const user = userEvent.setup();
const exampleContext = createContext<number>();

function App() {
const showState = createState(false);
return createElement("div", {
children: [
createElement("button", {
"data-testid": "button",
onClick: () => showState.setValue((value) => !value),
}),
showState.useValue((shouldShow) =>
shouldShow ? createElement(NestedComponent) : null
),
],
});
}

function NestedComponent() {
const exampleValue = exampleContext.readContext();
return createElement("div", {
"data-testid": "container",
children: [`value is ${exampleValue}`],
});
}

cleanup = attachComponent({
htmlElement: document.body,
component: createElement(exampleContext.Provider, {
value: 7,
children: createElement(App),
}),
});

await user.click(screen.getByTestId("button"));

expect(screen.getByTestId("container").textContent).toBe("value is 7");
});

it("newly added elements in useValueIterator have access to Context", async () => {
const user = userEvent.setup();
type Item = { id: number; text: string; value: number };
const item1: Item = { id: 1, text: "first item", value: 1 };
const item2: Item = { id: 2, text: "second item", value: 2 };
const item3: Item = { id: 3, text: "third item", value: 3 };

const exampleContext = createContext<number>();

const itemsState = createState<Item[]>([item1, item2]);
function App() {
return createElement("div", {
children: [
createElement("div", {
"data-testid": "container",
children: itemsState.useValueIterator<Item>(
{ key: "id" },
({ elementState }) => createElement(Item, { elementState })
),
}),
],
});
}

function Item({ elementState }: { elementState: State<Item> }) {
const exampleValue = exampleContext.readContext();

return createElement("div", {
children: [
elementState.useValueSelector((element) => element.text),
" ",
elementState.useValueSelector(
(element) => element.value,
(value) => String(value * exampleValue)
),
],
});
}

cleanup = attachComponent({
htmlElement: document.body,
component: createElement(exampleContext.Provider, {
value: 3,
children: createElement(App),
}),
});

const listElement = screen.getByTestId("container");
expect(listElement.childNodes.length).toBe(2);
expect(listElement.childNodes[0].textContent).toBe("first item 3");
expect(listElement.childNodes[1].textContent).toBe("second item 6");

itemsState.setValue([item1, item2, item3]);
expect(listElement.childNodes.length).toBe(3);
expect(listElement.childNodes[0].textContent).toBe("first item 3");
expect(listElement.childNodes[1].textContent).toBe("second item 6");
expect(listElement.childNodes[2].textContent).toBe("third item 9");
});

it("another Context overrides same value for children correctly", () => {
const exampleContext = createContext<number>();

function App() {
return createElement("div", {
children: [
createElement("h1", { children: "Application" }),
createElement(NestedComponent),
],
});
}

function NestedComponent() {
const exampleValue = exampleContext.readContext();

return createElement("div", {
children: [
createElement("div", {
"data-testid": "contextContent",
children: `context value is ${exampleValue}`,
}),
createElement(exampleContext.Provider, {
value: 6,
children: createElement(DoubleNestedComponent),
}),
],
});
}

function DoubleNestedComponent() {
const exampleValue = exampleContext.readContext();

return createElement("div", {
"data-testid": "doubleContextContent",
children: `context value is ${exampleValue}`,
});
}

cleanup = attachComponent({
htmlElement: document.body,
component: createElement(exampleContext.Provider, {
value: 5,
children: createElement(App),
}),
});

expect(screen.getByTestId("contextContent").textContent).toBe(
"context value is 5"
);
expect(screen.getByTestId("doubleContextContent").textContent).toBe(
"context value is 6"
);
});
});
2 changes: 2 additions & 0 deletions src/create-state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ function createState<T>(
indexState: State<number>;
}) => VelesElement | VelesComponentObject
) {
const currentContext = getCurrentContext();
const trackingParams = {} as TrackingIterator;
trackingParams.savedContext = currentContext

const wrapperComponent = createElement((_props, componentAPI) => {
const children: [
Expand Down
1 change: 1 addition & 0 deletions src/create-state/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export type TrackingIterator = {
};
};
wrapperComponent: VelesElement | VelesComponentObject;
savedContext: ComponentContext;
};

export type StateTrackers = {
Expand Down
2 changes: 1 addition & 1 deletion src/create-state/update-usevalue-selector-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ function updateUseValueSelector<T>({
: newSelectedValue == undefined
? ""
: String(newSelectedValue);
popPublicContext();
const newNode =
!returnednewNode || typeof returnednewNode === "string"
? createTextElement(returnednewNode as string)
: returnednewNode;

const newRenderedNode = renderTree(newNode);
popPublicContext();
newNode.executedVersion = newRenderedNode;

// `executedVersion` is added when we convert it to tree. It doesn't have
Expand Down
4 changes: 4 additions & 0 deletions src/create-state/update-usevalueiterator-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
callUnmountHandlers,
getExecutedComponentVelesNode,
} from "../_utils";
import { addPublicContext, popPublicContext } from "../context";

import type {
ExecutedVelesComponent,
Expand Down Expand Up @@ -33,6 +34,7 @@ function updateUseValueIteratorValue<T>({
elementsByKey,
wrapperComponent,
selector,
savedContext
} = trackingIterator;
if (!wrapperComponent) {
console.error("there is no wrapper component for the iterator");
Expand Down Expand Up @@ -124,13 +126,15 @@ function updateUseValueIteratorValue<T>({
} else {
const elementState = createState(element);
const indexState = createState(index);
addPublicContext(savedContext)
const node = cb({ elementState, indexState });
// this TypeScript conversion should always be correct, because `node` is
// also either a component or an element
const renderedNode = renderTree(node) as
| ExecutedVelesComponent
| ExecutedVelesElement;
node.executedVersion = renderedNode;
popPublicContext()

newRenderedElements.push([node, calculatedKey, elementState]);
newElementsByKey[calculatedKey] = {
Expand Down

0 comments on commit 65e76c6

Please sign in to comment.