fecaille 9 年 前
コミット
58a1bd06a1

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+.classpath
+.project
+.settings
+src/main/resources/static
+src/main/webapp/js/.module-cache
+src/test/resources/static/js/compiled
+src/test/resources/static/js/.module-cache
+target

+ 349 - 0
pom.xml

@@ -0,0 +1,349 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>com.opengroupe.cloud.saas</groupId>
+	<artifactId>catalog-ui</artifactId>
+	<version>1.0.0-SNAPSHOT</version>
+	<name>UI service project</name>
+	<description>Default tools to build UI BFF</description>
+	<packaging>jar</packaging>
+	<url>http://www.open-groupe.com</url>
+
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>1.3.3.RELEASE</version>
+	</parent>
+
+	<properties>
+		<java.version>1.8</java.version>
+		<jasmine.version>2.4.1</jasmine.version>
+		<jasmine-ajax.version>3.2.0</jasmine-ajax.version>
+
+		<bootstrap.version>3.3.6</bootstrap.version>
+		<jquery.version>2.2.1</jquery.version>
+		<react.version>0.14.7</react.version>
+		<marked.version>0.3.2-1</marked.version>
+		<marked-lib.version>0.3.2</marked-lib.version>
+	</properties>
+
+	<dependencies>
+		<!-- Spring Boot Core -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+		<!-- View templating -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-thymeleaf</artifactId>
+		</dependency>
+		<!-- Testing -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.jayway.jsonpath</groupId>
+			<artifactId>json-path</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.jayway.jsonpath</groupId>
+			<artifactId>json-path-assert</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.webjars</groupId>
+			<artifactId>jasmine</artifactId>
+			<version>${jasmine.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+		    <groupId>org.webjars</groupId>
+		    <artifactId>jquery-mockjax</artifactId>
+		    <version>1.5.3</version>
+		    <scope>test</scope>
+		</dependency>
+		<!-- Monitoring -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-actuator</artifactId>
+		</dependency>
+		<!-- Hot swapping for dev purposes -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-devtools</artifactId>
+			<optional>true</optional>
+		</dependency>
+		<!-- Webjars -->
+		<dependency>
+			<groupId>org.webjars</groupId>
+			<artifactId>bootstrap</artifactId>
+			<version>${bootstrap.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.webjars</groupId>
+			<artifactId>jquery</artifactId>
+			<version>${jquery.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.webjars</groupId>
+			<artifactId>react</artifactId>
+			<version>${react.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.webjars</groupId>
+			<artifactId>marked</artifactId>
+			<version>${marked.version}</version>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<resources>
+			<resource>
+				<directory>${project.basedir}/src/main/resources</directory>
+			</resource>
+		</resources>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>com.fizzed</groupId>
+				<artifactId>fizzed-watcher-maven-plugin</artifactId>
+				<version>1.0.6</version>
+				<configuration>
+					<watches>
+						<watch>
+							<directory>src/main/webapp/js</directory>
+						</watch>
+						<watch>
+							<directory>src/main/wro</directory>
+						</watch>
+					</watches>
+					<goals>
+						<goal>process-resources</goal>
+					</goals>
+				</configuration>
+			</plugin>
+			<!-- Resource optimization -->
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-antrun-plugin</artifactId>
+				<executions>
+					<execution>
+						<phase>clean</phase>
+						<goals>
+							<goal>run</goal>
+						</goals>
+						<configuration>
+							<target>
+								<delete dir="${project.basedir}/src/main/resources/static" />
+								<delete dir="${project.basedir}/src/test/resources/static/js/compiled" />
+							</target>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<artifactId>maven-resources-plugin</artifactId>
+				<executions>
+					<execution>
+						<!-- Serves *only* to filter the wro.xml so it can get an absolute 
+							path for the project -->
+						<id>copy-resources</id>
+						<phase>validate</phase>
+						<goals>
+							<goal>copy-resources</goal>
+						</goals>
+						<configuration>
+							<outputDirectory>${basedir}/target/wro</outputDirectory>
+							<resources>
+								<resource>
+									<directory>src/main/wro</directory>
+									<filtering>true</filtering>
+								</resource>
+							</resources>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>uk.co.codezen</groupId>
+				<artifactId>react-jsxtransformer-maven-plugin</artifactId>
+				<version>1.0</version>
+				<executions>
+					<execution>
+						<id>Compile resources</id>
+						<phase>process-resources</phase>
+						<goals>
+							<goal>compile</goal>
+						</goals>
+						<configuration>
+							<extension>jsx</extension>
+							<sourcePath>
+								${project.basedir}/src/main/webapp/js
+							</sourcePath>
+							<targetPath>
+								${project.basedir}/src/main/resources/static/js
+							</targetPath>
+						</configuration>
+					</execution>
+					<execution>
+						<id>Compile test resources</id>
+						<phase>process-test-resources</phase>
+						<goals>
+							<goal>compile</goal>
+						</goals>
+						<configuration>
+							<extension>jsx</extension>
+							<sourcePath>
+								${project.basedir}/src/test/resources/static/js
+							</sourcePath>
+							<targetPath>
+								${project.basedir}/src/test/resources/static/js/compiled
+							</targetPath>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>ro.isdc.wro4j</groupId>
+				<artifactId>wro4j-maven-plugin</artifactId>
+				<version>1.7.9</version>
+				<executions>
+					<execution>
+						<phase>process-resources</phase>
+						<goals>
+							<goal>run</goal>
+						</goals>
+					</execution>
+				</executions>
+				<configuration>
+					<wroManagerFactory>ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory</wroManagerFactory>
+					<cssDestinationFolder>${project.basedir}/src/main/resources/static/css</cssDestinationFolder>
+					<jsDestinationFolder>${project.basedir}/src/main/resources/static/js</jsDestinationFolder>
+					<wroFile>${project.build.directory}/wro/wro.xml</wroFile>
+					<extraConfigFile>${project.basedir}/src/main/wro/wro.properties</extraConfigFile>
+				</configuration>
+			</plugin>
+			<!-- Testing -->
+			<plugin>
+				<groupId>com.github.klieber</groupId>
+				<artifactId>phantomjs-maven-plugin</artifactId>
+				<version>0.7</version>
+				<executions>
+					<execution>
+						<goals>
+							<goal>install</goal>
+						</goals>
+					</execution>
+				</executions>
+				<configuration>
+					<version>2.1.1</version>
+					<skip>false</skip>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>com.github.searls</groupId>
+				<artifactId>jasmine-maven-plugin</artifactId>
+				<version>2.1</version>
+				<executions>
+					<execution>
+						<goals>
+							<goal>test</goal>
+						</goals>
+					</execution>
+				</executions>
+				<configuration>
+					<webDriverClassName>org.openqa.selenium.phantomjs.PhantomJSDriver</webDriverClassName>
+					<webDriverCapabilities>
+						<capability>
+							<name>phantomjs.binary.path</name>
+							<value>${phantomjs.binary}</value>
+						</capability>
+					</webDriverCapabilities>
+					<preloadSources>
+						<preloadSource>/webjars/jquery.js</preloadSource>
+						<preloadSource>/webjars/react-with-addons.js</preloadSource>
+						<preloadSource>/webjars/react-dom.js</preloadSource>
+						<preloadSource>/webjars/jquery.mockjax.js</preloadSource>
+						<preloadSource>${project.basedir}/src/test/resources/jasmine/config.js</preloadSource>
+					</preloadSources>
+ 					<jsSrcDir>${project.basedir}/src/main/resources/static/js</jsSrcDir>
+					<sourceIncludes>
+						<include>**/*.js</include>
+					</sourceIncludes>
+ 					<sourceExcludes>
+ 						<exclude>**/react-bootstrap.js</exclude>
+ 						<exclude>**/app.render.js</exclude>
+ 					</sourceExcludes>
+					<jsTestSrcDir>${project.basedir}/src/test/resources/static/js</jsTestSrcDir>
+					<specIncludes>
+						<include>**/*.spec.js</include>
+					</specIncludes>
+					<customRunnerTemplate>${project.basedir}/src/test/resources/jasmine/ReactJsSpecRunner.htmltemplate</customRunnerTemplate>
+				</configuration>
+			</plugin>
+		</plugins>
+
+		<pluginManagement>
+			<plugins>
+				<plugin>
+					<groupId>org.eclipse.m2e</groupId>
+					<artifactId>lifecycle-mapping</artifactId>
+					<version>1.0.0</version>
+					<configuration>
+						<lifecycleMappingMetadata>
+							<pluginExecutions>
+								<pluginExecution>
+									<pluginExecutionFilter>
+										<groupId>ro.isdc.wro4j</groupId>
+										<artifactId>wro4j-maven-plugin</artifactId>
+										<versionRange>[1.7.9,)</versionRange>
+										<goals>
+											<goal>jshint</goal>
+											<goal>run</goal>
+										</goals>
+									</pluginExecutionFilter>
+									<action>
+										<ignore></ignore>
+									</action>
+								</pluginExecution>
+								<pluginExecution>
+									<pluginExecutionFilter>
+										<groupId>uk.co.codezen</groupId>
+										<artifactId>react-jsxtransformer-maven-plugin</artifactId>
+										<versionRange>[1.0,)</versionRange>
+										<goals>
+											<goal>compile</goal>
+										</goals>
+									</pluginExecutionFilter>
+									<action>
+										<ignore></ignore>
+									</action>
+								</pluginExecution>
+								<pluginExecution>
+									<pluginExecutionFilter>
+										<groupId>com.github.klieber</groupId>
+										<artifactId>phantomjs-maven-plugin</artifactId>
+										<versionRange>[0.7,)</versionRange>
+										<goals>
+											<goal>install</goal>
+										</goals>
+									</pluginExecutionFilter>
+									<action>
+										<ignore></ignore>
+									</action>
+								</pluginExecution>
+							</pluginExecutions>
+						</lifecycleMappingMetadata>
+					</configuration>
+				</plugin>
+			</plugins>
+		</pluginManagement>
+	</build>
+</project>

+ 12 - 0
src/main/java/com/opengroupe/cloud/saas/Application.java

@@ -0,0 +1,12 @@
+package com.opengroupe.cloud.saas;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+	public static void main(String[] args) {
+		SpringApplication.run(Application.class, args);
+	}
+}

+ 26 - 0
src/main/java/com/opengroupe/cloud/saas/domain/Comment.java

@@ -0,0 +1,26 @@
+package com.opengroupe.cloud.saas.domain;
+
+import groovy.transform.ToString;
+
+@ToString
+public class Comment {
+
+	private final Long id;
+	private final String author;
+	private final String text;
+	
+	public Comment(Long id, String author, String text) {
+		this.id = id;
+		this.author = author;
+		this.text = text;
+	}
+	public Long getId() {
+		return id;
+	}
+	public String getAuthor() {
+		return author;
+	}
+	public String getText() {
+		return text;
+	}
+}

+ 37 - 0
src/main/java/com/opengroupe/cloud/saas/rest/CommentController.java

@@ -0,0 +1,37 @@
+package com.opengroupe.cloud.saas.rest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.opengroupe.cloud.saas.domain.Comment;
+
+@RestController
+public class CommentController {
+
+	private List<Comment> comments = new ArrayList<Comment>();
+
+	@RequestMapping("/")
+	public String index() {
+		return "Greetings from Spring Boot!";
+	}
+
+	@RequestMapping(value="/api/comments", method=RequestMethod.GET)
+	public @ResponseBody List<Comment> comments() {
+		return comments;
+	}
+
+	@RequestMapping(value="/api/comments", method=RequestMethod.POST)
+	public @ResponseBody List<Comment> comments(
+			@RequestParam(value="id", required=true) Long id, 
+			@RequestParam(value="author", required=true) String author, 
+			@RequestParam(value="text", required=true) String text) {
+		comments.add(new Comment(id, author, text));
+		return comments;
+	}
+}

+ 21 - 0
src/main/java/com/opengroupe/cloud/saas/web/ViewController.java

@@ -0,0 +1,21 @@
+package com.opengroupe.cloud.saas.web;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Controller
+public class ViewController {
+	
+	@RequestMapping("/greeting")
+	public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {
+		model.addAttribute("name", name);
+		return "greeting";
+	}
+	
+	@RequestMapping("/index")
+	public String index(Model model) {
+		return "index";
+	}
+}

+ 16 - 0
src/main/resources/application.yml

@@ -0,0 +1,16 @@
+spring:
+  main:
+    banner_mode: off
+
+project:
+  artifactId: template
+  name: Demo
+  version: X.X.X
+  description: Demo project for info endpoint
+
+info:
+  build:
+    artifact: ${project.artifactId}
+    name: ${project.name}
+    description: ${project.description}
+    version: ${project.version}

+ 10 - 0
src/main/resources/templates/greeting.html

@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <title>Getting Started: Serving Web Content</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+</head>
+<body>
+    <p th:text="'Hello, ' + ${name} + '!'" />
+</body>
+</html>

+ 17 - 0
src/main/resources/templates/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+	<head lang="en">
+	    <meta charset="UTF-8"/>
+	    <title>ReactJS</title>
+	    <link rel="stylesheet" href="/css/react-bootstrap.css" />
+	</head>
+	<body>
+	
+	    <div id="content"></div>
+	
+		<script src="/js/react-bootstrap.js"></script>
+	    <script src="/js/bundle.js"></script>
+	    <script src="/js/app.js"></script>
+	    <script src="/js/app.render.js"></script>
+	</body>
+</html>

+ 123 - 0
src/main/webapp/js/app.jsx

@@ -0,0 +1,123 @@
+'use strict';
+
+var Comment = React.createClass({
+	rawMarkup: function() {
+		var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
+		return { __html: rawMarkup };
+	},
+	render: function() {
+		return (
+			<div className="comment">
+				<h2 className="commentAuthor">
+					{this.props.author}
+				</h2>
+				<span dangerouslySetInnerHTML={this.rawMarkup()} />
+			</div>
+		);
+	}
+});
+
+var CommentForm = React.createClass({
+	getInitialState: function() {
+		return {author: '', text: ''};
+	},
+	handleAuthorChange: function(e) {
+		this.setState({author: e.target.value});
+	},
+	handleTextChange: function(e) {
+		this.setState({text: e.target.value});
+	},
+	handleSubmit: function(e) {
+		e.preventDefault();
+		var author = this.state.author.trim();
+		var text = this.state.text.trim();
+		if( !text || !author ) {
+			return;
+		}
+		this.props.onCommentSubmit({author: author, text: text})
+		this.setState({author: '', text: ''});
+	},
+	render: function() {
+		return (
+			<form className="commentForm" onSubmit={this.handleSubmit}>
+				<input
+					type="text"
+					placeholder="Your name"
+					value={this.state.author}
+					onChange={this.handleAuthorChange}
+				/>
+				<input
+					type="text"
+					placeholder="Say something..."
+					value={this.state.text}
+					onChange={this.handleTextChange}
+				/>
+				<input type="submit" value="Post" />
+			</form>
+		);
+	}
+});
+var CommentBox = React.createClass({
+	getInitialState: function() {
+		return {data: []};
+	},
+	loadCommentsFromServer: function() {
+		$.ajax({
+			url: this.props.url,
+			dataType: 'json',
+			cache: false,
+			success: function(data) {
+				this.setState({data: data});
+			}.bind(this),
+			error: function(xhr, status, err) {
+				console.error(this.props.url, status, err.toString());
+			}.bind(this)
+		});
+	},
+	handleCommentSubmit: function(comment) {
+		var comments = this.state.data;
+		comment.id = Date.now();
+		var newComments = comments.concat([comment]);
+		this.setState({data: newComments});
+		$.ajax({
+			url: this.props.url,
+			method: 'POST',
+			dataType: 'json',
+			data: comment,
+			success: function(data) {
+				this.setState({data: data});
+			}.bind(this),
+			error: function(xhr, status, err) {
+				this.setState({data: comments});
+				console.error(this.props.url, status, err.toString());
+			}.bind(this)
+		});
+	},
+	componentDidMount: function() {
+		this.loadCommentsFromServer();
+		setInterval(this.loadCommentsFromServer, this.props.pollInterval);
+	},
+	render: function() {
+		return (
+			<div className="commentBox">
+				<h1>Comments</h1>
+				<CommentList data={this.state.data}/>
+				<CommentForm onCommentSubmit={this.handleCommentSubmit}/>
+			</div>
+		);
+	}
+});
+var CommentList = React.createClass({
+	render: function() {
+		var commentNodes = this.props.data.map( function(comment) {
+			return (
+				<Comment author={comment.author} key={comment.id}>{comment.text}</Comment>
+			);
+		});
+		return (
+			<div className="commentList">
+				{commentNodes}
+			</div>
+		)
+	}
+});

+ 4 - 0
src/main/webapp/js/app.render.js

@@ -0,0 +1,4 @@
+ReactDOM.render(
+	<CommentBox url="/api/comments" pollInterval={2000}/>,
+	document.getElementById('content')
+);

+ 8 - 0
src/main/wro/wro.properties

@@ -0,0 +1,8 @@
+debug=true
+# Available processors : http://wro4j.readthedocs.org/en/stable/AvailableProcessors/
+preProcessors=lessCssImport
+postProcessors=less4j,cssMin
+# explicitly invalidates the cache each 5 seconds
+cacheUpdatePeriod=5
+# check for changes each 5 seconds and invalidates the cache only when a change is detected
+resourceWatcherUpdatePeriod=5

+ 13 - 0
src/main/wro/wro.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<groups xmlns="http://www.isdc.ro/wro">
+	<group name="react-bootstrap">
+		<css>webjar:bootstrap/@bootstrap.version@/css/bootstrap.css</css>
+		<js>webjar:jquery/@jquery.version@/jquery.js</js>
+		<js>webjar:react/@react.version@/react-with-addons.js</js>
+		<js>webjar:react/@react.version@/react-dom.js</js>
+	</group>
+	
+	<group name="bundle">
+		<js>webjar:marked/@marked-lib.version@/marked.js</js>
+	</group>
+</groups>

+ 64 - 0
src/test/java/com/opengroupe/cloud/saas/rest/CommentControllerTest.java

@@ -0,0 +1,64 @@
+package com.opengroupe.cloud.saas.rest;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = MockServletContext.class)
+@WebAppConfiguration
+public class CommentControllerTest {
+
+	private MockMvc mvc;
+	
+	@Before
+	public void setUp() throws Exception {
+		mvc = MockMvcBuilders.standaloneSetup(new CommentController()).build();
+		
+	}
+
+	@Test
+	public void getHello() throws Exception {
+		mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
+			.andExpect(status().isOk())
+			.andExpect(content().string(equalTo("Greetings from Spring Boot!")));
+	}
+	
+	@Test
+	public void getEmptyComment() throws Exception {
+		mvc.perform(MockMvcRequestBuilders.get("/api/comments").accept(MediaType.APPLICATION_JSON))
+			.andExpect(status().isOk())
+			.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
+			.andExpect(jsonPath("$", hasSize(0)));
+	}
+	
+	@Test
+	public void postComment() throws Exception {
+		mvc.perform(MockMvcRequestBuilders.post("/api/comments")
+					.accept(MediaType.APPLICATION_JSON)
+					.param("id", "1")
+					.param("author", "Lao Tzu")
+					.param("text", "The journey of a thousand miles begins with one step"))
+			.andExpect(status().isOk())
+			.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
+			.andExpect(jsonPath("$", hasSize(1)))
+			.andExpect(jsonPath("$[0].id", is(1)))
+			.andExpect(jsonPath("$[0].author", is("Lao Tzu")))
+			.andExpect(jsonPath("$[0].text", is("The journey of a thousand miles begins with one step")));
+	}
+}

+ 52 - 0
src/test/java/com/opengroupe/cloud/saas/web/ViewControllerTest.java

@@ -0,0 +1,52 @@
+package com.opengroupe.cloud.saas.web;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
+
+import javax.annotation.Resource;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import com.opengroupe.cloud.saas.Application;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = Application.class)
+@WebAppConfiguration
+public class ViewControllerTest {
+	private MockMvc mvc;
+	
+	@Resource
+	WebApplicationContext wac;
+
+	@Before
+	public void setUp() throws Exception {
+
+		// Process mock annotations
+		MockitoAnnotations.initMocks(this);
+
+		mvc = MockMvcBuilders.webAppContextSetup(wac).build();
+	}
+
+	@Test
+	public void getDefaultGreetings() throws Exception {
+		mvc.perform(MockMvcRequestBuilders.get("/greeting"))
+			.andExpect(status().isOk())
+			.andExpect(content().contentType(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8"))
+			.andExpect(model().attribute("name", equalTo("World")))
+			.andExpect(view().name("greeting"));
+	}
+}

+ 35 - 0
src/test/resources/jasmine/ReactJsSpecRunner.htmltemplate

@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=$sourceEncoding$">
+  $if(autoRefresh)$
+  <meta http-equiv="refresh" content="$autoRefreshInterval$">
+  $endif$
+  <title>Jasmine Spec Runner</title>
+  <script type="text/javascript">
+    window.onerror = function(msg,url,line) {
+      if (document.head) {
+        var jserror = document.head.getAttribute('jmp_jserror') || '';
+        if (jserror) {
+          jserror += ':!:';
+        }
+        jserror += msg;
+        document.head.setAttribute('jmp_jserror',jserror);
+      }
+    };
+  </script>
+  $cssDependencies$
+  $javascriptDependencies$
+  <script type="text/javascript">
+    window.onload = jasmine.boot;
+  </script>
+</head>
+<body>
+  $allScriptTags$
+  <script type="text/javascript">
+    if(window.location.href.indexOf("ManualSpecRunner.html") !== -1) {
+      document.body.appendChild(document.createTextNode("Warning: Opening this HTML file directly from the file system is deprecated. You should instead try running `mvn jasmine:bdd` from the command line, and then visit `http://localhost:8234` in your browser. "))
+    }
+  </script>
+</body>
+</html>

+ 8 - 0
src/test/resources/jasmine/config.js

@@ -0,0 +1,8 @@
+'use strict';
+
+$("body").append("<div id='content'></div>");
+
+$.mockjax({
+  url: "/api/comments",
+  responseText: []
+});

+ 80 - 0
src/test/resources/static/js/app.spec.jsx

@@ -0,0 +1,80 @@
+'use strict';
+
+var AjaxResponses = {
+	empty: {
+		success: {
+			status: 200,
+			responseText: '[]'
+		}
+	},
+	submit: {
+		success: {
+			status: 200,
+			responseText: '[{id: 1, author: "Lao Tzu", text: "The journey of a thousand miles begins with one step"}]'
+		}
+	}
+};
+
+var TestUtils = React.addons.TestUtils;
+
+describe('Comments', function() {
+	
+	var instance,
+		container = document.createElement("div");
+	
+	afterEach(function() {
+		if (instance && instance.isMounted()) {
+			// Only components with a parent will be unmounted
+			ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(instance).parentNode);
+	    }
+	});
+	
+	describe("rendered without a container reference", function() {
+	    beforeEach(function() {
+	      instance = TestUtils.renderIntoDocument(<CommentBox url="/api/comments" pollInterval={200000}/>);
+	    });
+
+	    it("should render a heading with the given text", function() {
+	      var heading = TestUtils.findRenderedDOMComponentWithTag(instance, "h1");
+	      expect(ReactDOM.findDOMNode(heading).textContent).toBe("Comments");
+	    });
+	  });
+
+	  describe("with a container reference required", function() {
+	    var inputs,
+			form;
+		
+	    beforeEach(function() {
+	      instance = ReactDOM.render(React.createElement(CommentBox, {"url": "/api/comments", "pollInterval": 200000}), container);
+
+	      this.eventSpy = jasmine.createSpy();
+	      container.addEventListener("broadcast", this.eventSpy, false);
+	      inputs = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'input');
+	      form = TestUtils.findRenderedDOMComponentWithTag(instance, 'form')
+	    });
+
+	    afterEach(function() {
+	      container.removeEventListener("broadcast", this.eventSpy, false);
+	    });
+
+	    it("should send comment and retrieve a list", function() {
+	    	$.mockjax({
+	    		  url: "/api/comments",
+	    		  responseText: AjaxResponses.submit.success
+	    		});
+	    	
+	    	var name = inputs[0],
+		    	text = inputs[1];
+	    	
+	    	TestUtils.Simulate.change(name, { target: { value: 'Lao Tzu' } });
+	    	TestUtils.Simulate.change(text, { target: { value: 'The journey of a thousand miles begins with one step' } });
+	    	
+	    	TestUtils.Simulate.submit(form);
+	    	var comments = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'comment');
+	    	expect(comments.length).toBe(1);
+	    	expect(TestUtils.findRenderedDOMComponentWithClass(instance, 'commentAuthor').textContent).toBe('Lao Tzu');
+	    	expect(TestUtils.findRenderedDOMComponentWithTag(instance, 'span').textContent.trim()).toBe('The journey of a thousand miles begins with one step');
+	    });
+	  });
+	  
+});

+ 8 - 0
src/test/resources/static/js/fake.spec.js

@@ -0,0 +1,8 @@
+describe('Fake', function() {
+    
+    describe('fake()', function() {
+    	it("contains spec with an expectation", function() {
+    	    expect(true).toBe(true);
+    	  });
+    });
+});