React Fluxible Testing (in progress!)
Table of Contents
Chapter 1 - React Fluxible Testing
- Install Fluxible. Create/Run Fluxible app at http://localhost:3000
- Fluxible Stores API
- Dfn
- Store app state
- Handle business logic
- React to data events
- Classes that follow simple interface
- Stores are full decoupled from Fluxible (hence default exports exclude any Store implementation)
- Creation of Flux Stores (using Helper Utilities
BaseStore
andcreateStore
)createStore
- Create a createStore class that extendsBaseStore
(extendable Store class)BaseStore
of dispatchr library is an extendable Store class that extends EventEmitter eventemitter3 (Library dispatchr/addons/BaseStore.js) providing methodsBaseStore.addChangeListener
,BaseStore.removeChangeListener
, andBaseStore.emitChange
- Note: Library fluxible/addons uses classes from Library dispatcher/addons
- Fluxible BaseStore is extendable base class (reduces boilerplate when creating stores)
- Built-in methods
emitChange()
- emits change eventgetContext()
- returns Store ContextaddChangeListener(callback)
- adds change listenerremoveChangeListener(callback)
- removes change listenershouldDehydrate()
- default returns true when change event emitted
- Built-in methods
- Instantiation of Flux Stores
new Store(dispatcher)
- Instantiates Store using constructor fndispatcher
- Parameter is object with access to methodsdispatcher.getContext()
- Retrieve Store Contextdispatcher.getStore(storeClass)
dispatcher.waitFor(storeClass[], callback)
- Store Context has no methods (by default), but may be modified with Plugins
- Dfn
- Fluxible Actions API
- Dfn
- Actions (aka “Action Creators” in Flux) are stateless async fns receiving 3x params
actionContext
- access to Flux methodspayload
- Action payloaddone
-executeAction
waits fordone()
callback fn that signifies Action completed. If Action returns promise thenexecuteAction
waits for Action to resolved/rejected.
- Actions called with either
FluxibleContext.executeAction(myAction, payload, [done])
- OR from Other Actions
- OR from a Component
- Note: Fire-and-forget Enforces that Actions are fire-forget with state changes only handled using Flux flow.
Hence components running
executeAction
cannot return a promise or pass a callback, the callback becomes high level (app level)componentActionErrorHandler
fn provided to Fluxible constructor to allow handling errors at high level that spawn from components firing actions
- Note: Fire-and-forget Enforces that Actions are fire-forget with state changes only handled using Flux flow.
Hence components running
- Action Context (i.e. access of Action to Flux Context)
dispatch(eventName, payload)
- dispatch data event and call Store handlersexecuteAction(action, payload, [done])
- execute action, wait for promise to be resolved/rejected, ordone
callback to be calledgetStore(storeConstructor)
- get Store instance to reading from it onlyrootId
- generated for each root level Action executed and persisted to all subsequent Actions called under itstack
- array of Action names that were called (for debugging)
- Actions (aka “Action Creators” in Flux) are stateless async fns receiving 3x params
- Dfn
- Testing Fluxible Actions API
createMockActionContext
lib is passed Action instance and records methods called by the Action on the contextdispatch
when called pushes an object todispatchCalls
array (each object in array contains keysname
andpayload
)executeAction
when called pushes an object toexecuteActionCalls
array (each object in array contains keyaction
andpayload
)getStore
calls are proxied to dispatcher instance (upon which Stores may be registered when instantiated)
- Fluxible Components API
- Dfn
- Components must access State of app held in Stores
- Components must be able to Execute Actions that Stores react to as a result of events
- Access to React Component Context from Components
ComponentContext
of current request is passed to Components for accessComponentContext
receives limited access to FluxibleContext (prevents avoidance of Flux flow and dispatching directly)ComponentContext
contains methods:executeAction(action, payload, [done])
- execute action, wait for promise to be resolved/rejected, ordone
callback to be called. Note: Fire-and-forget (see earlier comments)getStore(storeConstructor)
- get Store instance to reading from it only
ComponentContext
of current request is passed as a Prop to top-level container Component for access, then either:- Implicitly propagated via React Context to controller views (i.e. implicit handling of propagation
to controller views that have registered its
contextTypes
and useprovideContext
provideContext Helper (wraps a Component with a higher-order component that declares/specifies child context withchildContextTypes
and allows the React contextComponentContext
to propagate to all children that specify theircontextTypes
so the Components have access to listen to Store instances, execute Actions, and access methods added to the ComponentContext by Plugins), and FluxibleComponent (wrapper component that imperatively declares child context) (RECOMMENDED) OR - Manually by passed
ComponentContext
manually to all Child Components as Props
- Implicitly propagated via React Context to controller views (i.e. implicit handling of propagation
to controller views that have registered its
- Accessing Stores from Components
- Components access Store instance state via
this.context.getStore(StoreConstructor)
- Components access Store instance state via
- Accessing Changes to Stores from Components
- Component adds/removes listeners to listen to a Store for state changes (with or without Helpers) so it may re-render
itself using
connectToStores
Helper connectToStores (higher-order component) and store latest state locally in the component
- Component adds/removes listeners to listen to a Store for state changes (with or without Helpers) so it may re-render
itself using
- Executing Actions from Components
this.context.executeAction(action, { payload });
- Dfn
- Testing Fluxible Component API
createMockComponentContext
libdispatch
when called pushes an object todispatchCalls
array (each object in array contains keysname
andpayload
)executeAction
when called pushes an object toexecuteActionCalls
array (each object in array contains keyaction
andpayload
)getStore
calls are proxied to dispatcher instance (upon which Stores may be registered when instantiated)
- Fluxible Fluxible API
- Dfn
- Fluxible instance instantiated once for app (i.e. such as in app.js)
- Holds Settings and Interfaces that are used across Requests (i.e. in server.js)
- Server exposes dehydrated server-side context state (serialised object of Fluxible instance and FluxibleContext) to by used to rehydrate the client-side state with the same state client.js
- Dfn
- Fluxible FluxibleContext API
- Dfn
- FluxibleContext instance instantiated once per Request/Session by calling
createContext(contextOptions)
on the Fluxible instance - FluxibleContext provides server-side isolation of Stores, Dispatches, and other data between requests
- FluxibleContext instance instantiated once per Request/Session by calling
- SubContexts
- SubContexts are subsets of methods from FluxibleContext are provided to each Component of app to prevent them breaking Flux flow.
- SubContexts are provided a
getComponentContext()
getter to access the FluxibleContext - SubContexts (see table at FluxibleContext API link):
- Action Context - passed as first param to all Actions (with access to most Fluxible methods)
- ComponentContext - passed as prop to top-level React Component and then propagated to Child Components requiring access to it
- Store Context - passed as first param to all Store Constructors (no methods/properties by default)
- Plugins
- Plugins allow modification of SubContexts
- SubContext Methods
executeAction(actionContext, payload, [done])
executeAction
is entrypoint to app execution starting the Flux flow of:- Action/Dispatcher - Action dispatches events to Stores
- Stores - Stores updating their Data structures
- Server-side - Wait for initial Action to finish before React rendering
- Client-side - Already immediately render Components and wait for Store change events
- Dfn
Callback example (i.e. in server.js)
Promise example
- Cont’d
- Cont’d
plug(plugin)
- allows custom context settings to be shared between server and client, and allows dynamically plugging ActionContext, ComponentContext, and StoreContext with extra methodsgetActionContext()
- returns ActionContext with access to functions that should only be called from Actions (i.e. by defaultdispatch
,executeAction
,getStore
). Note: ActionContext object is passed as first parameter to the Action each timeexecuteAction
is calledgetComponentContext()
- returns ActionContext with access to functions that should only be called from Components (i.e. by defaultexecuteAction
,getStore
). Note:executeAction
enforces Actions to be send and forget by not allowing a callback to be passed from Components.getStoreContext()
- returns StoreContext with access to only functions that should be called from Stores (empty with no methods by default, but modifiable with Plugins)dehydrate()
(i.e. use withcontext.dehydrate()
) returns serializable object containing state ofFluxibleContext
and itsDispatchr
instance, and calls any plugins whoseplugContext
method returns an object containing adehydrate
methodrehydrate(state)
- takes object representing state ofFluxibleContext
andDispatchr
instances (i.e. usually retrieved from dehydrate) to rehydrate them to same state as on the server, and calls any plugins whoseplugContext
method returns an object containing adehydrate
method
- Cont’d
- Fluxible Plugins API
- Dfn
- Fluxible Plugins allow us to extend the interface of each context type (i.e. provide extra properties to
respective
contextOptions
object)
- Fluxible Plugins allow us to extend the interface of each context type (i.e. provide extra properties to
respective
- Implementation:
- See my Pull Request
- TODO
- Unable to console.log the following in Store as mentioned as being possible at the link (only able to do in Component)
- Dfn
// Retrieve Plugin state value of ‘bar’ from actions, stores or components // console.log(“context.getActionContext().getFoo()”, context.getActionContext().getFoo()); // console.log(“context.getStoreContext().getFoo()”, context.getStoreContext().getFoo()); // console.log(“context.getComponentContext().getFoo()”, context.getComponentContext().getFoo());
Interpreting Errors
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
- Solution: If importing from
export default function About() { return (<div><h2>About</h2></div>); };
into unit test withimport {About} from './About'
then change toimport About from './About'
- Solution: If importing from
Testing DRAFT Templates
TODO
Links
- dispatchr - Flux dispatcher for apps on server/client (i.e. node_modules/dispatchr/addons/BaseStore.js)
- Generator Fluxible @ Fluxible Quickstart
- Fluxible BaseStore
- Fluxible createStore
- Store Context
Other Links
Written on December 24, 2016