Added test suite in tutorial
diff --git a/lib/Korap/Plugin/KorapSearch.pm b/lib/Korap/Plugin/KorapSearch.pm
index a0fc4a4..46b6195 100644
--- a/lib/Korap/Plugin/KorapSearch.pm
+++ b/lib/Korap/Plugin/KorapSearch.pm
@@ -19,6 +19,12 @@
   ];
 };
 
+# Remove these attributes from stash after searching
+our @remove_from_stash =
+  qw/hits totalResults benchmark itemsPerPage error query/;
+
+
+# Register plugin
 sub register {
   my ($plugin, $mojo, $param) = @_;
   $param ||= {};
@@ -38,18 +44,23 @@
     }
   );
 
+  # TODO: Create a hash and either make these stash values
+  # or return the hash in case of non-blocking requests
   $mojo->helper(
     search => sub {
       my $c = shift;
 
-      # Todo: If there is no callback, return the hits object!
+      # If there is no callback, return the JSON object
       my $cb = pop if ref $_[-1] && ref $_[-1] eq 'CODE';
 
       my %param = @_;
 
-      # Test envronment
+      # Test environment / Fixtures - set via config!
       if ($c->app->mode eq 'test') {
 	state $json = decode_json(join(' ', <DATA>));
+
+	return $json unless $cb;
+
 	$c->stash('search.count' => 10);
 	$c->stash('search.startPage' => 1);
 	$c->stash('search.totalResults' => 666);
@@ -89,12 +100,17 @@
 
       my $url = Mojo::URL->new($api);
 
+      # Search in corpus
       if ($c->stash('corpus_id')) {
 	$url->path('corpus/' . $c->stash('corpus_id') . '/search');
       }
+
+      # search in collection
       elsif ($c->stash('collection_id')) {
 	$url->path('virtualcollection/' . $c->stash('collection_id') . '/search');
       }
+
+      # Just search
       else {
 	$url->path('search');
       };
@@ -107,7 +123,7 @@
       #};
 
       my %query = (q => $query);
-      $query{ql} = scalar $c->param('ql') // 'poliqarp';
+      $query{ql} = $param{ql} // scalar $c->param('ql') // 'poliqarp';
       $query{count} = $count if $count;
       $query{cutoff} = 'true' if $cutoff;
 
@@ -117,37 +133,50 @@
       $url->query({context => 'paragraph'});
 
       # Check cache for total results
-      my $total_results = $c->chi->get('total-' . $cache_url);
-      if (defined $total_results) {
+      my $total_results;
+      if (
+	# TODO: This should also be a query parameter
+	!$param{no_cache} &&
+	  defined ($total_results = $c->chi->get('total-' . $cache_url))) {
 	$c->stash('search.totalResults' => $total_results);
 	$c->app->log->debug('Get total result from cache');
 
 	# Set cutoff unless already set
 	$url->query({cutoff => 'true'}) unless defined $cutoff;
       }
+
+      # No cache
       else {
 	$c->stash('search.totalResults' => 0);
       };
 
       $url->query({page => $start_page});
 
+      # This may be incorrect
       $c->stash('search.itemsPerPage' => $count);
 
-      $c->stash('search.apirequest' => $url->to_string);
+      # Only set this when on test port
+      if ($c->stash('test_port')) {
+	$c->stash('search.apirequest' => $url->to_string);
+      };
 
       my $ua = Mojo::UserAgent->new;
 
       # Set timeout to 2 minutes
-      # This may a bit too far for demo users
       $ua->inactivity_timeout(120);
 
+      $c->app->log->debug('Search for ' . $url->to_string);
+
       # Blocking request
       # TODO: Make non-blocking
       my $tx = $ua->get($url);
 
       if (my $e = $tx->error) {
-	$c->notify(error => ($e->{code} ? $e->{code} . ': ' : '') .
-		     $e->{message} . ' (remote)');
+	$c->notify(
+	  error =>
+	    ($e->{code} ? $e->{code} . ': ' : '') .
+	      $e->{message} . ' (remote)'
+        );
 	return;
       };
 
@@ -157,6 +186,7 @@
 	$c->stash('search.apiresponse' => $res->body);
 
 	my $json = $res->json;
+	return $json unless $cb;
 
 	# Reformat benchmark counter
 	my $benchmark = $json->{benchmark};
@@ -193,6 +223,7 @@
 
       # Request failed
       else {
+	return unless $cb;
 	$plugin->_notify_on_error($c, 1, $tx->res);
       };
 
@@ -200,9 +231,10 @@
       my $v = $cb->();
 
       # Delete useless stash keys
-      foreach (qw/hits totalResults benchmark itemsPerPage error query/) {
+      foreach (@remove_from_stash) {
 	delete $c->stash->{'search.' . $_};
       };
+
       return $v;
     }
   );
diff --git a/lib/Korap/Plugin/KorapTagHelpers.pm b/lib/Korap/Plugin/KorapTagHelpers.pm
index 6ce4af2..8ceb679 100644
--- a/lib/Korap/Plugin/KorapTagHelpers.pm
+++ b/lib/Korap/Plugin/KorapTagHelpers.pm
@@ -1,38 +1,158 @@
 package Korap::Plugin::KorapTagHelpers;
 use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::JSON::Pointer;
 use Mojo::ByteStream 'b';
 use Mojo::Util qw/xml_escape/;
 
 sub register {
   my ($plugin, $mojo) = @_;
 
+  # Create helper for queries in the tutorial
   $mojo->helper(
     korap_tut_query => sub {
       my ($c, $ql, $q, %param) = @_;
       my $onclick = 'top.useQuery(this)';
+
+      my ($fail, $pass, @report) = (0,0);
+
+      if ($c->param('testing')) {
+	$c->res->headers->cache_control('max-age=1, no-cache');
+      };
+
+      # Store current tutorial position
       if ($c->param('embedded')) {
 	$onclick = 'setTutorialPage(this);' . $onclick;
+      }
+
+      # Tutorial wasn't embedded - but opened for testing
+      elsif ($c->param('testing') &&
+	       ($c->stash('test_port') || ($c->app->mode eq 'development')) &&
+		 $param{tests}) {
+
+	my $tests = $param{tests} // [];
+	my $json = $c->search(
+	  query => $q,
+	  ql => $ql,
+	  cutoff => 'true',
+	  no_cache => 1
+	);
+
+	# There is a response
+	if ($json) {
+	  my $json_pointer = Mojo::JSON::Pointer->new($json);
+	  foreach my $test (@$tests) {
+	    my ($type, $path, @rest) = @$test;
+
+	    # Check for equality
+	    if ($type eq 'is') {
+	      my $found = $json_pointer->get($path);
+	      if ($found && $found eq $rest[0]) {
+		$pass++;
+	      }
+	      else {
+		my $result = $path . q! isn't ! . shift @rest;
+		$result .= ' but was ' . $found if $found;
+		$result .= '; ' . join('; ', @rest);
+		push(@report, $result);
+		$fail++;
+	      };
+	    }
+
+	    # Check for inequality
+	    elsif ($type eq 'isnt') {
+	      if ($json_pointer->get($path) ne $rest[0]) {
+		$pass++;
+	      }
+	      else {
+		push(@report, $path . q! is ! . join('; ', @rest));
+		$fail++;
+	      };
+	    }
+
+	    # Check for existence
+	    elsif ($type eq 'ok') {
+	      if ($json_pointer->contains($path)) {
+		$pass++;
+	      }
+	      else {
+		push(@report, $path . q! doesn't exist; ! . join('; ', @rest));
+		$fail++;
+	      };
+	    }
+
+	    # Check for inexistence
+	    elsif ($type eq 'not_ok') {
+	      unless ($json_pointer->contains($path)) {
+		$pass++;
+	      }
+	      else {
+		push(@report, $path . q! doesn't exist; ! . join('; ', @rest));
+		$fail++;
+	      };
+	    };
+	  };
+	}
+	else {
+	  # There may be notifications here!
+	  $fail++ foreach @$tests;
+	};
+
+	# Emit hook to possible subscribers
+	# This is used for self-testing
+	# $plugin->emit_hook(korap_tut_query => (
+	#   query_language => $ql,
+	#   query => $q,
+	#   %param
+	# ));
       };
+
+      # Escape query for html embedding
       $q = xml_escape $q;
+
+      # There is something to talk about
+      my $msg = '';
+      if (($pass + $fail) > 0) {
+	$msg .= '<div class="test">';
+	$msg .= qq!<p class="pass">Pass: $pass</p>!;
+	$msg .= qq!<p class="fail">Fail: $fail</p>! if $fail;
+	foreach (@report) {
+	  $msg .= qq!<p class="fail">${_}</p>!;
+	};
+	$msg .= '</div>';
+      };
+
+      # Return tag
       b('<pre class="query tutorial" onclick="' . $onclick . '" ' .
 	  qq!data-query="$q" data-query-cutoff="! .
 	    ($param{cutoff} ? 1 : 0) .
 	      '"' .
 		qq! data-query-language="$ql"><code>! .
-		  $q . '</code></pre>');
+		  $q .
+		    '</code></pre>' . $msg);
     }
   );
 
+
+  # Create links in the tutorial that make sure the current position is preserved,
+  # in case the tutorial was opened embedded
   $mojo->helper(
     korap_tut_link_to => sub {
       my $c = shift;
       my $title = shift;
       my $link = shift;
       my $url = Mojo::URL->new($link);
+
+      # Link is part of the embedded tutorial
       if ($c->param('embedded')) {
 	$url->query({ embedded => 1 });
-	return $c->link_to($title, $url, onclick => 'setTutorialPage("' . $url . '")');
+	return $c->link_to(
+	  $title,
+	  $url,
+	  onclick => qq!setTutorialPage("$url")!
+	);
       };
+
+      # Build link
       return $c->link_to($title, $url);
     }
   );
diff --git a/public/sass/tutorial.scss b/public/sass/tutorial.scss
index 9ce65e0..5985821 100644
--- a/public/sass/tutorial.scss
+++ b/public/sass/tutorial.scss
@@ -48,3 +48,20 @@
   }
 }
 
+div.test {
+  display: block;
+  border-left: 10px solid $dark-green;
+  margin: 1em;
+  padding-left: 5px;
+  p {
+    color: black;
+    &.fail {
+      font-weight: bold;
+      color: red;
+    }
+    &.pass {
+      font-weight: bold;
+      color: green;
+    }
+  }
+}
diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep
index a35bbab..ac376e3 100644
--- a/templates/layouts/default.html.ep
+++ b/templates/layouts/default.html.ep
@@ -4,7 +4,9 @@
   <body>
 
 %# Background-image
-<% unless (param('q')) { %><div id="crab-bg"></div><% } %>
+% if (!param('q') && current_route eq 'index') {
+<div id="crab-bg"></div>
+% };
 
 % my $location;
 % my $search_route;
diff --git a/templates/tutorial/index.html.ep b/templates/tutorial/index.html.ep
index 9d8e048..a2bbaa6 100644
--- a/templates/tutorial/index.html.ep
+++ b/templates/tutorial/index.html.ep
@@ -34,32 +34,39 @@
 <section id="tut-examples">
 
 <h3>Example Queries</h3>
-%# <p>This is a Tutorial to KorAP. For examples click on the queries below:</p>
+
+%# Tests:
+%# [is => 'json_pointer', 'result']
+%# [ok => 'json_pointer']
+%# [isnt => 'json_pointer', 'result']
+%# [not_ok => 'json_pointer']
 
 <p><strong>Poliqarp</strong>: Find all occurrences of the lemma &quot;baum&quot; as annotated by the <%= korap_tut_link_to 'default foundry', '/tutorial/foundries' %>.</p>
-%= korap_tut_query poliqarp => '[base=Baum]'
+%= korap_tut_query poliqarp => '[base=Baum]', 'tests' => [[is => '/query', 'tokens:tt/l:Baum'],[is => '/request/query/wrap/layer', 'lemma'],[is => '/request/query/wrap/foundry', 'tt'], [ok => '/matches/10']]
 
 <p><strong>Poliqarp</strong>: Find all sequences of adjectives as annotated by Treetagger, that are repeated 3 to 5 times in a row.</p>
-%= korap_tut_query poliqarp => '[tt/p=ADJA]{3,5}'
+%= korap_tut_query poliqarp => '[tt/p=ADJA]{3,5}', 'tests' => [[is => '/query', 'spanRepetition(tokens:tt/p:ADJA{3,5})'], [is => '/request/query/operation', 'operation:repetition'],[is => '/request/query/operands/0/wrap/foundry', 'tt'], [ok => '/matches/5']]
 
 <p><strong>Cosmas-II</strong>: Find all occurrences of the words &quot;der&quot; and &quot;Baum&quot;, in case they are in a maximum distance of 5 tokens. The order is not relevant.</p>
-%= korap_tut_query cosmas2 => 'der /w5 Baum'
+%= korap_tut_query cosmas2 => 'der /w5 Baum', 'tests' => [[is => '/query', 'shrink(129: spanDistance({129: tokens:s:der}, {129: tokens:s:Baum}, [(w[0:5], notOrdered, notExcluded)]))'], [is => '/request/query/operation', 'operation:focus'], [is => '/request/query/@type', 'korap:reference'],[is => '/request/query/operands/0/operands/1/operation', 'operation:class'], [is => '/itemsPerPage', 25], [ok => '/matches/20'], [is => '/matches/4/corpusID', 'WPD'], [is => '/matches/12/corpusID', 'WPD']]
 
-<p><strong>Poliqarp+</strong>: Find all nominal phrases as annotated using Connexor, that contain an adverb as annotated by OpenNLP, that is annotated as something starting with an &quot;A&quot; in Treetagger.</p>
-%= korap_tut_query poliqarp => 'contains(<cnx/c=np>,{[opennlp/p=ADV & tt/p="A.*"]})', cutoff => 1
+<p><strong>Cosmas-II</strong>: Find all sequences of a word starting with a &quot;d&quot; (using a wildcard) followed by an adjective as annotated in the mate foundry, followed by the word &quot;Baum&quot; (ignore the case), that is in a sentence element annotated by the <%= korap_tut_link_to 'default foundry', '/tutorial/foundries' %>.</p>
+%= korap_tut_query cosmas2 => 'd* MORPH(mate/p=ADJA) $Baum #IN #ELEM(s)', 'tests' => [[ok => '/matches/3'], [is => '/query', 'shrink(130: {131: spanContain({129: <tokens:s />}, {130: spanNext(spanNext(SpanMultiTermQueryWrapper(tokens:s:d*), tokens:mate/p:ADJA), tokens:i:baum)})})'], [is => '/request/query/@type', 'korap:reference'], [is => '/request/query/operation', 'operation:focus'], [is => '/request/query/operands/0/operands/0/operation', 'operation:position'], [is => '/request/query/operands/0/operands/0/operands/1/operands/0/operation', 'operation:sequence'], [is => '/request/query/operands/0/operands/0/operands/1/operands/0/operands/0/wrap/type', 'type:wildcard'], [is => '/request/query/operands/0/operands/0/operands/1/operands/0/operands/1/wrap/key', 'ADJA'], [is => '/request/query/operands/0/operands/0/operands/1/operands/0/operands/1/wrap/foundry', 'mate'], [ok => '/request/query/operands/0/operands/0/operands/1/operands/0/operands/2/wrap/caseInsensitive'], [ok => '/matches/5']]
+
+<p><strong>Poliqarp+</strong>: Find all nominal phrases as annotated using Connexor, that contain an adverb as annotated by OpenNLP, that is annotated as something starting with an &quot;A&quot; using regular expressions in Treetagger.</p>
+%= korap_tut_query poliqarp => 'contains(<cnx/c=np>,{[opennlp/p=ADV & tt/p="A.*"]})', cutoff => 1, 'tests' => [[is => '/query', 'spanContain(<tokens:cnx/c:np />, {1: spanSegment(tokens:opennlp/p:ADV, SpanMultiTermQueryWrapper(tokens:/tt/p:A.*/))})'], [is => '/request/query/operation', 'operation:position'], [is => '/request/query/frames/0', 'frames:contains'], [is => '/request/query/operands/0/foundry', 'cnx'], [is => '/request/query/operands/0/layer', 'c'], [is => '/request/query/operands/0/foundry', 'cnx'], [is => '/request/query/operands/0/key', 'np'], [is => '/request/query/operands/1/operands/0/wrap/operands/0/foundry', 'opennlp'], [is => '/request/query/operands/1/operands/0/wrap/operands/0/layer', 'p'],  [is => '/request/query/operands/1/operands/0/wrap/operands/1/foundry', 'tt'],  [is => '/request/query/operands/1/operands/0/wrap/operands/1/type', 'type:regex'], [is => '/request/query/operands/1/operands/0/wrap/operands/1/key', 'A.*'], [ok => '/matches/5']]
 
 <p><strong>Poliqarp+</strong>: Find all sentences as annotated by the base foundry that start with a sequence of one token in present tense as annotated by Connexor and the lemma &quot;die&quot; annotated by the <%= korap_tut_link_to 'default foundry', '/tutorial/foundries' %>. Highlight both terms of the sequence.</p>
-%= korap_tut_query poliqarp => 'startswith(<s>, {1:[cnx/m=PRES]}{2:[base=die]})', cutoff => 1
+%= korap_tut_query poliqarp => 'startswith(<s>, {1:[cnx/m=PRES]}{2:[base=die]})', cutoff => 1, tests => [[is => '/query', 'spanStartsWith(<tokens:s />, spanNext({1: tokens:cnx/m:PRES}, {2: tokens:tt/l:die}))'], [is => '/request/meta/startPage', 1], [is => '/request/query/operation', 'operation:position'], [is => '/request/query/operands/0/@type','korap:span'], [is => '/request/query/operands/1/operands/0/operation', 'operation:class'], [is => '/request/query/operands/1/operands/1/operation', 'operation:class'], [is => '/request/query/operands/1/operands/1/operands/0/wrap/foundry', 'tt'], [ok => '/matches/4']]
 
 <p><strong>Poliqarp+</strong>: Find all sequences of an article, followed by three to four adjectives and a noun as annotated by the Treetagger foundry, that finish a sentence. Highlight all parts of the sequence.</p>
-%= korap_tut_query poliqarp => 'focus(3:endswith(<s>,{3:[tt/p=ART]{1:{2:[tt/p=ADJA]{3,4}}[tt/p=NN]}}))', cutoff => 1
+%= korap_tut_query poliqarp => 'focus(3:endswith(<s>,{3:[tt/p=ART]{1:{2:[tt/p=ADJA]{3,4}}[tt/p=NN]}}))', cutoff => 1, 'tests' => [[is => '/query', 'shrink(3: spanEndsWith(<tokens:s />, {3: spanNext(tokens:tt/p:ART, {1: spanNext({2: spanRepetition(tokens:tt/p:ADJA{3,4})}, tokens:tt/p:NN)})}))'], [is => '/request/query/operation', 'operation:focus'], [is => '/request/query/operands/0/frames/0', 'frames:endswith'], [ok => '/matches/3'], [is => '/matches/4/corpusID', 'WPD']]
 
 <p><strong>Annis</strong>: Find all occurrences of the sequence of two tokens annotated as adverbs by the <%= korap_tut_link_to 'default foundry', '/tutorial/foundries' %>.</p>
-%= korap_tut_query annis => 'pos="ADV" & pos="ADV" & #1 . #2'
+%= korap_tut_query annis => 'pos="ADV" & pos="ADV" & #1 . #2', 'tests' => [[is => '/query', 'spanNext(tokens:tt/p:ADV, tokens:tt/p:ADV)'], [is => '/request/query/operands/0/wrap/foundry', 'tt'], [is => '/request/query/operands/1/wrap/foundry', 'tt'], [ok => '/matches/5'], [ok => '/matches/15'], [is => '/matches/15/corpusID', 'WPD']]
 
 <p><strong>CQL</strong>: Find all occurrences of the sequence &quot;der alte Mann&quot;.</p>
-%= korap_tut_query cql => '"der alte Mann"'
-
+%= korap_tut_query cql => '"der alte Mann"', 'tests' => [[is => '/query', 'spanNext(spanNext(tokens:s:der, tokens:s:alte), tokens:s:Mann)'], [is => '/request/query/operation', 'operation:sequence'],[is => '/request/query/operands/0/wrap/key', 'der'],[is => '/request/query/operands/1/wrap/key', 'alte'],[is => '/request/query/operands/2/wrap/key', 'Mann'], [ok => '/matches/5'], [ok => '/matches/5']]
 
 </section>