diff --git a/packages/manatea/__tests__/manatea.ts b/packages/manatea/__tests__/manatea.ts index cfb9c33..ccab182 100644 --- a/packages/manatea/__tests__/manatea.ts +++ b/packages/manatea/__tests__/manatea.ts @@ -71,4 +71,15 @@ describe('Manatea', () => { await cup(NaN); expect(fn).not.toHaveBeenCalled(); }); + + it('should have flavors', async () => { + const cup = orderCup('0' as string, unflavored => parseInt(unflavored, 10)); + const fn = jest.fn(); + cup.on(tea => fn(tea)); + expect(cup()).toBe(0); + + await cup('1'); + expect(cup()).toBe(1); + expect(fn).toHaveBeenCalledWith(1); + }); }); diff --git a/packages/manatea/src/index.ts b/packages/manatea/src/index.ts index 320e303..e9300dd 100644 --- a/packages/manatea/src/index.ts +++ b/packages/manatea/src/index.ts @@ -11,33 +11,44 @@ export type Tea = | Map | Set; -type Handler = (tea: T, context: Context) => void; +type Handler = ( + tea: UnflavoredTea, + context: Context, +) => void; export interface Server { (): boolean; listening: boolean; } -type Order = ((tea: T) => T | Promise) | T; +type Order = + | ((tea: FlavoredTea) => UnflavoredTea | Promise) + | UnflavoredTea; -export interface Cup { - (): T; - (order: Order, context?: Context): Promise; - on: (fn: Handler) => Server; +export interface Cup { + (): FlavoredTea; + ( + order: Order, + context?: Context, + ): Promise; + on: (fn: Handler) => Server; clear: () => void; } -export type Context = WeakSet>; +export type Context = WeakSet>; -export function orderCup( - firstTea: T, - flavoring: (tea: T) => T = t => t, -): Cup { - let handlers = new Set>(); +export function orderCup< + FlavoredTea extends Tea, + UnflavoredTea extends Tea = FlavoredTea +>( + firstTea: UnflavoredTea, + flavoring: (tea: UnflavoredTea) => FlavoredTea = t => t as any, +): Cup { + let handlers = new Set>(); let flavoredTea = flavoring(firstTea); let isPreviousCancelled = { cancelled: false }; - const setTea = (teaRefill: T, context: Context) => { + const setTea = (teaRefill: UnflavoredTea, context: Context) => { const flavoredTeaRefill = flavoring(teaRefill); if ( flavoredTea === flavoredTeaRefill || @@ -58,9 +69,15 @@ export function orderCup( }); }; - function cup(): T; - function cup(order: Order, context?: Context): Promise; - function cup(order?: Order, context: Context = new WeakSet()) { + function cup(): FlavoredTea; + function cup( + order: Order, + context?: Context, + ): Promise; + function cup( + order?: Order, + context: Context = new WeakSet(), + ) { if (arguments.length === 0) { return flavoredTea; } @@ -76,7 +93,7 @@ export function orderCup( }); } - cup.on = (fn: Handler) => { + cup.on = (fn: Handler) => { handlers.add(fn); const server = () => handlers.delete(fn); Object.defineProperty(server, 'listening', { diff --git a/packages/react-manatea/__tests__/useInfuser.ts b/packages/react-manatea/__tests__/useInfuser.ts index 3b96d7b..31e931c 100644 --- a/packages/react-manatea/__tests__/useInfuser.ts +++ b/packages/react-manatea/__tests__/useInfuser.ts @@ -17,6 +17,7 @@ describe('useInfuser', () => { expect(result.current[0]).toBe(1); }); + it('should trigger updates', async () => { const cup = orderCup(0); @@ -30,6 +31,7 @@ describe('useInfuser', () => { expect(cup()).toBe(-1); }); + it('should avoid infinite loops', async () => { const cup = orderCup(0); @@ -44,4 +46,21 @@ describe('useInfuser', () => { expect(cup()).toBe(2); }); + + it('should have flavors', async () => { + const cup = orderCup('0' as string, unflavored => parseInt(unflavored, 10)); + + const { result, waitForNextUpdate } = renderHook(() => useInfuser(cup)); + expect(result.current[0]).toBe(0); + + const fn = jest.fn(); + cup.on(tea => fn(tea)); + + await act(async () => { + await result.current[1]('1'); + }); + + expect(cup()).toBe(1); + expect(fn).toHaveBeenCalledWith(1); + }); }); diff --git a/packages/react-manatea/src/Infuser.ts b/packages/react-manatea/src/Infuser.ts index 7b1ba56..1a40adc 100644 --- a/packages/react-manatea/src/Infuser.ts +++ b/packages/react-manatea/src/Infuser.ts @@ -3,12 +3,18 @@ import { Cup, Tea } from 'manatea'; import { useInfuser } from './useInfuser'; -interface InfuserProps { - cup: Cup; - children: (tea: T) => ReactNode; +interface InfuserProps { + cup: Cup; + children: (tea: FlavoredTea) => ReactNode; } -export const Infuser = ({ cup, children }: InfuserProps) => { - const [tea] = useInfuser(cup); +export const Infuser = < + FlavoredTea extends Tea, + UnflavoredTea extends Tea = FlavoredTea +>({ + cup, + children, +}: InfuserProps) => { + const [tea] = useInfuser(cup); return children(tea); }; diff --git a/packages/react-manatea/src/infuse.ts b/packages/react-manatea/src/infuse.ts index 3411691..502c166 100644 --- a/packages/react-manatea/src/infuse.ts +++ b/packages/react-manatea/src/infuse.ts @@ -3,11 +3,14 @@ import { Cup, Tea } from 'manatea'; import { useInfuser } from './useInfuser'; -export const infuse = (cup: Cup) => ( - component: React.ComponentType, -) => { +export const infuse = < + FlavoredTea extends Tea, + UnflavoredTea extends Tea = FlavoredTea +>( + cup: Cup, +) => (component: React.ComponentType) => { const Consumer = (props: any) => { - const [tea] = useInfuser(cup); + const [tea] = useInfuser(cup); return React.createElement(component, { ...props, diff --git a/packages/react-manatea/src/useInfuser.ts b/packages/react-manatea/src/useInfuser.ts index 7521511..232808a 100644 --- a/packages/react-manatea/src/useInfuser.ts +++ b/packages/react-manatea/src/useInfuser.ts @@ -1,11 +1,16 @@ import * as React from 'react'; import { Cup, Tea, Server, Context } from 'manatea'; -export const useInfuser = (cup: Cup) => { +export const useInfuser = < + FlavoredTea extends Tea, + UnflavoredTea extends Tea = FlavoredTea +>( + cup: Cup, +) => { const [tea, setTea] = React.useState(() => cup()); React.useEffect(() => { - const server: Server = cup.on((tea: T) => setTea(tea)); + const server: Server = cup.on((tea: FlavoredTea) => setTea(tea)); setTea(cup()); return () => { if (server.listening) { @@ -14,5 +19,8 @@ export const useInfuser = (cup: Cup) => { }; }, [cup]); - return [tea, (tea: T, context?: Context) => cup(tea, context)] as const; + return [ + tea, + (tea: UnflavoredTea, context?: Context) => cup(tea, context), + ] as const; };