Integrating progressively decoupling into your workflow.
Yuriy is a Front-end Developer and Solutions Architect with Myplanet, specializing in Front-end and Drupal development. Originally joining the team remotely from Ukraine as a Creative Lead, Yuriy made the switch to full-time coding—and to Canada—in 2013. He holds his Acquia certifications for Development and Front-end Development.
Erin has 7 years of Drupal experience and excels at Accessible front-end development and custom Drupal modules. She has deployed several Drupal 8 projects, and is passionate about customer service and creating emotional connections with people using technology. She has her ‘Grand Master’ Drupal certification.
{
"nid": [{ "value": 1 }],
"type": [
{
"target_id": "attendee",
"target_type": "node_type",
"target_uuid": "232c8ff1-47f9-4783-9e5c-e747c75ce558"
}
],
"field_bio": [{ "value": "Qui natus et ..." }],
"field_firstname": [{ "value": "Dana" }],
"field_lastname": [{ "value": "Oneil" }],
"field_presenter": [{ "value": true }],
"field_title": [{ "value": "Ms." }]
...
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://local.skeletor.com/schemata/node/attendee?_format=schema_json&_describes=api_json",
"type": "object",
"title": "node:attendee Schema",
"description": "Describes the payload for 'node' entities of the 'attendee' bundle.",
"properties": {
...
}
composer require myplanet/skeletor-scaffold
"repositories": [
...
{
"type": "package",
"package": {
"name": "myplanet/react-redux-skeletor",
"version": "1.0",
"type": "npm-package",
"source": {
"type": "git",
"reference": "presentation",
"url": "https://github.com/myplanet/react-redux-skeletor.git"
},
"require": {
"composer/installers": "^1.2",
"oomphinc/composer-installers-extender": "dev-master"
}
}
}
],
"extra": {
...
"installer-paths": {
...
"docroot/modules/npm-packages/{$name}": ["type:npm-package"]
},
"installer-types": ["npm-package"]
}
"autoload": {
"classmap": [
...
"scripts/skeletor/NpmPackage.php"
]
},
"scripts": {
...
"npm-install": "DrupalSkeletor\\NpmPackage::npmInstall",
"npm-build": "DrupalSkeletor\\NpmPackage::npmBuild",
"npm-test": "DrupalSkeletor\\NpmPackage::npmTest",
"post-install-cmd": [
"DrupalProject\\composer\\ScriptHandler::createRequiredFiles",
"DrupalComposer\\DrupalScaffold\\Plugin::scaffold",
"@composer npm-install",
"@composer npm-build"
],
}
namespace DrupalSkeletor;
use Composer\Script\Event;
use Symfony\Component\Finder\Finder;
use DrupalFinder\DrupalFinder;
/**
* Class NpmPackage.
*
* @package DrupalSkeletor
*/
class NpmPackage {
/**
* NPM Install.
*
* @param \Composer\Script\Event $event
* Event to echo output.
*/
public static function npmInstall(Event $event) {
static::runNpmCommand('install', $event);
}
...
We want to make sure that component behaves as expected when new features have been added to it or model has been changed.
We want to make sure that when the action creator has been called, the right action (or sequence of actions) have been emitted.
We want to make sure that Redux state has been updated accordingly for the particular action.
“Jest is used by Facebook to test all JavaScript code including React applications. One of Jest's philosophies is to provide an integrated "zero-configuration" experience. We observed that when engineers are provided with ready-to-use tools, they end up writing more tests, which in turn results in more stable and healthy code bases.”
Enzyme is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components' output.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class AttendeeCard extends Component {
render() {
let {id, firstName, lastName, dorgId, bio, presenter}
= this.props
return (
<div
className=`attendee ${presenter ? '_presenter' : ''}`>
<h3>{`${firstName} ${lastName}`}</h3>
<p className="-bio">{bio}</p>
</div>
)
}
}
AttendeeCard.propTypes = {
id: PropTypes.number,
name: PropTypes.string,
dorgId: PropTypes.string,
bio: PropTypes.string,
presenter: PropTypes.bool
};
export default AttendeeCard;
import React from 'react';
import {shallow, render, mount} from 'enzyme';
import AttendeeCard from './AttendeeCard'
test('Attendee Card renders', () => {
let data = {
id: 1,
firstName: 'John',
lastName: 'Smith',
bio: 'bio...'
}
const wrapper = render(
<AttendeeCard
{...data}
/>
);
expect(wrapper.find('h3').text())
.toEqual('John Smith');
expect(wrapper.find('.-bio').text())
.toEqual('bio...');
});
export function getData() {
return function (dispatch, getState) {
let endpointId = 'attendee'
let endpoint = apiEndpoints[endpointId]
dispatch(getDataPending())
return axios({
url: endpoint.url,
method: endpoint.method,
timeout: endpoint.timeout
})
.then(res => {
var validate = ajv.compile(attendeeSchema)
var valid = validate(res.data)
if (!valid) {
dispatch(getDataFail('Error fetching the data',
validate.errors))
}
else {
dispatch(getDataSuccess(res.data.attendee))
}
})
.catch(err => {
getDataFail('Error fetching the data')
});
}
}
describe('Attendee actions', () => {
it('creates GET_DATA_SUCCESS', () => {
let endpointId = 'attendee'
let endpoint = apiEndpoints[endpointId]
var mock = new MockAdapter(axios);
const data = mockedObject;
mock.onGet(endpoint.url).reply(200, data);
const expectedActions = [
{ type: 'GET_DATA_PENDING' },
{ type: 'GET_DATA_SUCCESS', data: mockedObject}
]
const store = mockStore({})
return store.dispatch(actions.getData()).then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})
});
const default = {
data: null,
isFetching: false,
isLoaded: false,
error: null
}
export default function attendee(state = default, action) {
switch(action.type) {
case 'GET_DATA_SUCCESS' :
return {
...state,
data: action.data,
isLoaded: true,
isFetching: false
}
case 'GET_DATA_PENDING' :
return {
...state,
isFetching: true
}
...
default:
return state;
}
}
import attendee from '../attendee'
test('Attendee reducer test', () => {
let initState = attendee(undefined, {type: null});
let dataSuccess = {
type: 'GET_DATA_SUCCESS',
data: { test: "test" }
};
let attendeeSuccessUpdate = {
isLoaded: true,
isFetching: false
};
expect(attendee(initState, dataSuccess))
.toEqual({
...initialState,
...attendeeSuccessUpdate,
data: actionDataSuccess.data
});
});
$ npm run test
> react-skeletor@1.0.0 test ~/DrupalCon
> NODE_ENV=testing jest
PASS src/components/AttendeeCard/AttendeeCard.test.js
PASS src/reducers/reducers/tests/attendee.test.js
PASS src/actions/tests/attendeeActions.test.js
Test Suites: 3 passed, 3 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 1.903s
Ran all test suites.
$ npm run test
> react-skeletor@1.0.0 test ~/DrupalCon
> NODE_ENV=testing jest
FAIL src/components/AttendeeCard/AttendeeCard.test.js
● Attendee Card renders name
expect(received).toEqual(expected)
Expected value to equal:
"John Smith"
Received:
"John Smith "
...
PASS src/reducers/reducers/tests/attendee.test.js
PASS src/actions/tests/attendeeActions.test.js
Test Suites: 1 failed, 2 passed, 3 total
Tests: 1 failed, 2 passed, 3 total
...
Ran all test suites.
$ npm run coverage
...
Test Suites: 3 passed, 3 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 1.725s
Ran all test suites.
-----------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------|----------|----------|----------|----------|----------------|
All files | 88.46 | 88.89 | 77.78 | 88.46 | |
src/actions | 81.25 | 50 | 71.43 | 81.25 | |
actionCreators.js | 81.25 | 50 | 71.43 | 81.25 | 32,39,58 |
src/components/AttendeeCard | 100 | 100 | 100 | 100 | |
AttendeeCard.js | 100 | 100 | 100 | 100 | |
src/constants | 100 | 100 | 100 | 100 | |
endpoints.js | 100 | 100 | 100 | 100 | |
src/reducers | 100 | 100 | 100 | 100 | |
attendee.js | 100 | 100 | 100 | 100 | |
-----------------------------|----------|----------|----------|----------|----------------|
JSON Schema is a vocabulary that allows you to annotate and validate JSON documents.
Javascript is not a strongly typed language, and as a developer you need to rely on additional tools which will allow you to develop stable apps with Javascript.
We recommend to use JSON schema validators if JSON is your communication protocol.
There are a lot of implementations for JSON Schema validators in javascript.
{
"id": 1002,
"title": "Ms.",
"firstName": "James",
"lastName": "Noname",
"bio": "lorem ipsum...",
"presenter": false
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "...?_format=schema_json&_describes=api_json",
"title": "node:attendee Schema",
"description": "Describes the payload for 'node' entities of the 'attendee' bundle.",
"type": "object",
"properties": {
"id" : {"type": "integer"},
"title" : {"type": "string"},
"firstName" : {"type": "string"},
"lastName" : {"type": "string"},
"bio" : {"type": "string"},
"presenter" : {"type": "boolean"}
},
"required": ["id", "firstName", "lastName"]
}
export function getAttendee() {
return function (dispatch, getState) {
let endpointId = 'attendee'
let endpoint = apiEndpoints[endpointId] //import { apiEndpoints } from '../constants/endpoints'
dispatch(getDataPending())
axios({
url: endpoint.url,
method: endpoint.method,
timeout: endpoint.timeout
})
.then(res => {
// Validate response data.
var validate = ajv.compile(attendeeSchema) // import Ajv from 'ajv'; let ajv = Ajv({allErrors: true});
var valid = validate(res.data)
if (!valid) {
getDataFail('Error fetching the data', validate.errors)
}
else {
dispatch(getDataSuccess(res.data))
}
.catch(err => {
getDataFail('Error fetching the data')
});
}
}
Locate this session at the DrupalCon Vienna website
https://events.drupal.org/vienna2017/sessions/skeletor-scaffolding-architecture-devops-progressive-decoupling
Take the survey!
https://www.surveymonkey.com/r/drupalconvienna