Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-habitat

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-habitat - npm Package Compare versions

Comparing version 0.2.0-beta2 to 0.2.0-beta3

60

index.d.ts

@@ -9,9 +9,5 @@ /**

export interface IDomFactory {
export interface IDomFactory {
/**
* An identifier string for the type of components this injects
*/
identifier: () => string;
/**
* The inject method after a wire-up has been requested

@@ -22,6 +18,12 @@ * @param {*} module - The component

*/
inject?: (module: any, props: {}, target: Element) => void;
inject: (module: any, props: {}, target: Element) => void;
/**
* The dispose method
* @param {Element} target - The element to dispose
*/
dispose: (target: Element) => void;
}
export interface IContainer {
export interface IContainer {
/**

@@ -33,2 +35,3 @@ * Register a new component in the container

registerComponent: (name: string, comp: any) => void;
/**

@@ -39,12 +42,17 @@ * Get a registered component for a key

resolve: (name: string) => any;
}
/**
* The container's unique id
*/
id: () => string;
/**
* The containers dom factory
*/
domFactory: () => IDomFactory;
}
export interface IBootstrapper {
/**
* The dom factory
*/
factory: IDomFactory;
/**
* Set the container

@@ -55,2 +63,6 @@ * @param {IContainer} container - The container

/**
* Dispose of the container
*/
dispose: () => void;
}

@@ -62,9 +74,4 @@

export class Bootstrapper implements IBootstrapper {
/**
* The bootstrapping factory
*/
factory: IDomFactory;
/**
* Sets the container

@@ -85,6 +92,5 @@ */

/**
* The container
* @private
* Disposes the container
*/
_container: IContainer;
dispose: () => void;
}

@@ -95,2 +101,7 @@

/**
* The containers unique id
*/
id: () => string;
/**
* Register a component

@@ -104,2 +115,7 @@ */

resolve: (name: string) => any;
/**
* The containers dom factory
*/
domFactory: () => IDomFactory;
}

@@ -106,0 +122,0 @@

module.exports = function(config) {
config.set({
basePath: './',
frameworks: ['jasmine'],
files: ['tests/**/*.spec.js'],
config.set({
basePath: './',
frameworks: ['jasmine'],
files: [
'./node_modules/phantomjs-polyfill-object-assign/object-assign-polyfill.js',
'tests/**/*.spec.js'
],
preprocessors: {
// add webpack as preprocessor
'tests/*.spec.js': ['webpack'],
'tests/**/*.spec.js': ['webpack'],
'tests/mochs/*.jsx': ['webpack']
},
preprocessors: {
// add webpack as preprocessor
'tests/*.spec.js': ['webpack'],
'tests/**/*.spec.js': ['webpack'],
'tests/mochs/*.jsx': ['webpack']
},
webpack: {
// karma watches the test entry points
// (you don't need to specify the entry option)
// webpack watches dependencies
webpack: {
// karma watches the test entry points
// (you don't need to specify the entry option)
// webpack watches dependencies
// webpack configuration
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: ['', '.jsx', '.js', '.es6']
},
// webpack configuration
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: ['', '.jsx', '.js', '.es6']
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
},
phantomjsLauncher: {
// Have phantomjs exit if a ResourceError is encountered (useful if karma exits without killing phantom)
exitOnResourceError: true
},
phantomjsLauncher: {
// Have phantomjs exit if a ResourceError is encountered (useful if karma exits without killing phantom)
exitOnResourceError: true
},
webpackMiddleware: {
// webpack-dev-middleware configuration
// i. e.
stats: 'errors-only'
}
webpackMiddleware: {
// webpack-dev-middleware configuration
// i. e.
stats: 'errors-only'
}
});
});
};
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
value: true
});

@@ -19,6 +19,2 @@

var _ReactDomFactory = require('./factories/ReactDomFactory');
var _ReactDomFactory2 = _interopRequireDefault(_ReactDomFactory);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -32,3 +28,2 @@

* Parses a container and populate components
* @param {object} factory The dom factory
* @param {array} container The container

@@ -39,21 +34,25 @@ * @param {array} elements The elements to parse

*/
function parseContainer(factory, container, elements, componentSelector) {
var cb = arguments.length <= 4 || arguments[4] === undefined ? null : arguments[4];
function parseContainer(container, elements, componentSelector) {
var cb = arguments.length <= 3 || arguments[3] === undefined ? null : arguments[3];
// Iterate over component elements in the dom
for (var i = 0; i < elements.length; ++i) {
var ele = elements[i];
var componentName = ele.getAttribute(componentSelector);
var component = container.resolve(componentName);
if (component) {
factory.inject(component, _Habitat2.default.parseProps(ele), _Habitat2.default.create(ele, factory.identifier()));
} else {
console.warn('Cannot resolve component "' + componentName + '"');
}
}
var factory = container.domFactory();
var id = container.id();
if (typeof cb === 'function') {
cb.call();
}
// Iterate over component elements in the dom
for (var i = 0; i < elements.length; ++i) {
var ele = elements[i];
var componentName = ele.getAttribute(componentSelector);
var component = container.resolve(componentName);
if (component) {
factory.inject(component, _Habitat2.default.parseProps(ele), _Habitat2.default.create(ele, id));
} else {
console.warn('Cannot resolve component "' + componentName + '"');
}
}
if (typeof cb === 'function') {
cb.call();
}
}

@@ -67,43 +66,82 @@

/**
* Constructor
*/
function Bootstrapper() {
_classCallCheck(this, Bootstrapper);
/**
* Constructor
*/
function Bootstrapper() {
_classCallCheck(this, Bootstrapper);
// Sanity check
if (!window || !window && !window.document) {
throw new Error('ReactBootstrapper requires a DOM but cannot see one :(');
}
// Sanity check
if (!window || !window && !window.document) {
throw new Error('ReactBootstrapper requires a DOM but cannot see one :(');
}
// Set dom component selector
this.componentSelector = DEFAULT_HABITAT_SELECTOR;
// Set dom component selector
this.componentSelector = DEFAULT_HABITAT_SELECTOR;
// Set dom factory
this.factory = new _ReactDomFactory2.default();
// Find all the elements in the dom with the component selector attribute
this.elements = window.document.body.querySelectorAll('[' + this.componentSelector + ']');
// Find all the elements in the dom with the component selector attribute
this.elements = window.document.body.querySelectorAll('[' + this.componentSelector + ']');
}
// The container
this._container = null;
}
/**
* Set the container
* @param {object} container - The container
* @param {function} [cb=null] - Optional callback
/**
* Set the container
* @param {object} container - The container
* @param {function} [cb=null] - Optional callback
*/
_createClass(Bootstrapper, [{
key: 'setContainer',
value: function setContainer(container) {
var cb = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
if (this._container !== null) {
throw new Error('A container is already set. Please call dispose() before assigning a new one.');
}
this._container = container;
// Wire up the components from the container
parseContainer(this._container, this.elements, this.componentSelector, cb);
}
/**
* Dispose the container and destroy habitat instances
* @param {function} [cb=null] - Optional callback
*/
}, {
key: 'dispose',
value: function dispose() {
var cb = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
_createClass(Bootstrapper, [{
key: 'setContainer',
value: function setContainer(container) {
var cb = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
// Wire up the components from the container
parseContainer(this.factory, container, this.elements, this.componentSelector, cb);
}
}]);
// get the container's factory
var factory = this._container.domFactory();
return Bootstrapper;
// Look up open habitats for this container in the dom
var habitats = window.document.body.querySelectorAll('[data-habitat="' + this._container.id() + '"]');
// Clean up
for (var i = 0; i < habitats.length; ++i) {
factory.dispose(habitats[i]);
_Habitat2.default.destroy(habitats[i]);
}
// Reset and release container
this._container = null;
// Handle callback
if (typeof cb === 'function') {
cb.call();
}
}
}]);
return Bootstrapper;
}();
exports.default = Bootstrapper;
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
value: true
});

@@ -36,36 +36,36 @@ exports._Mixin = undefined;

var _Mixin = exports._Mixin = function (_Bootstrapper) {
_inherits(_Mixin, _Bootstrapper);
_inherits(_Mixin, _Bootstrapper);
/*
* A Constructor that takes a spec
*/
function _Mixin(spec) {
_classCallCheck(this, _Mixin);
/*
* A Constructor that takes a spec
*/
function _Mixin(spec) {
_classCallCheck(this, _Mixin);
// Check if a container spec was supplied
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(_Mixin).call(this));
// Check if a container spec was supplied
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(_Mixin).call(this));
if (!spec.container) {
console.warn('"Container" property was not supplied');
return _possibleConstructorReturn(_this);
}
if (!spec.container) {
console.warn('"Container" property was not supplied');
return _possibleConstructorReturn(_this);
}
// Create a new container
var container = new _Container2.default();
// Create a new container
var container = new _Container2.default();
// Map the components
for (var i = 0; i < spec.container.length; i++) {
container.registerComponent(spec.container[i].register, spec.container[i].for);
}
// Map the components
for (var i = 0; i < spec.container.length; i++) {
container.registerComponent(spec.container[i].register, spec.container[i].for);
}
// Finally, set the container
_this.setContainer(container);
return _this;
}
// Finally, set the container
_this.setContainer(container);
return _this;
}
return _Mixin;
return _Mixin;
}(_Bootstrapper3.default);
/*
*
* The classic bootstrapper
*/

@@ -75,3 +75,3 @@

function createBootstrapper(spec) {
return new _Mixin(spec);
return new _Mixin(spec);
}
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
value: true
});

@@ -9,73 +9,132 @@

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
* Copyright 2016-present, Deloitte Digital.
* All rights reserved.
*
* This source code is licensed under the BSD-3-Clause license found in the
* LICENSE file in the root directory of this source tree.
*/
var _ReactDomFactory = require('./factories/ReactDomFactory');
var _ReactDomFactory2 = _interopRequireDefault(_ReactDomFactory);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Copyright 2016-present, Deloitte Digital.
* All rights reserved.
*
* This source code is licensed under the BSD-3-Clause license found in the
* LICENSE file in the root directory of this source tree.
* Creates a unique id
* Example 'C22'
* @returns {string}
*/
var assignId = function idFactory() {
var _nextId = 0;
return function _assignId() {
_nextId++;
return 'C' + _nextId;
};
}();
/**
* The Container class
*/
var Container = function () {
/**
* Constructor
*/
function Container() {
var comps = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
/**
* Constructor
*/
function Container() {
_classCallCheck(this, Container);
_classCallCheck(this, Container);
// TODO: need to make these private in a future version (eg use a WeakMap)
this._components = {};
this._id = assignId();
}
if ((typeof comps === 'undefined' ? 'undefined' : _typeof(comps)) !== 'object') {
throw new Error('Unexpected initial container.', comps);
}
/**
* The unique id for this container
* @returns {*}
*/
// TODO: need to make this private (eg use a WeakMap)
this._components = comps;
}
/**
* Register a component in the container
* @param {string} name - A unique component key
* @param {object} comp - The component
*/
_createClass(Container, [{
key: 'id',
value: function id() {
return this._id;
}
/**
* Register a component in the container
* @param {string} name - A unique component key
* @param {object} comp - The component
*/
_createClass(Container, [{
key: 'registerComponent',
value: function registerComponent(name, comp) {
if (typeof name !== 'string') {
throw new Error('Unexpected component key. Expects a string.', name);
}
this._components[name] = comp;
}
}, {
key: 'registerComponent',
value: function registerComponent(name, comp) {
if (typeof name !== 'string') {
throw new Error('Unexpected component key. Expects a string.', name);
}
this._components[name] = comp;
}
/**
* Resolve a component from the container
* @param {string} name - The unique component key
* @returns {object}
*/
/**
* Register multiple components to the container
* @param {object} comps - The components
*/
}, {
key: 'resolve',
value: function resolve(name) {
return this._components[name];
}
}, {
key: 'getComponent',
value: function getComponent(name) {
console.warn('getComponent is deprecated. Please use resolve() instead.');
return this.resolve(name);
}
}]);
}, {
key: 'registerComponents',
value: function registerComponents(comps) {
if ((typeof comps === 'undefined' ? 'undefined' : _typeof(comps)) !== 'object') {
throw new Error('Unexpected components type. Expects type object', comps);
}
return Container;
Object.assign(this._components, comps);
}
/**
* Resolve a component from the container
* @param {string} name - The unique component key
* @returns {object}
*/
}, {
key: 'resolve',
value: function resolve(name) {
return this._components[name];
}
/**
* Gets a component for key
* @param name
* @returns {Object}
* @deprecated
*/
}, {
key: 'getComponent',
value: function getComponent(name) {
console.warn('getComponent is being deprecated. Please update to use "resolve()" instead.');
return this.resolve(name);
}
/**
* Returns the containers dom factory
* @returns {ReactDomFactory}
*/
}, {
key: 'domFactory',
value: function domFactory() {
return _ReactDomFactory2.default;
}
}]);
return Container;
}();
exports.default = Container;
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
value: true
});

@@ -29,34 +29,44 @@

var ReactDomFactory = function () {
function ReactDomFactory() {
_classCallCheck(this, ReactDomFactory);
}
function ReactDomFactory() {
_classCallCheck(this, ReactDomFactory);
}
_createClass(ReactDomFactory, [{
key: 'identifier',
_createClass(ReactDomFactory, null, [{
key: 'inject',
/**
* The factory identifier
* @returns {string}
*/
value: function identifier() {
return 'react';
}
}, {
key: 'inject',
value: function inject(module) {
var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var target = arguments[2];
/**
* Injects a react component
* @param {object} module - The react component
* @param {object} props - Props to initiate component with
* @param {HTMLElement} target - The target element to inject to
*/
value: function inject(module) {
var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var target = arguments[2];
if (target) {
_reactDom2.default.render(_react2.default.createElement(module, props || {}), target);
} else {
console.warn('Target element is null or undefined. Cannot inject component');
}
}
}]);
if (target) {
_reactDom2.default.render(_react2.default.createElement(module, props || {}), target);
} else {
console.warn('Target element is null or undefined. Cannot inject component');
}
}
return ReactDomFactory;
/**
* Disposes a react component
* @param {HTMLElement} target - The target element to dispose
*/
}, {
key: 'dispose',
value: function dispose(target) {
if (target) {
_reactDom2.default.unmountComponentAtNode(target);
}
}
}]);
return ReactDomFactory;
}();
exports.default = ReactDomFactory;
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
value: true
});

@@ -20,6 +20,22 @@

function firstLetterToUpper(input) {
return input[1].toUpperCase();
return input[1].toUpperCase();
}
/**
* The host id
* @type {string}
*/
var HABITAT_HOST_ID = 'data-habitat-host';
/**
* Determine an elements computed display style
* @param {HTMLElement} ele - The element to test
* @returns {string} - Returns 'block' or 'inline'
*/
function getDisplayType(ele) {
var cStyle = ele.currentStyle || window.getComputedStyle(ele, '');
return cStyle.display;
}
/**
* The Habitat provider class

@@ -29,103 +45,153 @@ */

var Habitat = function () {
function Habitat() {
_classCallCheck(this, Habitat);
}
function Habitat() {
_classCallCheck(this, Habitat);
}
_createClass(Habitat, null, [{
key: 'parseProps',
_createClass(Habitat, null, [{
key: 'parseProps',
/**
* Returns a dictionary of properties and values defined on an element
*/
value: function parseProps(ele) {
// Default props with reference to the initiating node
var props = {
proxy: ele };
/**
* Returns a dictionary of properties and values defined on an element
*/
value: function parseProps(ele) {
// Default props with reference to the initiating node
var props = {
proxy: ele };
// Populate custom props from reading any ele attributes that start with 'data-prop-'
for (var i = 0; i < ele.attributes.length; i++) {
var a = ele.attributes[i];
// Populate custom props from reading any ele attributes that start with 'data-prop-'
for (var i = 0; i < ele.attributes.length; i++) {
var a = ele.attributes[i];
if (a.name.indexOf('data-prop-') >= 0) {
// Convert prop name from hyphens to camel case
var name = a.name.replace('data-prop-', '').replace(/-([a-z])/g, firstLetterToUpper);
if (a.name.indexOf('data-prop-') >= 0) {
// Convert prop name from hyphens to camel case
var name = a.name.replace('data-prop-', '').replace(/-([a-z])/g, firstLetterToUpper);
var value = a.value || '';
var value = a.value || '';
// Parse booleans
if (typeof value === 'string' && value.toLocaleLowerCase() === 'false') {
value = false;
}
if (typeof value === 'string' && value.toLocaleLowerCase() === 'true') {
value = true;
}
// Parse booleans
if (typeof value === 'string' && value.toLocaleLowerCase() === 'false') {
value = false;
}
if (typeof value === 'string' && value.toLocaleLowerCase() === 'true') {
value = true;
}
// Parse json strings
if (typeof value === 'string' && value.length > 2 && (value[0] === '{' && value[value.length - 1] === '}' || value[0] === '[' && value[value.length - 1] === ']')) {
value = JSON.parse(value);
}
// Parse json strings
if (typeof value === 'string' && value.length > 2 && (value[0] === '{' && value[value.length - 1] === '}' || value[0] === '[' && value[value.length - 1] === ']')) {
value = JSON.parse(value);
}
props[name] = value;
}
}
props[name] = value;
} else if (a.name === 'data-props') {
Object.assign(props, JSON.parse(a.value));
}
}
return props;
}
return props;
}
/**
* Creates a new habitat in the dom
*/
/**
* Creates a new habitat in the dom
* @param {HTMLElement} ele - The element
* @param {string} id - The container id
* @returns {Element}
*/
}, {
key: 'create',
value: function create(ele, type) {
var tag = void 0;
}, {
key: 'create',
value: function create(ele, id) {
var tag = 'span';
// If tag is a block level element, then replicate it with the portal
switch (ele.tagName) {
case 'span':
tag = 'span';
break;
default:
tag = 'div';
}
// If tag is a block level element, then replicate it with the portal
if (getDisplayType(ele) === 'block') {
tag = 'div';
}
var habitat = window.document.createElement(tag);
var className = ele.getAttribute('data-habitat-class') || null;
var habitat = window.document.createElement(tag);
var className = ele.getAttribute('data-habitat-class') || null;
// Keep references to habitats
habitat.setAttribute('data-habitat', type);
var replaceDisabled = false;
if (ele.getAttribute('data-habitat-no-replace') !== null) {
replaceDisabled = ele.getAttribute('data-habitat-no-replace').toLocaleLowerCase() === 'true';
}
// Set habitat class name if any
if (className) {
habitat.className = '' + className;
}
// Keep references to habitats container id's
habitat.setAttribute('data-habitat', id);
// inject habitat
if (ele === window.document.body) {
document.body.appendChild(habitat);
} else {
ele.parentNode.insertBefore(habitat, ele.nextSibling);
}
// Set habitat class name if any
if (className) {
habitat.className = '' + className;
}
// Determine if we should keep target element in the dom
if (ele.tagName !== 'input' || ele.tagName !== 'textarea') {
// Not an input so assumed we dont need to keep the targe
// element around
ele.parentNode.removeChild(ele);
} else {
// The element is an input, leave it in the
// dom to allow passing data back to the backend again but we can
// hide it
ele.setAttribute('style', 'display:none;');
}
// inject habitat
if (ele === window.document.body) {
document.body.appendChild(habitat);
} else {
ele.parentNode.insertBefore(habitat, ele.nextSibling);
}
return habitat;
}
}]);
// Determine if we should keep host element in the dom
if (ele.tagName !== 'INPUT') {
return Habitat;
// Not an input so assumed we don't need to keep the target
// element around
// Check it is empty first
if (ele.innerHTML !== '') {
throw new Error('React Habitat elements must be empty. ' + 'Any child components should be added inside React.');
}
if (!replaceDisabled) {
// Detach it
var host = ele.parentNode.removeChild(ele);
// But try to keep a reference to the host in-case destroy is ever called
// and we need to reinstate it back to how we found it
try {
habitat[HABITAT_HOST_ID] = host;
} catch (e) {
console.log(e);
}
}
} else if (ele.getAttribute('type') !== 'hidden') {
// The element is an input, leave it in the
// dom to allow passing data back to the backend again
// Set display none however if the input is not a hidden input
// TODO: Investigate what this does to accessibility
ele.setAttribute('style', 'display: none;');
}
return habitat;
}
/**
* Destroys a habitat
* @param ele
*/
}, {
key: 'destroy',
value: function destroy(ele) {
// Attempt to reinstate any host objects
try {
if (typeof ele[HABITAT_HOST_ID] !== 'undefined') {
// Put back any hosts that where removed
ele.parentNode.insertBefore(ele[HABITAT_HOST_ID], ele);
}
} catch (e) {
console.log(e);
}
// Remove the habitat element
ele.parentNode.removeChild(ele);
}
}]);
return Habitat;
}();
exports.default = Habitat;
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
value: true
});

@@ -20,5 +20,5 @@

exports.default = {
Bootstrapper: _Bootstrapper2.default,
Container: _Container2.default,
createBootstrapper: _createBootstrapper.createBootstrapper
Bootstrapper: _Bootstrapper2.default,
Container: _Container2.default,
createBootstrapper: _createBootstrapper.createBootstrapper
};
{
"name": "react-habitat",
"version": "0.2.0-beta2",
"description": "A React DOM Bootstrapper designed to harmonise a hybrid application",
"version": "0.2.0-beta3",
"description": "A React DOM Bootstrapper designed to harmonise a hybrid application",
"main": "./lib/index.js",

@@ -56,7 +56,8 @@ "repository": {

"karma-webpack": "^1.8.0",
"phantomjs-polyfill-object-assign": "0.0.2",
"webpack": "^1.13.0"
},
"dependencies": {
"react": "^15.0.2",
"react-dom": "^15.0.2"
"react": "^15.3.1",
"react-dom": "^15.3.1"
},

@@ -63,0 +64,0 @@ "scripts": {

@@ -10,3 +10,2 @@ /**

import Habitat from './Habitat';
import ReactDomFactory from './factories/ReactDomFactory';

@@ -17,3 +16,2 @@ const DEFAULT_HABITAT_SELECTOR = 'data-component';

* Parses a container and populate components
* @param {object} factory The dom factory
* @param {array} container The container

@@ -24,22 +22,26 @@ * @param {array} elements The elements to parse

*/
function parseContainer(factory, container, elements, componentSelector, cb = null) {
// Iterate over component elements in the dom
for (let i = 0; i < elements.length; ++i) {
const ele = elements[i];
const componentName = ele.getAttribute(componentSelector);
const component = container.resolve(componentName);
function parseContainer(container, elements, componentSelector, cb = null) {
if (component) {
factory.inject(
component,
Habitat.parseProps(ele),
Habitat.create(ele, factory.identifier()));
} else {
console.warn(`Cannot resolve component "${componentName}"`);
}
}
const factory = container.domFactory();
const id = container.id();
if (typeof cb === 'function') {
cb.call();
}
// Iterate over component elements in the dom
for (let i = 0; i < elements.length; ++i) {
const ele = elements[i];
const componentName = ele.getAttribute(componentSelector);
const component = container.resolve(componentName);
if (component) {
factory.inject(
component,
Habitat.parseProps(ele),
Habitat.create(ele, id));
} else {
console.warn(`Cannot resolve component "${componentName}"`);
}
}
if (typeof cb === 'function') {
cb.call();
}
}

@@ -52,37 +54,74 @@

/**
* Constructor
*/
constructor() {
// Sanity check
if (!window || (!window && !window.document)) {
throw new Error('ReactBootstrapper requires a DOM but cannot see one :(');
}
/**
* Constructor
*/
constructor() {
// Sanity check
if (!window || (!window && !window.document)) {
throw new Error('ReactBootstrapper requires a DOM but cannot see one :(');
}
// Set dom component selector
this.componentSelector = DEFAULT_HABITAT_SELECTOR;
// Set dom component selector
this.componentSelector = DEFAULT_HABITAT_SELECTOR;
// Set dom factory
this.factory = new ReactDomFactory();
// Find all the elements in the dom with the component selector attribute
this.elements = window.document.body.querySelectorAll(`[${this.componentSelector}]`);
// Find all the elements in the dom with the component selector attribute
this.elements = window.document.body.querySelectorAll(`[${this.componentSelector}]`);
}
// The container
this._container = null;
}
/**
* Set the container
* @param {object} container - The container
* @param {function} [cb=null] - Optional callback
*/
setContainer(container, cb = null) {
// Wire up the components from the container
parseContainer(
this.factory,
container,
this.elements,
this.componentSelector,
cb
);
}
/**
* Set the container
* @param {object} container - The container
* @param {function} [cb=null] - Optional callback
*/
setContainer(container, cb = null) {
if (this._container !== null) {
throw new Error(
'A container is already set. Please call dispose() before assigning a new one.'
);
}
this._container = container;
// Wire up the components from the container
parseContainer(
this._container,
this.elements,
this.componentSelector,
cb
);
}
/**
* Dispose the container and destroy habitat instances
* @param {function} [cb=null] - Optional callback
*/
dispose(cb = null) {
// get the container's factory
const factory = this._container.domFactory();
// Look up open habitats for this container in the dom
const habitats = window
.document
.body
.querySelectorAll(`[data-habitat="${this._container.id()}"]`);
// Clean up
for (let i = 0; i < habitats.length; ++i) {
factory.dispose(habitats[i]);
Habitat.destroy(habitats[i]);
}
// Reset and release container
this._container = null;
// Handle callback
if (typeof cb === 'function') {
cb.call();
}
}
}

@@ -18,35 +18,35 @@ /**

/*
* A Constructor that takes a spec
*/
constructor(spec) {
super();
/*
* A Constructor that takes a spec
*/
constructor(spec) {
super();
// Check if a container spec was supplied
if (!spec.container) {
console.warn('"Container" property was not supplied');
return;
}
// Check if a container spec was supplied
if (!spec.container) {
console.warn('"Container" property was not supplied');
return;
}
// Create a new container
const container = new Container();
// Create a new container
const container = new Container();
// Map the components
for (let i = 0; i < spec.container.length; i++) {
container.registerComponent(
spec.container[i].register,
spec.container[i].for
);
}
// Map the components
for (let i = 0; i < spec.container.length; i++) {
container.registerComponent(
spec.container[i].register,
spec.container[i].for
);
}
// Finally, set the container
this.setContainer(container);
}
// Finally, set the container
this.setContainer(container);
}
}
/*
*
* The classic bootstrapper
*/
export function createBootstrapper(spec) {
return new _Mixin(spec);
return new _Mixin(spec);
}

@@ -9,3 +9,21 @@ /**

import ReactDomFactory from './factories/ReactDomFactory';
/**
* Creates a unique id
* Example 'C22'
* @returns {string}
*/
const assignId = (function idFactory() {
let _nextId = 0;
return function _assignId() {
_nextId++;
return `C${_nextId}`;
};
}());
/**
* The Container class

@@ -15,40 +33,71 @@ */

/**
* Constructor
*/
constructor(comps = {}) {
/**
* Constructor
*/
constructor() {
// TODO: need to make these private in a future version (eg use a WeakMap)
this._components = {};
this._id = assignId();
}
if (typeof comps !== 'object') {
throw new Error('Unexpected initial container.', comps);
}
/**
* The unique id for this container
* @returns {*}
*/
id() {
return this._id;
}
// TODO: need to make this private (eg use a WeakMap)
this._components = comps;
}
/**
* Register a component in the container
* @param {string} name - A unique component key
* @param {object} comp - The component
*/
registerComponent(name, comp) {
if (typeof name !== 'string') {
throw new Error('Unexpected component key. Expects a string.', name);
}
this._components[name] = comp;
}
/**
* Register a component in the container
* @param {string} name - A unique component key
* @param {object} comp - The component
*/
registerComponent(name, comp) {
if (typeof name !== 'string') {
throw new Error('Unexpected component key. Expects a string.', name);
}
this._components[name] = comp;
}
* Register multiple components to the container
* @param {object} comps - The components
*/
registerComponents(comps) {
if (typeof comps !== 'object') {
throw new Error('Unexpected components type. Expects type object', comps);
}
/**
* Resolve a component from the container
* @param {string} name - The unique component key
* @returns {object}
*/
resolve(name) {
return this._components[name];
}
Object.assign(this._components, comps);
}
getComponent(name) {
console.warn('getComponent is deprecated. Please use resolve() instead.');
return this.resolve(name);
}
/**
* Resolve a component from the container
* @param {string} name - The unique component key
* @returns {object}
*/
resolve(name) {
return this._components[name];
}
/**
* Gets a component for key
* @param name
* @returns {Object}
* @deprecated
*/
getComponent(name) {
console.warn('getComponent is being deprecated. Please update to use "resolve()" instead.');
return this.resolve(name);
}
/**
* Returns the containers dom factory
* @returns {ReactDomFactory}
*/
domFactory() {
return ReactDomFactory;
}
}

@@ -13,20 +13,29 @@ /**

/**
* The factory identifier
* @returns {string}
*/
identifier() {
return 'react';
}
/**
* Injects a react component
* @param {object} module - The react component
* @param {object} props - Props to initiate component with
* @param {HTMLElement} target - The target element to inject to
*/
static inject(module, props = {}, target) {
if (target) {
ReactDOM.render(
React.createElement(module, props || {}),
target
);
} else {
console.warn('Target element is null or undefined. Cannot inject component');
}
}
inject(module, props = {}, target) {
if (target) {
ReactDOM.render(
React.createElement(module, props || {}),
target
);
} else {
console.warn('Target element is null or undefined. Cannot inject component');
}
}
/**
* Disposes a react component
* @param {HTMLElement} target - The target element to dispose
*/
static dispose(target) {
if (target) {
ReactDOM.unmountComponentAtNode(target);
}
}
}

@@ -10,6 +10,22 @@ /**

function firstLetterToUpper(input) {
return input[1].toUpperCase();
return input[1].toUpperCase();
}
/**
* The host id
* @type {string}
*/
const HABITAT_HOST_ID = 'data-habitat-host';
/**
* Determine an elements computed display style
* @param {HTMLElement} ele - The element to test
* @returns {string} - Returns 'block' or 'inline'
*/
function getDisplayType(ele) {
const cStyle = ele.currentStyle || window.getComputedStyle(ele, '');
return cStyle.display;
}
/**
* The Habitat provider class

@@ -22,86 +38,137 @@ */

*/
static parseProps(ele) {
// Default props with reference to the initiating node
const props = {
proxy: ele, // Pass in a reference to the original node
};
static parseProps(ele) {
// Default props with reference to the initiating node
const props = {
proxy: ele, // Pass in a reference to the original node
};
// Populate custom props from reading any ele attributes that start with 'data-prop-'
for (let i = 0; i < ele.attributes.length; i++) {
const a = ele.attributes[i];
// Populate custom props from reading any ele attributes that start with 'data-prop-'
for (let i = 0; i < ele.attributes.length; i++) {
const a = ele.attributes[i];
if (a.name.indexOf('data-prop-') >= 0) {
// Convert prop name from hyphens to camel case
const name = a.name
.replace('data-prop-', '')
.replace(/-([a-z])/g, firstLetterToUpper);
if (a.name.indexOf('data-prop-') >= 0) {
// Convert prop name from hyphens to camel case
const name = a.name
.replace('data-prop-', '')
.replace(/-([a-z])/g, firstLetterToUpper);
let value = a.value || '';
let value = a.value || '';
// Parse booleans
if (typeof value === 'string' && value.toLocaleLowerCase() === 'false') { value = false; }
if (typeof value === 'string' && value.toLocaleLowerCase() === 'true') { value = true; }
// Parse booleans
if (typeof value === 'string' && value.toLocaleLowerCase() === 'false') { value = false; }
if (typeof value === 'string' && value.toLocaleLowerCase() === 'true') { value = true; }
// Parse json strings
if (typeof value === 'string' && value.length > 2 &&
((value[0] === '{' && value[value.length - 1] === '}') ||
(value[0] === '[' && value[value.length - 1] === ']'))) {
value = JSON.parse(value);
}
// Parse json strings
if (typeof value === 'string' && value.length > 2 &&
((value[0] === '{' && value[value.length - 1] === '}') ||
(value[0] === '[' && value[value.length - 1] === ']'))) {
value = JSON.parse(value);
}
props[name] = value;
}
}
props[name] = value;
} else if (a.name === 'data-props') {
Object.assign(props, JSON.parse(a.value));
}
}
return props;
}
return props;
}
/**
* Creates a new habitat in the dom
*/
static create(ele, type) {
let tag;
/**
* Creates a new habitat in the dom
* @param {HTMLElement} ele - The element
* @param {string} id - The container id
* @returns {Element}
*/
static create(ele, id) {
let tag = 'span';
// If tag is a block level element, then replicate it with the portal
switch (ele.tagName) {
case 'span':
tag = 'span';
break;
default:
tag = 'div';
}
// If tag is a block level element, then replicate it with the portal
if (getDisplayType(ele) === 'block') {
tag = 'div';
}
const habitat = window.document.createElement(tag);
const className = ele.getAttribute('data-habitat-class') || null;
const habitat = window.document.createElement(tag);
const className = ele.getAttribute('data-habitat-class') || null;
// Keep references to habitats
habitat.setAttribute('data-habitat', type);
let replaceDisabled = false;
if (ele.getAttribute('data-habitat-no-replace') !== null) {
replaceDisabled = ele
.getAttribute('data-habitat-no-replace')
.toLocaleLowerCase() === 'true';
}
// Set habitat class name if any
if (className) {
habitat.className = `${className}`;
}
// Keep references to habitats container id's
habitat.setAttribute('data-habitat', id);
// inject habitat
if (ele === window.document.body) {
document.body.appendChild(habitat);
} else {
ele.parentNode.insertBefore(habitat, ele.nextSibling);
}
// Set habitat class name if any
if (className) {
habitat.className = `${className}`;
}
// Determine if we should keep target element in the dom
if (ele.tagName !== 'input' || ele.tagName !== 'textarea') {
// Not an input so assumed we dont need to keep the targe
// element around
ele.parentNode.removeChild(ele);
} else {
// The element is an input, leave it in the
// dom to allow passing data back to the backend again but we can
// hide it
ele.setAttribute('style', 'display:none;');
}
// inject habitat
if (ele === window.document.body) {
document.body.appendChild(habitat);
} else {
ele.parentNode.insertBefore(habitat, ele.nextSibling);
}
return habitat;
}
// Determine if we should keep host element in the dom
if (ele.tagName !== 'INPUT') {
// Not an input so assumed we don't need to keep the target
// element around
// Check it is empty first
if (ele.innerHTML !== '') {
throw new Error(
'React Habitat elements must be empty. ' +
'Any child components should be added inside React.'
);
}
if (!replaceDisabled) {
// Detach it
const host = ele.parentNode.removeChild(ele);
// But try to keep a reference to the host in-case destroy is ever called
// and we need to reinstate it back to how we found it
try {
habitat[HABITAT_HOST_ID] = host;
} catch (e) {
console.log(e);
}
}
} else if (ele.getAttribute('type') !== 'hidden') {
// The element is an input, leave it in the
// dom to allow passing data back to the backend again
// Set display none however if the input is not a hidden input
// TODO: Investigate what this does to accessibility
ele.setAttribute('style', 'display: none;');
}
return habitat;
}
/**
* Destroys a habitat
* @param ele
*/
static destroy(ele) {
// Attempt to reinstate any host objects
try {
if (typeof ele[HABITAT_HOST_ID] !== 'undefined') {
// Put back any hosts that where removed
ele.parentNode.insertBefore(ele[HABITAT_HOST_ID], ele);
}
} catch (e) { console.log(e); }
// Remove the habitat element
ele.parentNode.removeChild(ele);
}
}

@@ -6,5 +6,5 @@ import Bootstrapper from './Bootstrapper';

export default {
Bootstrapper,
Container,
createBootstrapper,
Bootstrapper,
Container,
createBootstrapper,
};

@@ -9,6 +9,6 @@ /**

import Container from '../src/Container';
import Bootstrapper from '../src/Bootstrapper';
import MochComponent from './mochs/MochComponent';
import MochComponentTwo from './mochs/MochComponentTwo';
import Container from '../src/Container';
import Bootstrapper from '../src/Bootstrapper';
import MochComponent from './mochs/MochComponent';
import MochComponentTwo from './mochs/MochComponentTwo';

@@ -19,123 +19,174 @@ let node = null;

class App extends Bootstrapper {
constructor(container, cb = null) {
super();
this.setContainer(container, cb);
}
}
class App extends Bootstrapper {
constructor(container, cb = null) {
super();
this.setContainer(container, cb);
}
}
beforeEach(() => {
node = document.createElement('div');
window.document.body.appendChild(node);
});
beforeEach(() => {
node = document.createElement('div');
window.document.body.appendChild(node);
});
afterEach(() => {
window.document.body.removeChild(node);
});
afterEach(() => {
window.document.body.removeChild(node);
});
it('should log unknown component warning', () => {
spyOn(console, 'warn');
it('should log unknown component warning', () => {
spyOn(console, 'warn');
node.innerHTML = '<div data-component="aUnknownComponent"></div>';
const app = new App(new Container());
node.innerHTML = '<div data-component="aUnknownComponent"></div>';
const app = new App(new Container());
expect(app).toBeDefined();
expect(console.warn).toHaveBeenCalled();
});
expect(app).toBeDefined();
expect(console.warn).toHaveBeenCalled();
});
it('should render a component', () => {
node.innerHTML = '<div data-component="IMochComponent"></div>';
it('should render a component', () => {
node.innerHTML = '<div data-component="IMochComponent"></div>';
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
// --------------------------- //
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
// --------------------------- //
const app = new App(container);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const app = new App(container);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
expect(app).toBeDefined();
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
});
expect(app).toBeDefined();
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
});
it('should render a component with callback', () => {
node.innerHTML = '<div data-component="IMochComponent"></div>';
it('should render a component with callback', () => {
node.innerHTML = '<div data-component="IMochComponent"></div>';
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
const mochCallbackHandler = jasmine.createSpy('My Method');
// --------------------------- //
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
const mochCallbackHandler = jasmine.createSpy('My Method');
// --------------------------- //
const app = new App(container, mochCallbackHandler);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const app = new App(container, mochCallbackHandler);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
expect(app).toBeDefined();
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
expect(mochCallbackHandler).toHaveBeenCalledTimes(1);
});
expect(app).toBeDefined();
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
expect(mochCallbackHandler).toHaveBeenCalledTimes(1);
});
it('should render multiple components', () => {
node.innerHTML =
'<div data-component="IMochComponent"></div>' +
'<div data-component="IMochComponent"></div>';
it('should render multiple components', () => {
node.innerHTML =
'<div data-component="IMochComponent"></div>' +
'<div data-component="IMochComponent"></div>';
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
// --------------------------- //
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
// --------------------------- //
const app = new App(container);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const app = new App(container);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
expect(app).toBeDefined();
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(2);
});
expect(app).toBeDefined();
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(2);
});
it('should render two different components', () => {
node.innerHTML =
'<div data-component="IMochComponent"></div>' +
'<div data-component="IMochComponentTwo"></div>';
it('should render two different components', () => {
node.innerHTML =
'<div data-component="IMochComponent"></div>' +
'<div data-component="IMochComponentTwo"></div>';
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
container.registerComponent('IMochComponentTwo', MochComponentTwo);
// --------------------------- //
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
container.registerComponent('IMochComponentTwo', MochComponentTwo);
// --------------------------- //
const app = new App(container);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const component2Lookup = node.innerHTML.match(/\[component MochComponentTwo\]/g);
const app = new App(container);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const component2Lookup = node.innerHTML.match(/\[component MochComponentTwo\]/g);
expect(app).toBeDefined();
expect(app).toBeDefined();
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
expect(component2Lookup).not.toEqual(null);
expect(component2Lookup.length).toEqual(1);
});
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
it('should pass props', () => {
node.innerHTML = '<div data-component="IMochComponent" data-prop-title="test"></div>';
expect(component2Lookup).not.toEqual(null);
expect(component2Lookup.length).toEqual(1);
});
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
// --------------------------- //
it('should pass props', () => {
node.innerHTML = '<div data-component="IMochComponent" data-prop-title="test"></div>';
const app = new App(container);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const propLookup = node.innerHTML.match(/title='test'/g);
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
// --------------------------- //
expect(app).toBeDefined();
expect(componentLookup).not.toEqual(null);
expect(propLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
expect(propLookup.length).toEqual(1);
});
const app = new App(container);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const propLookup = node.innerHTML.match(/title='test'/g);
it('should not allow direct container replacements', () => {
expect(app).toBeDefined();
expect(componentLookup).not.toEqual(null);
expect(propLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
expect(propLookup.length).toEqual(1);
});
const container1 = new Container();
const container2 = new Container();
const app = new App(container1);
function test() {
app.setContainer(container2);
}
expect(test).toThrowError();
});
it('should dispose', () => {
node.innerHTML = '<div data-component="IMochComponent"></div>';
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
container.registerComponent('IMochComponent', MochComponent);
// --------------------------- //
const app = new App(container);
function componentLookup() { return node.innerHTML.match(/\[component MochComponent\]/g); }
expect(app).toBeDefined();
expect(componentLookup()).not.toEqual(null);
expect(componentLookup().length).toEqual(1);
app.dispose();
expect(componentLookup()).toEqual(null);
expect(node.innerHTML).toBe('<div data-component="IMochComponent"></div>');
});
it('should dispose with callback', () => {
// -- MOCH CONTAINER SET UP -- //
const container = new Container();
const mochCallbackHandler = jasmine.createSpy('My Method');
// --------------------------- //
const app = new App(container);
app.dispose(mochCallbackHandler);
expect(app).toBeDefined();
expect(mochCallbackHandler).toHaveBeenCalledTimes(1);
});
});

@@ -14,106 +14,125 @@ /**

describe('Classic Bootstrapper', () => {
let node = null;
let node = null;
beforeEach(() => {
node = document.createElement('div');
window.document.body.appendChild(node);
});
beforeEach(() => {
node = document.createElement('div');
window.document.body.appendChild(node);
});
afterEach(() => {
window.document.body.removeChild(node);
});
afterEach(() => {
window.document.body.removeChild(node);
});
it('should log missing container warning', () => {
spyOn(console, 'warn');
it('should log missing container warning', () => {
spyOn(console, 'warn');
createBootstrapper({});
createBootstrapper({});
expect(console.warn).toHaveBeenCalled();
});
expect(console.warn).toHaveBeenCalled();
});
it('should log unknown component warning', () => {
spyOn(console, 'warn');
it('should log unknown component warning', () => {
spyOn(console, 'warn');
node.innerHTML = '<div data-component="aUnknownComponent"></div>';
node.innerHTML = '<div data-component="aUnknownComponent"></div>';
createBootstrapper({
container: [],
});
createBootstrapper({
container: [],
});
expect(console.warn).toHaveBeenCalled();
});
expect(console.warn).toHaveBeenCalled();
});
it('should render a component', () => {
node.innerHTML = '<div data-component="IMochComponent"></div>';
it('should render a component', () => {
node.innerHTML = '<div data-component="IMochComponent"></div>';
createBootstrapper({
container: [
{ register: 'IMochComponent', for: MochComponent },
],
});
createBootstrapper({
container: [
{ register: 'IMochComponent', for: MochComponent },
],
});
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
});
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
});
it('should render multiple components', () => {
node.innerHTML =
'<div data-component="IMochComponent"></div>' +
'<div data-component="IMochComponent"></div>';
it('should render multiple components', () => {
node.innerHTML =
'<div data-component="IMochComponent"></div>' +
'<div data-component="IMochComponent"></div>';
createBootstrapper({
container: [
{ register: 'IMochComponent', for: MochComponent },
],
});
createBootstrapper({
container: [
{ register: 'IMochComponent', for: MochComponent },
],
});
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(2);
});
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(2);
});
it('should render two different components', () => {
node.innerHTML =
'<div data-component="IMochComponent"></div>' +
'<div data-component="IMochComponentTwo"></div>';
it('should render two different components', () => {
node.innerHTML =
'<div data-component="IMochComponent"></div>' +
'<div data-component="IMochComponentTwo"></div>';
createBootstrapper({
container: [
{ register: 'IMochComponent', for: MochComponent },
{ register: 'IMochComponentTwo', for: MochComponentTwo },
],
});
createBootstrapper({
container: [
{ register: 'IMochComponent', for: MochComponent },
{ register: 'IMochComponentTwo', for: MochComponentTwo },
],
});
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const component2Lookup = node.innerHTML.match(/\[component MochComponentTwo\]/g);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const component2Lookup = node.innerHTML.match(/\[component MochComponentTwo\]/g);
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
expect(componentLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
expect(component2Lookup).not.toEqual(null);
expect(component2Lookup.length).toEqual(1);
});
expect(component2Lookup).not.toEqual(null);
expect(component2Lookup.length).toEqual(1);
});
it('should pass props', () => {
node.innerHTML = '<div data-component="IMochComponent" data-prop-title="test"></div>';
it('should pass props', () => {
node.innerHTML = '<div data-component="IMochComponent" data-prop-title="test"></div>';
createBootstrapper({
container: [
{ register: 'IMochComponent', for: MochComponent },
],
});
createBootstrapper({
container: [
{ register: 'IMochComponent', for: MochComponent },
],
});
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const propLookup = node.innerHTML.match(/title='test'/g);
const componentLookup = node.innerHTML.match(/\[component MochComponent\]/g);
const propLookup = node.innerHTML.match(/title='test'/g);
expect(componentLookup).not.toEqual(null);
expect(propLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
expect(propLookup.length).toEqual(1);
});
expect(componentLookup).not.toEqual(null);
expect(propLookup).not.toEqual(null);
expect(componentLookup.length).toEqual(1);
expect(propLookup.length).toEqual(1);
});
it('should dispose', () => {
node.innerHTML = '<div data-component="IMochComponent" data-prop-title="test"></div>';
const app = createBootstrapper({
container: [
{ register: 'IMochComponent', for: MochComponent },
],
});
function componentLookup() { return node.innerHTML.match(/\[component MochComponent\]/g); }
expect(componentLookup()).not.toEqual(null);
expect(componentLookup().length).toEqual(1);
app.dispose();
expect(componentLookup()).toEqual(null);
});
});

@@ -8,5 +8,5 @@ /**

*/
import Container from '../src/Container';
import MochComponent from './mochs/MochComponent';
import MochComponentTwo from './mochs/MochComponentTwo';
import Container from '../src/Container';
import MochComponent from './mochs/MochComponent';
import MochComponentTwo from './mochs/MochComponentTwo';

@@ -16,44 +16,63 @@ let container = null;

describe('Container', () => {
beforeEach(() => {
container = new Container();
});
beforeEach(() => {
container = new Container();
});
it('does construct', () => {
expect(container).toBeDefined();
});
it('does construct', () => {
expect(container).toBeDefined();
});
it('does register components', () => {
container.registerComponent('aComponent', MochComponent);
it('does register components', () => {
container.registerComponent('aComponent', MochComponent);
expect(container).toBeDefined();
expect(container.resolve('aComponent')).toBe(MochComponent);
});
expect(container).toBeDefined();
expect(container.resolve('aComponent')).toBe(MochComponent);
});
it('does register initial components', () => {
it('does register multiple components', () => {
const testContainer = new Container({
aComponent: MochComponent,
});
const testContainer = new Container();
testContainer.registerComponents({
aComponent: MochComponent,
anotherComponent: MochComponentTwo,
});
expect(testContainer).toBeDefined();
expect(testContainer.resolve('aComponent')).toBe(MochComponent);
});
expect(testContainer).toBeDefined();
expect(testContainer.resolve('aComponent')).toBe(MochComponent);
expect(testContainer.resolve('anotherComponent')).toBe(MochComponentTwo);
});
it('does override registered components', () => {
container.registerComponent('aComponent', MochComponent);
container.registerComponent('aComponent', MochComponentTwo);
it('does override registered components', () => {
container.registerComponent('aComponent', MochComponent);
container.registerComponent('aComponent', MochComponentTwo);
expect(container).toBeDefined();
expect(container.resolve('aComponent')).toBe(MochComponentTwo);
});
expect(container).toBeDefined();
expect(container.resolve('aComponent')).toBe(MochComponentTwo);
});
it('does resolve distinct components', () => {
container.registerComponent('aComponent', MochComponent);
container.registerComponent('aComponent2', MochComponentTwo);
it('does resolve distinct components', () => {
container.registerComponent('aComponent', MochComponent);
container.registerComponent('aComponent2', MochComponentTwo);
expect(container.resolve('aComponent')).toBe(MochComponent);
expect(container.resolve('aComponent2')).toBe(MochComponentTwo);
});
expect(container.resolve('aComponent')).toBe(MochComponent);
expect(container.resolve('aComponent2')).toBe(MochComponentTwo);
});
it('does return valid id', () => {
expect(container.id()).toBeDefined();
expect(typeof container.id()).toBe('string');
expect(container.id()).not.toBe('');
});
it('does self assign unique id', () => {
const containerB = new Container();
expect(container.id()).not.toBe(containerB.id());
});
it('does return dom factory', () => {
expect(container.domFactory()).toBeDefined();
});
});

@@ -14,66 +14,166 @@ /**

describe('Habitat parse', () => {
beforeEach(() => {
node = document.createElement('div');
window.document.body.appendChild(node);
});
beforeEach(() => {
node = document.createElement('div');
window.document.body.appendChild(node);
});
afterEach(() => {
window.document.body.removeChild(node);
});
afterEach(() => {
window.document.body.removeChild(node);
});
it('parse simple props', () => {
node.setAttribute('data-prop-name', 'John Citizen');
it('parse simple props', () => {
node.setAttribute('data-prop-name', 'John Citizen');
const results = Habitat.parseProps(node);
const results = Habitat.parseProps(node);
expect(results.name).toEqual('John Citizen');
});
expect(results.name).toEqual('John Citizen');
});
it('converts hyphen props to camel case', () => {
node.setAttribute('data-prop-first-name', 'John');
node.setAttribute('data-prop-first-name-initial', 'J');
it('converts hyphen props to camel case', () => {
node.setAttribute('data-prop-first-name', 'John');
node.setAttribute('data-prop-first-name-initial', 'J');
const results = Habitat.parseProps(node);
const results = Habitat.parseProps(node);
expect(results.firstName).toEqual('John');
expect(results.firstNameInitial).toEqual('J');
});
expect(results.firstName).toEqual('John');
expect(results.firstNameInitial).toEqual('J');
});
it('parses booleans', () => {
node.setAttribute('data-prop-is-active', 'true');
node.setAttribute('data-prop-is-active2', 'True');
node.setAttribute('data-prop-is-active3', 'TruE');
node.setAttribute('data-prop-is-active4', 'TRUE');
it('parses booleans', () => {
node.setAttribute('data-prop-is-active', 'true');
node.setAttribute('data-prop-is-active2', 'True');
node.setAttribute('data-prop-is-active3', 'TruE');
node.setAttribute('data-prop-is-active4', 'TRUE');
node.setAttribute('data-prop-is-active5', 'false');
node.setAttribute('data-prop-is-active6', 'False');
node.setAttribute('data-prop-is-active7', 'FalsE');
node.setAttribute('data-prop-is-active8', 'FALSE');
node.setAttribute('data-prop-is-active5', 'false');
node.setAttribute('data-prop-is-active6', 'False');
node.setAttribute('data-prop-is-active7', 'FalsE');
node.setAttribute('data-prop-is-active8', 'FALSE');
const results = Habitat.parseProps(node);
const results = Habitat.parseProps(node);
expect(results.isActive).toEqual(true);
expect(results.isActive2).toEqual(true);
expect(results.isActive3).toEqual(true);
expect(results.isActive4).toEqual(true);
expect(results.isActive5).toEqual(false);
expect(results.isActive6).toEqual(false);
expect(results.isActive7).toEqual(false);
expect(results.isActive8).toEqual(false);
});
expect(results.isActive).toEqual(true);
expect(results.isActive2).toEqual(true);
expect(results.isActive3).toEqual(true);
expect(results.isActive4).toEqual(true);
expect(results.isActive5).toEqual(false);
expect(results.isActive6).toEqual(false);
expect(results.isActive7).toEqual(false);
expect(results.isActive8).toEqual(false);
});
it('parses json', () => {
node.setAttribute('data-prop-user', '{"name": "John Citizen", "isActive": true, "age": 22}');
it('parses json', () => {
node.setAttribute('data-prop-user',
'{"name": "John Citizen", "isActive": true, "age": 22}');
const results = Habitat.parseProps(node);
const results = Habitat.parseProps(node);
expect(results.user).toBeDefined();
expect(results.user.name).toEqual('John Citizen');
expect(results.user.isActive).toEqual(true);
expect(results.user.age).toEqual(22);
});
expect(results.user).toBeDefined();
expect(results.user.name).toEqual('John Citizen');
expect(results.user.isActive).toEqual(true);
expect(results.user.age).toEqual(22);
});
it('parses json props', () => {
node.setAttribute('data-props',
'{"user": {"name": "John Citizen", "isActive": true, "age": 22}}');
const results = Habitat.parseProps(node);
expect(results.user).toBeDefined();
expect(results.user.name).toEqual('John Citizen');
expect(results.user.isActive).toEqual(true);
expect(results.user.age).toEqual(22);
});
});
describe('Habitat create', () => {
beforeEach(() => {
node = document.createElement('div');
window.document.body.appendChild(node);
});
afterEach(() => {
window.document.body.removeChild(node);
});
it('should create a habitat with identifier', () => {
const testElement = window.document.createElement('div');
node.appendChild(testElement);
Habitat.create(testElement, 'C01');
const habitatLookup = node.querySelectorAll('[data-habitat="C01"]');
expect(habitatLookup.length).toEqual(1);
});
it('should throw non empty target error', () => {
const testElement = window.document.createElement('div');
testElement.innerHTML = '<p>test</p>';
node.appendChild(testElement);
function test() { Habitat.create(testElement, 'C01'); }
expect(test).toThrowError();
});
it('should leave inputs in the dom', () => {
const testElement = window.document.createElement('input');
testElement.setAttribute('type', 'text');
testElement.setAttribute('id', 'inpTest');
node.appendChild(testElement);
Habitat.create(testElement, 'C01');
const habitatLookup = window.document.getElementById('inpTest');
expect(habitatLookup).toEqual(testElement);
});
it('should leave elements in the dom with no-replace flag set to true', () => {
const testElement = window.document.createElement('div');
testElement.setAttribute('data-habitat-no-replace', 'true');
testElement.setAttribute('id', 'eleTest');
node.appendChild(testElement);
Habitat.create(testElement, 'C01');
const habitatLookup = window.document.getElementById('eleTest');
expect(habitatLookup).toEqual(testElement);
});
it('should remove elements from the dom with no-replace flag set to false', () => {
const testElement = window.document.createElement('div');
testElement.setAttribute('data-habitat-no-replace', 'false');
testElement.setAttribute('id', 'eleTest');
node.appendChild(testElement);
Habitat.create(testElement, 'C01');
const habitatLookup = window.document.getElementById('eleTest');
expect(habitatLookup).toEqual(null);
});
});

@@ -15,13 +15,13 @@ /**

describe('Habitat API', () => {
it('resolves a bootstrapper', () => {
expect(ReactHabitat.Bootstrapper).toEqual(Bootstrapper);
});
it('resolves a bootstrapper', () => {
expect(ReactHabitat.Bootstrapper).toEqual(Bootstrapper);
});
it('resolves a container', () => {
expect(ReactHabitat.Container).toEqual(Container);
});
it('resolves a container', () => {
expect(ReactHabitat.Container).toEqual(Container);
});
it('resolves a classic mixin', () => {
expect(ReactHabitat.createBootstrapper).toEqual(createBootstrapper);
});
it('resolves a classic mixin', () => {
expect(ReactHabitat.createBootstrapper).toEqual(createBootstrapper);
});
});

@@ -14,19 +14,19 @@ /**

innerText() {
if (this.props.title !== null) {
return `[component MochComponent](title='${this.props.title}')`;
}
innerText() {
if (this.props.title !== null) {
return `[component MochComponent](title='${this.props.title}')`;
}
return '[component MochComponent]';
}
return '[component MochComponent]';
}
render() {
return <div>{this.innerText()}</div>;
}
render() {
return <div>{this.innerText()}</div>;
}
}
MochComponent.propTypes = {
title: React.PropTypes.string,
title: React.PropTypes.string,
};
export default MochComponent;

@@ -14,13 +14,13 @@ /**

innerText() {
if (this.props.title !== null) {
return `[component MochComponentTwo](title='${this.props.title}')`;
}
innerText() {
if (this.props.title !== null) {
return `[component MochComponentTwo](title='${this.props.title}')`;
}
return '[component MochComponentTwo]';
}
return '[component MochComponentTwo]';
}
render() {
return <div>{this.innerText()}</div>;
}
render() {
return <div>{this.innerText()}</div>;
}

@@ -30,5 +30,5 @@ }

MochComponentTwo.propTypes = {
title: React.PropTypes.string,
title: React.PropTypes.string,
};
export default MochComponentTwo;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc