diff --git a/src/index.tsx b/src/index.tsx index cc0c243..ab87d40 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -24,10 +24,21 @@ type Hooks< // const [Provider, useContextValue] = constate(useValue) // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ type ConstateTuple[]> = [ - React.FC, + HookedProvider, ...Hooks ]; +// const [Provider, useContextValue] = constate(useValue) +// ^^^^^^^^ +type HookedProvider = React.FC> & { + FromValue: ValueProvider; +}; + +// const [Provider, useContextValue] = constate(useValue) +// ... +// ^^^^^^^^^ +type ValueProvider = React.FC>; + const isDev = process.env.NODE_ENV !== "production"; const NO_PROVIDER = {}; @@ -65,8 +76,7 @@ function constate[]>( createContext(useValue.name); } - const Provider: React.FC = ({ children, ...props }) => { - const value = useValue(props as Props); + const Providers: ValueProvider = ({ children, value }) => { let element = children as React.ReactElement; for (let i = 0; i < contexts.length; i += 1) { const context = contexts[i]; @@ -77,6 +87,11 @@ function constate[]>( } return element; }; + const Provider: HookedProvider = ({ children, ...props }) => { + const value = useValue(props as Props); + return Providers({ value, children }); + }; + Provider.FromValue = Providers; if (isDev && useValue.name) { Provider.displayName = "Constate"; diff --git a/test/index.test.tsx b/test/index.test.tsx index 1953620..ce7506b 100644 --- a/test/index.test.tsx +++ b/test/index.test.tsx @@ -175,3 +175,103 @@ test("displayName with named useValue with selectors", () => { ); expect(Provider.displayName).toBe("Constate"); }); + +describe("FromValue", () => { + test("no selectors", () => { + const [CounterProvider, useCounterContext] = constate(useCounter); + const Increment = () => { + const { increment } = useCounterContext(); + return ; + }; + const Decrement = () => { + const { decrement } = useCounterContext(); + return ; + }; + const Count = () => { + const { count } = useCounterContext(); + return
{count}
; + }; + const mockContextValue = { + count: 10, + increment: jest.fn(), + decrement: jest.fn(), + }; + const MockApp = () => ( + + + + + + ); + const { getByText } = render(); + expect(getByText("10")).toBeDefined(); + + fireEvent.click(getByText("Increment")); + expect(getByText("10")).toBeDefined(); + expect(mockContextValue.increment).toHaveBeenCalledTimes(1); + + fireEvent.click(getByText("Decrement")); + expect(getByText("10")).toBeDefined(); + expect(mockContextValue.decrement).toHaveBeenCalledTimes(1); + }); + + test("single selector", () => { + const [CounterProvider, useCount] = constate( + useCounter, + (value) => value.count + ); + const Count = () => { + const count = useCount(); + return
{count}
; + }; + const mockContextValue = { + count: 10, + increment: jest.fn(), + decrement: jest.fn(), + }; + const App = () => ( + + + + ); + const { getByText } = render(); + expect(getByText("10")).toBeDefined(); + }); + + test("two selectors", () => { + const [CounterProvider, useCount, useIncrement] = constate( + useCounter, + (value) => value.count, + (value) => value.increment + ); + const Increment = () => { + const increment = useIncrement(); + return ; + }; + const Count = () => { + const count = useCount(); + return
{count}
; + }; + const mockContextValue = { + count: 10, + increment: jest.fn(), + decrement: jest.fn(), + }; + const MockApp = () => ( + + + + + ); + const { getByText } = render(); + expect(getByText("10")).toBeDefined(); + + fireEvent.click(getByText("Increment")); + expect(getByText("10")).toBeDefined(); + expect(mockContextValue.increment).toHaveBeenCalledTimes(1); + + fireEvent.click(getByText("Increment")); + expect(getByText("10")).toBeDefined(); + expect(mockContextValue.increment).toHaveBeenCalledTimes(2); + }); +});