ku4es-react

5.2.1 • Public • Published

ku4es-react

kodmunki™ Utilities for React is an opinionated template for FE SPAs. It includes transpilation, hot dev, asset packing, unit testing, functional testing, Selenium end-to-end web testing, and source code that defines a modified MVC pattern to enforce unidirectional data flow and centralized state management.

Table Of Contents

Prerequisites

This project assumes a general knowledge of OO and Functional programming concepts and constructs. It also assumes you are running a *NIX dev box or have set up your Windows machine, and that the developer machine is set up per the recommendations that follow:

Start A New Project

To start a new project, you will need to fork the latest codebase from this repository, clone it to your box, and then install all of the necessary dependencies via npm.

Fork Project

Navigate to this project root here on GitHub. In the top right corner of the page, click "Fork."

Clone Project

Navigate to the deisired location on your box where you would like your local version of the newly Forked project to live. Then, run the following in your terminal.

git clone <URI>
 

*Note: the above script may require that you have proper credentials depending upon how you choose to connect to GitHub. SSH credential creation and management are outside of the scope of this document.

Install

nvm use v6 && \
npm i && \
npm link ku4es-kernel && \
npm run check && \
npm run compile
 

Overview

Classes

There are seven main constructs that comprise this project pattern:

Model

Though it is expected that the model will contain all of any specific project's domain models, business logic, decision support, services, operations, and capabilities, this project ships with the very basics of a project model.

Dispatcher

There should always be one and only one Dispatcher in any system built from this project. The dispatcher can be found at ./src/model/Dispatcher.js. This class is merely a wrapper on a Flux Dispatcher class intended to protect Taylor systems from risk exposure to necessary future system updates due to Flux Dispatcher API changes out of our control. The current API is 1:1 with the Flux Dispatcher. Therefore, it can be replaced with a Flux Dispatcher itself. This is discouraged due to the increased potential and unnecessary risk profile already discussed.

The Dispatcher is merely the Subject of an Observer pattern that exposes a register method to enable interested Observers to register for notifications. Any class that implements the register/unregister API will work in place of this class. The Flux Dispatcher is currently chosen to leverage any optimization internals provided.

Service

Services are responsible for exposing model internals to external dependents. Service responsibilities will include: server communications, cross-component communications, and system decisioning. Depending upon the system, they may also be responsible for overseeing and supporting the collaboration of component sub-systems and domain defining capabilities and operations. Note that the single responsibility of the Service is managing the collaboration of these components. Any domain operations exceeding the scope of collaboration should be delegated to other well defined system constructs.

All system Services should inherit from ./src/model/services/Service.js. Child classes will inherit the asynchronous server communication defined. Both the get and post methods return Bluebird Promises to their callers. All methods outside of the root level server communications should be defined in system defined subclasses.

Store

Stores are responsible for storing and managing system state. They will be notified of system activity via the Dispatcher. Stores will register for notifications from the Dispatcher. Stores extend EventEmitter and can be registered to by interested classes, namely, Views. Stores will have an opportunity to handle incoming data from Dispatcher notifications and update it's internal state accordingly before finally calling it's protected $notifyStateChanged() method to notify all of it's subscribing Views.

Using this project you can follow a multi-store or single-store style pattern. For those wishing to implement a multi-store solution i.e Flux, you will implement as many stores as you desire inheriting from ./src/model/store/AbstractStore for each store implemented. For those wishing to implement a single-store solution, i.e. Redux, you can simply leverage the ./src/model/store/Store class which merely subscribes to the Dispatcher.

View

Views are responsible for managing and displaying human readable screens that act as the Users Interface. They contain the markup (JSX) necessary to coordinate the structure, symantics, styling, and behavior of the User Interface. They should delegate all server communications and state management to Services and Stores respectively. Though, they could be responsible for necessary User input data collection. It is recommended that this responsibility be delegated to Controllers.

AbstractView And React Component Lifecycle

There are a number of methods that define the "React Component Lifecycle". These methods are abstracted away in the AbstractView of a react-application in effort to reveal the intentions of these methods in the "lifecycle" of a react-application. Any view that extends AbstractView will have access to the protected methods: $mount, $unmount, and $preRender. These methods should be used as follows:

  • $mount: Implement this method in a child class to register to any dispatchers or other object that extend EventEmitter. These registrations should unregistered in $unmount to prevent any memory leaks.

  • $unmount: Implement this method in a child class to unregister from any dispatchers or other object that extend EventEmitter. These unregistrations should unregister any registrations that occur in $mount to prevent any memory leaks.

  • $preRender: This method will be called before any render. This will allow you to update the Component state via setState() with any new prop data received from a parent class or dependent class before this component is rendered.

Controller

Controllers are not a mandatory system construct, and for small systems it may be advised that all of a Controllers responsibilities be relegated to a View. For large or enterprise scale systems it is advised that Controllers be leveraged to separate the responsibilities of User data collection to be owned by Controllers from User data display to be owned by Views.

Controllers collect User input data, modify it as necessary, and send it to a Service. A common example may be a View displaying a button on a form: when the button is invoked, the corresponding Controller will be called, the appropriate form data read, formatted, and then sent to the appropriate Service for processing.

Application State

The Application State is the root level application dependency injection mechanism and application cache found at ./ApplicationState. The system Dispatcher, Stores, and Controllers should all be instantiated here. The Dispatcher will be injected into the dependent Services and Stores, and the Services will be injected into the dependent Controllers in the Application State. The Application State exposes the Stores and Controllers to the View classes via extending ./src/views/AbstractView

Dependencies

This project defines an opinionated class dependency hierarchy.

Dispatcher  ←  Store
    ↑            ↑
 Service    ←  View    
 

Data Flow

This project enforces a unidirectional dataflow. A User is presented data in a View, the user's actions and inputs are handled by a Services that communicates with the server, operates on the domain model, and updates state. The Services leverage a Dispatcher to notify Stores of communication and model results. Stores update their state per the data that they receive, and notify registered Views of their updated state.

 View   →    Service  ⇄  [Server]
   ⇡            ⇣
 Store  ⇠  <Dispatcher>
 

Routing

Due to the nature of Single Page Applications, it is the responsibility of the FE application to manage routing. In this project routing is managed in the main entry point ./src/main.jsx leveraging React Router for routing.

Conventions and Patterns

Conventions

A few conventions to be aware of and adhere to, to aid in reasoning about systems that are modeled in an OO methodology where the language (JavaScript) does not natively support expected OO constructs.

Access Modifiers

Because JavaScript exposes all methods and properties implicitly as "Public," we leverage some conventions to express our member accessibility intentions:

  • $: Prepending member names with a "$" indicate a "Protected" member. Protected members should be accessed only by the defining class and inheriting classes.

  • _: Prepending member names with a "_" indicate a "Private" member. Private members should be accessed only by the defining class.

Patterns

Idioms

Development

This project promotes modular and test driven development workflow. Although this project does support hot load development by default, it is encouraged that developers leverage the built-in test driven workflow to maximize code quality and stability, simplify feature extention and maintenance, and ensure that features implemented correspond directly with feature requirements driven by product owners.

Before beginning development of a feature(s) it is important for a developer to fully understand the requirements and how those requirements can be defined by a set of tests. To ensure maximal code stability and quality it is suggested to not begin development on a feature(s) whose requirements cannot be so defined.

This section will assume a given arbitrary feature or set of features that can be defined by a cohesive test plan and are, therefore, ready for development.

Tests

All tests for a project are found in the ./test directory. This directory splits tests into Unit Tests for coverage of all system units, and End-To-End Tests to cover the application as a whole and the defined workflow scenarios as they run in a given browser.

Unit Tests

Unit tests are intended to cover individual units of functionality at an arbitrarily minimal aggregate. A common and natural arbitrary aggregate is a class. It is likely that a given project will have one test file per class file. For example, a class ./src/model/operation/ExampleClass.js will have a corresponding Unit Test ./test/ut/model/operation/ExampleClass.test.js. It is the intention of the unit test to confirm expected outcomes for all expected scenarios.

Unit Test Development

This project leverages Mocha for Unit Testing, and expands on it by providing helper modules, where possible, to contain common testing constructs. These helper constructs assume adherence to the APIs and patterns defined in this project.

AbstractNotificationStub

The ./test/stubs/AbstractNotificationStub exposes a common Observer Pattern API found in this project. It encapsulates the shared API between DispatcherStub and ServiceTest

DispatcherStub

The /test/stubs/DispatcherStub enables developers to hook into the dispatching mechanism and collect data from internal and "inaccessible" class calls to dispatch. This allows the test to assert data through those constructs that depend upon a Dispatcher. Example: ./test/ut/model/services/ExampleService.test.

ServiceTest

The ./test/ut/model/services/ServiceTest encapsulates an asynchronous server call mock that can be leveraged to make assertions about expected data returned to the target service from a server. All service tests that extend ServiceTest receive this functionality. Example: ./test/ut/model/service/ExampleService.test.

ViewTest

The ./test/ut/views/ViewTest encapsulates behaviors necessary for testing a React Component and exposes a common JQuery-style means of querying the test DOM to simplify test assertions. The common test flow for a view test will be to first test the rendering of the component, then to test all expected state changes under all relevant scenarios. Example: ./test/ut/views/ViewTest.

Running Unit Tests
Run All Unit Tests
npm test
 
Run One Unit Test File
npm run test-file -- <PATH>
 
Run A Single Test
  1. Navigate to the test that you wish to isolate, and append it with "only": it.only('TEST NAME', () => { /*assertions*/ }
  2. Run All Unit Tests or Run One Unit Test File.

WARNING: Do not forget to remove .only from your isolated test once you are finished isolating said test, or your test coverage will fail!

Test Coverage Overview
npm run test-coverage
 
Test Coverage Details
npm run test-coverage-details
 

*Note that currently test-coverage-details is set up to run in a Chrome browser on a Mac, and assumes default Chrome installation.

End-To-End Tests

End-to-end tests are driven by Selenium. All scenarios for the target application should be defined in ./test/e2e/scenarios/example/. Due to known race conditions there are no helper modules for end-to-end testing. Instead, there is a template at ./test/e2e/scenarios/Scenario.temp.js. This template should be used for all scenario testing. It will set up the necessary drivers. Example: ./test/e2e/scenarios/example/ExampleScenario.

*Note: that the Selenium Web Driver pattern is a modified Promise pattern. There are some deviations from the Promises/A+ definition discovered during development. This is a nice post that discusses some of these quirks.

Run All End-To-End Tests

You can run all of the end-to-end tests in a project by runnning the following npm command followed by that target browser.

npm run test-e2e -- [chrome|firefox|safari]
 
Troubleshooting
Cannot Find My Element

Selenium can fail such that the target browser actually opens but cannot connect to your server. This can cause valid markup to be displayed in the target browser, but that markup is error content. It is very difficult to distinguish that this is the case. You can run the following to see this markup to discover if this is your issue.

driver.navigate().to('<URL>')
  .then(() => { return driver.findElement(By.tagName('div')) })
  .then((element) => { return element.getAttribute('innerHTML') })
  .then((html) => { console.log(html) });
 

Source Code

The project source code is found in ./src. As the name would suggest, all project source code should be located in this directory and its subdirectories. It is the code in this directory that will be compiled into a deployable release unit artifact.

Directories

The source code should be organized into a logical directory hierarchy. A common and reasonable hierarchy follows. This project assumes this organization:

src
+-- controllers
+-- model
|   +-- capabilities
|   +-- decisionSupport
|   +-- operation
|   +-- services
|   +-- stores
|
+-- styles
|   +-- base
|   +-- views
|
+-- views
 

Running Locally

The project can be run locally for development by running:

npm start
 

This will start a local server running at http://localhost:3001. Note that this server is set up for hot-reloads. This means that you can leave this server running and as you make changes to your project and save them, the browser will automatically reload to display those changes.

Deployment

A deployment artifact can be generated by compiling the application source using the following command. The deployment artifact is built and saved to ./dist

npm run compile
 

Browser Support

This project is intended to build FE applications that support the following browsers:

Browser Version
Chrome Latest
Firefox Latest
Safari Latest
Edge Latest
IE 11+

Resources

Readme

Keywords

none

Package Sidebar

Install

npm i ku4es-react

Weekly Downloads

5

Version

5.2.1

License

MIT

Unpacked Size

648 kB

Total Files

76

Last publish

Collaborators

  • kodmunki