Merge pull request #65 from weaveworks/fix-topology-click

Fix grouping bar for topologies that don't support grouping
This commit is contained in:
Tom Wilkie
2015-05-22 17:55:15 +01:00
21 changed files with 283 additions and 238 deletions

View File

@@ -35,7 +35,12 @@ client/dist/scripts/bundle.js: client/app/scripts/*
mkdir -p client/dist
docker run -ti -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/dist:/home/weave/dist \
$(SCOPE_UI_BUILD_IMAGE)
$(SCOPE_UI_BUILD_IMAGE) gulp build
client-test: client/test/*
docker run -ti -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/test:/home/weave/test \
$(SCOPE_UI_BUILD_IMAGE) npm test
$(SCOPE_UI_BUILD_EXPORT): client/Dockerfile client/gulpfile.js client/package.json
docker build -t $(SCOPE_UI_BUILD_IMAGE) client

View File

@@ -37,6 +37,7 @@ test:
- cd $SRCDIR; make static
- cd $SRCDIR; make
- cd $SRCDIR; ./bin/test
- cd $SRCDIR; make client-test
post:
- goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/profile.cov -service=circleci || true
- cd $SRCDIR; cp coverage.html $CIRCLE_ARTIFACTS

View File

@@ -1,7 +1,12 @@
FROM mhart/alpine-node
FROM debian:latest
WORKDIR /home/weave
RUN apt-get update && apt-get install -y curl bzip2 libfreetype6 libfontconfig1
# Install nodejs
RUN curl -sL https://deb.nodesource.com/setup | bash -
RUN apt-get install -y nodejs
# build tool
RUN npm install -g gulp
@@ -11,9 +16,4 @@ RUN npm install
ADD gulpfile.js /home/weave/
# run container via
#
# `docker run -v $GOPATH/src/github.com/weaveworks/scope/client:/app weaveworks/scope-build`
#
# after the container is run, bundled app should be in ./dist/ dir
CMD gulp build
# For instructions on running this container, consult the toplevel Makefile

View File

@@ -15,7 +15,7 @@ module.exports = {
grouping: grouping
});
RouterUtils.updateRoute();
WebapiUtils.getNodesDelta(AppStore.getUrlForTopology(AppStore.getCurrentTopology()));
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
},
clickNode: function(nodeId) {
@@ -24,7 +24,7 @@ module.exports = {
nodeId: nodeId
});
RouterUtils.updateRoute();
WebapiUtils.getNodeDetails(AppStore.getUrlForTopology(AppStore.getCurrentTopology()), AppStore.getSelectedNodeId());
WebapiUtils.getNodeDetails(AppStore.getCurrentTopologyUrl(), AppStore.getSelectedNodeId());
},
clickTopology: function(topologyId) {
@@ -33,7 +33,14 @@ module.exports = {
topologyId: topologyId
});
RouterUtils.updateRoute();
WebapiUtils.getNodesDelta(AppStore.getUrlForTopology(AppStore.getCurrentTopology()));
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
},
enterNode: function(nodeId) {
AppDispatcher.dispatch({
type: ActionTypes.ENTER_NODE,
nodeId: nodeId
});
},
hitEsc: function() {
@@ -43,6 +50,13 @@ module.exports = {
RouterUtils.updateRoute();
},
leaveNode: function(nodeId) {
AppDispatcher.dispatch({
type: ActionTypes.LEAVE_NODE,
nodeId: nodeId
});
},
receiveNodeDetails: function(details) {
AppDispatcher.dispatch({
type: ActionTypes.RECEIVE_NODE_DETAILS,
@@ -50,13 +64,20 @@ module.exports = {
});
},
receiveNodesDelta: function(delta) {
AppDispatcher.dispatch({
type: ActionTypes.RECEIVE_NODES_DELTA,
delta: delta
});
},
receiveTopologies: function(topologies) {
AppDispatcher.dispatch({
type: ActionTypes.RECEIVE_TOPOLOGIES,
topologies: topologies
});
WebapiUtils.getNodesDelta(AppStore.getUrlForTopology(AppStore.getCurrentTopology()));
WebapiUtils.getNodeDetails(AppStore.getUrlForTopology(AppStore.getCurrentTopology()), AppStore.getSelectedNodeId());
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
WebapiUtils.getNodeDetails(AppStore.getCurrentTopologyUrl(), AppStore.getSelectedNodeId());
},
route: function(state) {
@@ -64,8 +85,8 @@ module.exports = {
state: state,
type: ActionTypes.ROUTE_TOPOLOGY
});
WebapiUtils.getNodesDelta(AppStore.getUrlForTopology(AppStore.getCurrentTopology()));
WebapiUtils.getNodeDetails(AppStore.getUrlForTopology(AppStore.getCurrentTopology()), AppStore.getSelectedNodeId());
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
WebapiUtils.getNodeDetails(AppStore.getCurrentTopologyUrl(), AppStore.getSelectedNodeId());
}
};

View File

@@ -1,25 +0,0 @@
var AppDispatcher = require('../dispatcher/app-dispatcher');
var ActionTypes = require('../constants/action-types');
module.exports = {
enterNode: function(nodeId) {
AppDispatcher.dispatch({
type: ActionTypes.ENTER_NODE,
nodeId: nodeId
});
},
leaveNode: function(nodeId) {
AppDispatcher.dispatch({
type: ActionTypes.LEAVE_NODE,
nodeId: nodeId
});
},
receiveNodesDelta: function(delta) {
AppDispatcher.dispatch({
type: ActionTypes.RECEIVE_NODES_DELTA,
delta: delta
});
}
};

View File

@@ -8,7 +8,6 @@ var AppStore = require('../stores/app-store');
var Groupings = require('./groupings.js');
var Status = require('./status.js');
var Topologies = require('./topologies.js');
var TopologyStore = require('../stores/topology-store');
var WebapiUtils = require('../utils/web-api-utils');
var AppActions = require('../actions/app-actions');
var Details = require('./details');
@@ -20,12 +19,12 @@ var ESC_KEY_CODE = 27;
function getStateFromStores() {
return {
activeTopology: AppStore.getCurrentTopology(),
currentTopology: AppStore.getCurrentTopology(),
connectionState: AppStore.getConnectionState(),
currentGrouping: AppStore.getCurrentGrouping(),
selectedNodeId: AppStore.getSelectedNodeId(),
nodeDetails: AppStore.getNodeDetails(),
nodes: TopologyStore.getNodes(),
nodes: AppStore.getNodes(),
topologies: AppStore.getTopologies()
}
}
@@ -38,7 +37,6 @@ var App = React.createClass({
},
componentDidMount: function() {
TopologyStore.on(TopologyStore.CHANGE_EVENT, this.onChange);
AppStore.on(AppStore.CHANGE_EVENT, this.onChange);
window.addEventListener('keyup', this.onKeyPress);
@@ -63,13 +61,12 @@ var App = React.createClass({
<div>
{showingDetails && <Details nodes={this.state.nodes}
nodeId={this.state.selectedNodeId}
details={this.state.nodeDetails}
topology={this.state.activeTopology} /> }
details={this.state.nodeDetails} /> }
<div className="header">
<Logo />
<Topologies topologies={this.state.topologies} active={this.state.activeTopology} />
<Groupings active={this.state.currentGrouping} />
<Topologies topologies={this.state.topologies} currentTopology={this.state.currentTopology} />
<Groupings active={this.state.currentGrouping} currentTopology={this.state.currentTopology} />
<Status connectionState={this.state.connectionState} />
</div>

View File

@@ -8,7 +8,6 @@ var IconButton = mui.IconButton;
var AppActions = require('../actions/app-actions');
var NodeDetails = require('./node-details');
var WebapiUtils = require('../utils/web-api-utils');
var Details = React.createClass({

View File

@@ -8,10 +8,12 @@ var AppStore = require('../stores/app-store');
var GROUPINGS = [{
id: 'none',
iconClass: 'fa fa-th'
iconClass: 'fa fa-th',
needsTopology: false
}, {
id: 'grouped',
iconClass: 'fa fa-th-large'
iconClass: 'fa fa-th-large',
needsTopology: 'grouped_url'
}];
var Groupings = React.createClass({
@@ -21,22 +23,40 @@ var Groupings = React.createClass({
AppActions.clickGrouping(ev.currentTarget.getAttribute('rel'));
},
renderGrouping: function(grouping, active) {
var className = grouping.id === active ? "groupings-item groupings-item-active" : "groupings-item";
isGroupingSupportedByTopology: function(topology, grouping) {
return !grouping.needsTopology || topology && topology[grouping.needsTopology];
},
getGroupingsSupportedByTopology: function(topology) {
return _.filter(GROUPINGS, _.partial(this.isGroupingSupportedByTopology, topology));
},
renderGrouping: function(grouping, activeGroupingId) {
var className = "groupings-item",
isSupportedByTopology = this.isGroupingSupportedByTopology(this.props.currentTopology, grouping);
if (grouping.id === activeGroupingId) {
className += " groupings-item-active";
} else if (!isSupportedByTopology) {
className += " groupings-item-disabled";
} else {
className += " groupings-item-default";
}
return (
<div className={className} key={grouping.id} rel={grouping.id} onClick={this.onGroupingClick}>
<div className={className} key={grouping.id} rel={grouping.id} onClick={isSupportedByTopology && this.onGroupingClick}>
<span className={grouping.iconClass} />
</div>
);
},
render: function() {
var activeGrouping = this.props.active;
var activeGrouping = this.props.active,
isGroupingSupported = _.size(this.getGroupingsSupportedByTopology(this.props.currentTopology)) > 1;
return (
<div className="groupings">
{GROUPINGS.map(function(grouping) {
{isGroupingSupported && GROUPINGS.map(function(grouping) {
return this.renderGrouping(grouping, activeGrouping);
}, this)}
</div>

View File

@@ -14,8 +14,9 @@ var Topologies = React.createClass({
},
renderTopology: function(topology, active) {
var className = AppStore.isUrlForTopology(topology.url, active) ? "topologies-item topologies-item-active" : "topologies-item",
topologyId = AppStore.getTopologyForUrl(topology.url),
var isActive = topology.name === this.props.currentTopology.name,
className = isActive ? "topologies-item topologies-item-active" : "topologies-item",
topologyId = AppStore.getTopologyIdForUrl(topology.url),
title = ['Topology: ' + topology.name,
'Nodes: ' + topology.stats.node_count,
'Connections: ' + topology.stats.node_count].join('\n');
@@ -32,15 +33,14 @@ var Topologies = React.createClass({
},
render: function() {
var activeTopologyId = this.props.active,
topologies = _.sortBy(this.props.topologies, function(topology) {
var topologies = _.sortBy(this.props.topologies, function(topology) {
return topology.name;
});
return (
<div className="topologies">
{topologies.map(function(topology) {
return this.renderTopology(topology, activeTopologyId);
{this.props.currentTopology && topologies.map(function(topology) {
return this.renderTopology(topology);
}, this)}
</div>
);

View File

@@ -0,0 +1,61 @@
describe('AppStore', function() {
var ActionTypes = require('../../constants/action-types');
var AppStore, registeredCallback;
// actions
var ClickTopologyAction = {
type: ActionTypes.CLICK_TOPOLOGY,
topologyId: 'topo1'
};
var ClickGroupingAction = {
type: ActionTypes.CLICK_GROUPING,
grouping: 'grouped'
};
var ReceiveTopologiesAction = {
type: ActionTypes.RECEIVE_TOPOLOGIES,
topologies: [{
url: '/topo1',
grouped_url: '/topo1grouped',
name: 'Topo1'
}]
};
beforeEach(function() {
AppStore = require('../app-store');
registeredCallback = AppStore.registeredCallback;
});
// topology tests
it('init with no topologies', function() {
var topos = AppStore.getTopologies();
expect(topos.length).toBe(0);
expect(AppStore.getCurrentTopology()).toBeUndefined();
});
it('get current topology', function() {
registeredCallback(ClickTopologyAction);
registeredCallback(ReceiveTopologiesAction);
expect(AppStore.getTopologies().length).toBe(1);
expect(AppStore.getCurrentTopology().name).toBe('Topo1');
expect(AppStore.getCurrentTopologyUrl()).toBe('/topo1');
});
it('get grouped topology', function() {
registeredCallback(ClickTopologyAction);
registeredCallback(ReceiveTopologiesAction);
registeredCallback(ClickGroupingAction);
expect(AppStore.getTopologies().length).toBe(1);
expect(AppStore.getCurrentTopology().name).toBe('Topo1');
expect(AppStore.getCurrentTopologyUrl()).toBe('/topo1grouped');
});
});

View File

@@ -5,15 +5,15 @@ var assign = require('object-assign');
var AppDispatcher = require('../dispatcher/app-dispatcher');
var ActionTypes = require('../constants/action-types');
var TopologyStore = require('./topology-store');
// var topologies = require('../constants/topologies');
// Initial values
var connectionState = 'disconnected';
var currentGrouping = 'none';
var currentTopology = 'applications';
var currentTopologyId = 'applications';
var mouseOverNode = null;
var nodes = {};
var nodeDetails = null;
var selectedNodeId = null;
var topologies = [];
@@ -26,8 +26,8 @@ var AppStore = assign({}, EventEmitter.prototype, {
getAppState: function() {
return {
currentTopology: this.getCurrentTopology(),
currentGrouping: this.getCurrentGrouping(),
topologyId: currentTopologyId,
grouping: this.getCurrentGrouping(),
selectedNodeId: this.getSelectedNodeId()
};
},
@@ -37,7 +37,17 @@ var AppStore = assign({}, EventEmitter.prototype, {
},
getCurrentTopology: function() {
return currentTopology;
return _.find(topologies, function(topology) {
return isUrlForTopologyId(topology.url, currentTopologyId);
});
},
getCurrentTopologyUrl: function() {
var topology = this.getCurrentTopology();
if (topology) {
return topology.grouped_url && currentGrouping == 'grouped' ? topology.grouped_url : topology.url;
}
},
getCurrentGrouping: function() {
@@ -48,6 +58,10 @@ var AppStore = assign({}, EventEmitter.prototype, {
return nodeDetails;
},
getNodes: function() {
return nodes;
},
getSelectedNodeId: function() {
return selectedNodeId;
},
@@ -56,40 +70,35 @@ var AppStore = assign({}, EventEmitter.prototype, {
return topologies;
},
getTopologyForUrl: function(url) {
getTopologyIdForUrl: function(url) {
return url.split('/').pop();
},
getUrlForTopology: function(topologyId) {
var topology = _.find(topologies, function(topology) {
return this.isUrlForTopology(topology.url, topologyId);
}, this);
if (topology) {
return topology.grouped_url && currentGrouping == 'grouped' ? topology.grouped_url : topology.url;
}
},
isUrlForTopology: function(url, topologyId) {
return _.endsWith(url, topologyId);
}
});
// Helpers
function isUrlForTopologyId(url, topologyId) {
return _.endsWith(url, topologyId);
}
// Store Dispatch Hooks
AppStore.dispatchToken = AppDispatcher.register(function(payload) {
AppStore.registeredCallback = function(payload) {
switch (payload.type) {
case ActionTypes.CLICK_CLOSE_DETAILS:
selectedNodeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLICK_GROUPING:
currentGrouping = payload.grouping;
AppDispatcher.waitFor([TopologyStore.dispatchToken]);
AppStore.emit(AppStore.CHANGE_EVENT);
if (payload.grouping !== currentGrouping) {
currentGrouping = payload.grouping;
nodes = {};
AppStore.emit(AppStore.CHANGE_EVENT);
}
break;
case ActionTypes.CLICK_NODE:
@@ -98,8 +107,15 @@ AppStore.dispatchToken = AppDispatcher.register(function(payload) {
break;
case ActionTypes.CLICK_TOPOLOGY:
currentTopology = payload.topologyId;
AppDispatcher.waitFor([TopologyStore.dispatchToken]);
if (payload.topologyId !== currentTopologyId) {
currentTopologyId = payload.topologyId;
nodes = {};
}
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.ENTER_NODE:
mouseOverNode = payload.nodeId;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
@@ -109,14 +125,43 @@ AppStore.dispatchToken = AppDispatcher.register(function(payload) {
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.LEAVE_NODE:
mouseOverNode = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_NODE_DETAILS:
nodeDetails = payload.details;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_NODES_DELTA:
console.log('RECEIVE_NODES_DELTA',
'remove', _.size(payload.delta.remove),
'update', _.size(payload.delta.update),
'add', _.size(payload.delta.add));
connectionState = "connected";
AppDispatcher.waitFor([TopologyStore.dispatchToken]);
// nodes that no longer exist
_.each(payload.delta.remove, function(nodeId) {
// in case node disappears before mouseleave event
if (mouseOverNode === nodeId) {
mouseOverNode = null;
}
delete nodes[nodeId];
});
// update existing nodes
_.each(payload.delta.update, function(node) {
nodes[node.id] = node;
});
// add new nodes
_.each(payload.delta.add, function(node) {
nodes[node.id] = node;
});
AppStore.emit(AppStore.CHANGE_EVENT);
break;
@@ -126,10 +171,10 @@ AppStore.dispatchToken = AppDispatcher.register(function(payload) {
break;
case ActionTypes.ROUTE_TOPOLOGY:
currentTopology = payload.state.currentTopology;
currentGrouping = payload.state.currentGrouping;
nodes = {};
currentTopologyId = payload.state.topologyId;
currentGrouping = payload.state.grouping;
selectedNodeId = payload.state.selectedNodeId;
AppDispatcher.waitFor([TopologyStore.dispatchToken]);
AppStore.emit(AppStore.CHANGE_EVENT);
break;
@@ -137,6 +182,8 @@ AppStore.dispatchToken = AppDispatcher.register(function(payload) {
break;
}
});
};
AppStore.dispatchToken = AppDispatcher.register(AppStore.registeredCallback);
module.exports = AppStore;

View File

@@ -1,86 +0,0 @@
var EventEmitter = require('events').EventEmitter;
var _ = require('lodash');
var assign = require('object-assign');
var AppDispatcher = require('../dispatcher/app-dispatcher');
var ActionTypes = require('../constants/action-types');
// Initial values
var nodes = {};
var mouseOverNode = null;
// Store API
var TopologyStore = assign({}, EventEmitter.prototype, {
CHANGE_EVENT: 'change',
getNodes: function() {
return nodes;
}
});
// Store Dispatch Hooks
TopologyStore.dispatchToken = AppDispatcher.register(function(payload) {
switch (payload.type) {
case ActionTypes.CLICK_GROUPING:
nodes = {};
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
break;
case ActionTypes.CLICK_TOPOLOGY:
nodes = {};
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
break;
case ActionTypes.ENTER_NODE:
mouseOverNode = payload.nodeId;
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
break;
case ActionTypes.LEAVE_NODE:
mouseOverNode = null;
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_NODES_DELTA:
// nodes that no longer exist
_.each(payload.delta.remove, function(nodeId) {
// in case node disappears before mouseleave event
if (mouseOverNode === nodeId) {
mouseOverNode = null;
}
delete nodes[nodeId];
});
// update existing nodes
_.each(payload.delta.update, function(node) {
nodes[node.id] = node;
});
// add new nodes
_.each(payload.delta.add, function(node) {
nodes[node.id] = node;
});
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
break;
case ActionTypes.ROUTE_TOPOLOGY:
nodes = {};
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
break;
default:
break;
}
});
module.exports = TopologyStore;

View File

@@ -1,8 +1,6 @@
var reqwest = require('reqwest');
var TopologyActions = require('../actions/topology-actions');
var AppActions = require('../actions/app-actions');
var AppStore = require('../stores/app-store');
var WS_URL = window.WS_URL || 'ws://' + location.host;
@@ -33,7 +31,7 @@ function createWebsocket(topologyUrl) {
socket.onmessage = function(event) {
var msg = JSON.parse(event.data);
if (msg.add || msg.remove || msg.update) {
TopologyActions.receiveNodesDelta(msg);
AppActions.receiveNodesDelta(msg);
}
};
@@ -48,9 +46,9 @@ function getTopologies() {
});
}
function getNodeDetails(topology, nodeId) {
if (nodeId) {
var url = [AppStore.getUrlForTopology(topology), nodeId].join('/');
function getNodeDetails(topologyUrl, nodeId) {
if (topologyUrl && nodeId) {
var url = [topologyUrl, nodeId].join('/');
reqwest(url, function(res) {
AppActions.receiveNodeDetails(res.node);
});

View File

@@ -101,7 +101,13 @@ body {
display: inline-block;
color: @text-tertiary-color;
&-active, &:hover {
&-disabled {
color: @text-tertiary-color;
cursor: default;
}
&-default:hover,
&-active {
color: @text-color;
}
}

View File

@@ -38,15 +38,25 @@
"gulp-size": "^1.2.1",
"gulp-sourcemaps": "^1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-useref": "^1.1.1",
"gulp-util": "^3.0.4",
"jasmine-core": "^2.3.4",
"jshint-stylish": "^1.0.2",
"karma": "^0.12.32",
"karma-browserify": "^4.2.1",
"karma-cli": "0.0.4",
"karma-jasmine": "^0.3.5",
"karma-phantomjs-launcher": "^0.1.4",
"opn": "^1.0.1",
"proxy-middleware": "^0.11.1",
"react-tools": "^0.13.3",
"reactify": "^1.1.1",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0"
},
"scripts": {
"start": "gulp"
"start": "gulp",
"test": "karma start test/karma.conf.js --single-run"
},
"engines": {
"node": ">=0.10.0"

9
client/test/README.md Normal file
View File

@@ -0,0 +1,9 @@
# Testing
Scope unit testing is done unsing Karma/Jasmine. (Jest was too big and slow.)
To run tests, do `npm test` in the toplevel directory.
The tests are placed in `__tests__` directories, relative to what they are testing.
For more info see [Testing Flux Apps with Karma](http://kentor.me/posts/testing-react-and-flux-applications-with-karma-and-webpack/)

View File

@@ -1,9 +0,0 @@
{
"name": "gulp-webapp",
"private": true,
"dependencies": {
"chai": "~1.8.0",
"mocha": "~1.14.0"
},
"devDependencies": {}
}

View File

@@ -1,26 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Mocha Spec Runner</title>
<link rel="stylesheet" href="bower_components/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
<script src="bower_components/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="bower_components/chai/chai.js"></script>
<script>
var assert = chai.assert;
var expect = chai.expect;
var should = chai.should();
</script>
<!-- include source files here... -->
<!-- include spec files here... -->
<script src="spec/test.js"></script>
<script>mocha.run()</script>
</body>
</html>

23
client/test/karma.conf.js Normal file
View File

@@ -0,0 +1,23 @@
module.exports = function(config) {
config.set({
browsers: [
'PhantomJS'
],
files: [
'../app/**/__tests__/*.js'
],
frameworks: [
'jasmine', 'browserify'
],
preprocessors: {
'../app/**/__tests__/*.js': ['browserify']
},
browserify: {
debug: true,
transform: ['reactify']
},
reporters: [
'dots'
]
});
};

View File

@@ -0,0 +1,7 @@
var ReactTools = require('react-tools');
module.exports = {
process: function(src) {
return ReactTools.transform(src);
}
};

View File

@@ -1,13 +0,0 @@
/* global describe, it */
(function () {
'use strict';
describe('Give it some context', function () {
describe('maybe a bit more context here', function () {
it('should run here few assertions', function () {
});
});
});
})();