Skip to content

Commit

Permalink
before week 3, initial Grails artefacts
Browse files Browse the repository at this point in the history
  • Loading branch information
Dierk Koenig committed Oct 3, 2023
1 parent 42a2c5e commit 88a9047
Show file tree
Hide file tree
Showing 15 changed files with 417 additions and 1 deletion.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ dependencies {
implementation "org.grails.plugins:events"
implementation "org.grails.plugins:gsp"
profile "org.grails.profiles:web"
runtimeOnly "org.glassfish.web:el-impl:2.2.1-b05"
// runtimeOnly "org.glassfish.web:el-impl:2.2.1-b05" // disabled because of vulnerability
runtimeOnly "com.h2database:h2"
runtimeOnly "org.apache.tomcat:tomcat-jdbc"
runtimeOnly "javax.xml.bind:jaxb-api:2.3.1"
Expand Down
88 changes: 88 additions & 0 deletions docs/week03/README-3-MVC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# WebEngineering Module, rooms

## Goals

### Abilities
- Writing tests for page navigation
- Writing tests for form submission
- Implementing basic workflow: form - controller - view
- Constructing html views with derived content
- Validating appropriately

### Knowledge
- Understanding the purpose and benefit of functional tests on the web
- Understanding the purpose and benefit of unit tests
- Understanding the web rooms cycle, request-response paradigm
- Using models for request data binding and response view creation
- Where and how to validate

## Assignment 1

Make sure that you have a Java JDK 17 installed and `JAVA_HOME`
set appropriately. Check by running

java -version

Run the tests

grailsw test-app

_this will take a while to download the first time_

Run the application

grailsw run-app

Browse to http://localhost:8080/static/Home.html .

## Assignment 2

Have a look at rooms/src/integration-test/groovy/rooms/HomeSecondSpec.groovy .

Write a test, that goes to http://www.fhnw.ch/de/
and clicks on a link with text "Standorte und Kontakt".
Validate the page title.

## Assignment 3

Have a look at
- http://localhost:8080/static/GradeCalculator.html
- rooms/src/integration-test/groovy/rooms/CalculatorSpec.groovy (note the commented line 26)
- rooms/src/main/resources/public/GradeCalculator.html
- rooms/grails-app/controllers/rooms/CalculatorController.groovy
- rooms/views/calculator/CalculatorOutput.gsp (note the output placeholder)
- rooms/src/test/groovy/rooms/CalculatorControllerSpec.groovy

Uncomment line 26 in the integration test and run `grailsw test-app`.

Open `/build/reports/tests/index.html` (or look at the internal test runner)
to see which test failed and why.

Use `${ result }` in CalculatorOutput.gsp to put that calculated result in the right place.

## Assignment 4

In the GradeCalculator:
what happens when _en_ or _exam_ do not represent numbers?

Extend the integration test to cover the invalid input scenario.

## Assignment 5

What happens when _en_ or _exam_ do not fall into 1.0 - 6.0?

Write down how you would address this issue.
Unit test or integration test?
Which code needs change: test, controller, view?


## Homework

Take the InPlaceCalculator and modify the current solution so that
- when the "en" field does not validate,
an error message is displayed right beneath the field and
the en input field itself is marked with a red border
- when the "exam" field does not validate,
an error message is displayed right beneath the field and
the exam input field itself is marked with a red border
- note that both, en and exam, may be erroneous at the same time.
9 changes: 9 additions & 0 deletions grails-app/controllers/rooms/CalculatorController.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package rooms

class CalculatorController {

def calc(double en, double exam) {
double result = (en + exam) / 2
render view: "CalculatorOutput", model: [result: result]
}
}
29 changes: 29 additions & 0 deletions grails-app/controllers/rooms/InPlaceCalculatorController.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package rooms

import grails.validation.Validateable

class InPlaceCalculatorController {

def calc(CalculatorModel calcModel) {
calcModel.en = Math.round(calcModel.en * 10) / 10
calcModel.exam = Math.round(calcModel.exam * 10) / 10
calcModel.result = Math.round((calcModel.en + calcModel.exam) / 2)
if (calcModel.hasErrors()) {
calcModel.result = "Cannot calculate. Input data was invalid."
}
render view:'calc', model: [calculatorInstance: calcModel]
}

}

class CalculatorModel implements Validateable {
double en = 0.0
double exam = 0.0
String result = ""

static constraints = {
en min:1d, max:6d, scale:1
exam min:1d, max:6d, scale:1
result nullable:true
}
}
19 changes: 19 additions & 0 deletions grails-app/taglib/rooms/DecorationTagLib.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package rooms

class DecorationTagLib {
static defaultEncodeAs = 'raw'
static namespace = "rooms"

def decorate = { attributes, body ->
String grade = attributes.grade
def decor = "<img src='http://2.bp.blogspot.com/";
switch (grade) {
case ["1","2","3"] : decor += "-rnfZUujszZI/UZEFYJ269-I/AAAAAAAADnw/BbB-v_QWo1w/s1600/facebook-frown-emoticon.png'>"; break
case ["4","5","6"] : decor += "-qODY1kxipZ0/Tv5dwDFFntI/AAAAAAAAAjM/cLXT6KEp-bE/s400/sunglasses%2Bemoticon.png'>"; break
default: decor = ""
}
out << decor
out << body()
out << decor
}
}
13 changes: 13 additions & 0 deletions grails-app/utils/rooms/FieldUtil.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package rooms

import grails.validation.Validateable
import org.springframework.validation.FieldError

class FieldUtil {
static boolean hasError(Validateable model, String propertyName) {
null != findError(model, propertyName)
}
static FieldError findError(Validateable model, String propertyName) {
model.errors.fieldErrors.find {it.field == propertyName}
}
}
15 changes: 15 additions & 0 deletions grails-app/views/calculator/CalculatorOutput.gsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<title>
Average
</title>
</head>
<body>

<p> Your average is <output>placeholder goes here</output>.</p>

<p> Back to the <a href="/static/GradeCalculator.html">calculator</a>.</p>

</body>
</html>
20 changes: 20 additions & 0 deletions grails-app/views/inPlaceCalculator/_form_row.gsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<%--
Emitting a single row of a form with label an input field.
Params:
name : name to use for id, name, labelFor
label : what the user sees beneath the input field
model : the values to show and error information
Depends on the "validate" function from outside.
--%>

<%@ page import="static rooms.FieldUtil.*" %>

<div>
<label for='${name}'>${label}</label>
<input type="number decimal" name="${name}" value="${model.getProperty(name)}"
required="true" min="1.0" max="6.0" id="${name}"
class="${hasError(model, name) ? 'error' : ''}"
title="${g.message(error: findError(model, name)) }"

/>
</div>
37 changes: 37 additions & 0 deletions grails-app/views/inPlaceCalculator/calc.gsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="form"/>
<title>
In-Place Calculator
</title>
</head>

<body>

<form action="/inPlaceCalculator/calc" method="get">
<fieldset class="form padded">

<tmpl:form_row name="en" label="En" model="${calculatorInstance}" />

<tmpl:form_row name="exam" label="Exam" model="${calculatorInstance}" />

<div>
<label>&nbsp;</label>
<input type="submit" value="Calculate"/>
</div>
</fieldset>
</form>


<div class="padded">
<label>Result</label>
<rooms:decorate grade="${calculatorInstance.result}">
<output>${calculatorInstance.result}</output>
</rooms:decorate>
</div>


</body>
</html>

36 changes: 36 additions & 0 deletions src/integration-test/groovy/rooms/CalculatorSpec.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package rooms

import geb.spock.GebSpec
import grails.testing.mixin.integration.Integration
import spock.lang.Ignore

/**
* See http://www.gebish.org/manual/current/ for more instructions
*/
@Integration
@Ignore
class CalculatorSpec extends GebSpec {


void "Basic calculation"() {
when:
go '/static/GradeCalculator.html'
then:
title == "Grade Calculator"

when: "set valid input"
$("form").en = 5.0
$("form").exam = 6.0
$("input", type: "submit").click()

then: "Result Page is displayed"
title == "Average"
// $("output").text() == "5.5"


when: "click on back link"
$("a", text: "calculator").click()
then:
title == "Grade Calculator"
}
}
36 changes: 36 additions & 0 deletions src/integration-test/groovy/rooms/HomeSecondSpec.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package rooms

import geb.spock.GebSpec
import grails.testing.mixin.integration.Integration


/**
* See http://www.gebish.org/manual/current/ for more instructions
*/
@Integration
class HomeSecondSpec extends GebSpec {

void "test home is there"() {
when:"The home page is visited"
go '/static/Home.html'
then:"The title is Homepage"
title == "Homepage"
}

void "home links to Second and Second links back"() {
when:"The home page is visited"
go '/static/Home.html'
then:"The title is Homepage"
title == "Homepage"

when: "click on link to second"
$("a", text: "second").click()
then: "Second page is displayed"
title == "Second"

when: "click on back link"
$("a", text: "home").click()
then: "Home page is displayed, again"
title == "Homepage"
}
}
69 changes: 69 additions & 0 deletions src/integration-test/groovy/rooms/InPlaceCalculatorSpec.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package rooms

import geb.spock.GebSpec
import grails.testing.mixin.integration.Integration
import spock.lang.Ignore

/**
* See http://www.gebish.org/manual/current/ for more instructions
*/
@Integration
@Ignore
class InPlaceCalculatorSpec extends GebSpec {

void "Calculate in place with a self-refreshing view"() {
when: "Go to start GSP page by calling it disguised as HTML"
go '/InPlaceCalculator.html?lang=en'
then:
title == "In-Place Calculator"

when: "set valid input"
$("form").en = 5.0
$("form").exam = 6.0
$("input", type: "submit").click()

then: "Result is displayed with proper rounding up"
$("output").text() == "6"
}

void "Invalid input shows error message and sets error class"() {
when: "Go to start GSP page by calling it disguised as HTML"
go '/InPlaceCalculator.html?lang=en'
then:
title == "In-Place Calculator"

when: "set invalid input"
$("form").en = 0.9
$("form").exam = 3.0
$("input", type: "submit").click()

then: "Result contains error message"
$("output").text() == "Cannot calculate. Input data was invalid."
then: "invalid en field has error class while valid exam input has no class"
$("#en", class:'error')
$("#exam").attr('class') == ""
}

// TODO: un-comment the commented lines below and see them failing, then make them pass

void "Invalid input is handled in-place by JS without submission"() {
given: "a valid state"
go '/InPlaceCalculator.html?lang=en'
$("form").en = 3.0
$("form").exam = 3.0
when:
$("input", type: "submit").click()
then: "we should have a clean, valid state to start from"
$("#en").attr('class') == ""
when: "we enter some invalid value _without_ submitting"
// def message = withAlert {
$("form").en = 0.9
// }
then: "the in-place JS logic should kick in"
// $("#en").attr('class') == "error"
// message == "en value needs to be at least 1.0"
// $("#en").focused
}


}
Loading

0 comments on commit 88a9047

Please sign in to comment.