Skip to content

Commit

Permalink
fix yancy editor for new yancy controller
Browse files Browse the repository at this point in the history
Changing over to the Yancy::Controller::Yancy controller for the editor
caused some bugs in the editor itself: The `create` API route returns
the new item, not just the ID of the new item.

Additionally, the editor application was not setting the correct
`Content-Type` header for the request, so we try a little harder to get
JSON in the controller (which may come back to bite me, but we'll try it
and see...)

We also have now started building Selenium-based tests for the JS
application. These will be filled in more as regressions need testing,
new features get added, and proper documentation is added.

Thanks [email protected]#mojo for the report.
  • Loading branch information
preaction committed Jun 6, 2019
1 parent 9ba6ecf commit 81b8e5a
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 47 deletions.
48 changes: 11 additions & 37 deletions lib/Mojolicious/Plugin/Yancy/resources/public/yancy/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
Vue.component('edit-field', {
template: '#edit-field',
props: {
name: {
required: true
},
value: {
required: true
},
Expand Down Expand Up @@ -515,7 +518,8 @@ var app = new Vue({
url: url,
method: 'PUT',
data: value,
dataType: "json"
dataType: "json",
contentType: "application/json"
}
).done(
function ( data, status, jqXHR ) {
Expand Down Expand Up @@ -548,38 +552,15 @@ var app = new Vue({
url: url,
method: 'POST',
data: value,
dataType: "json"
dataType: "json",
contentType: "application/json"
}
).done(
function ( data, status, jqXHR ) {
var id = self.currentOperations['add'].schema['x-id-field'] || 'id';
var urlParams = {};
urlParams[ id ] = data;

$.ajax(
{
url: self.fillUrl( self.currentOperations['get'].url, urlParams ),
method: 'GET',
dataType: 'json'
}
).done(
function ( data, status, jqXHR ) {
self.items.unshift( data );
self.total++;
self.cancelAddItem();
self.addToast( { icon: "fa-save", title: "Added", text: "Item added" } );
}
).fail(
function ( jqXHR, textStatus, errorThrown ) {
if ( jqXHR.responseJSON ) {
self.parseErrorResponse( jqXHR.responseJSON );
self.$set( self.error, 'addItem', 'Could not fetch new item' );
}
else {
self.$set( self.error, 'addItem', jqXHR.responseText );
}
}
);
self.items.unshift( data );
self.total++;
self.cancelAddItem();
self.addToast( { icon: "fa-save", title: "Added", text: "Item added" } );
}
).fail(
function ( jqXHR, textStatus, errorThrown ) {
Expand Down Expand Up @@ -623,13 +604,6 @@ var app = new Vue({
= marked( copy[k], { sanitize: false });
}
}
else if ( copy[k] === null ) {
// `null` doesn't pass type checks, and Perl doesn't
// distinguish between `undef` and `defined but no
// value` in an object, so make this field `undef`
// to Perl
delete copy[k];
}
}
return JSON.stringify( copy );
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
Schema
</h6>

<ul class="nav flex-column">
<ul id="sidebar-schema-list" class="nav flex-column">
<!-- schema list -->
<li v-for="( val, key ) in schema" class="nav-item">
<a href="#" @click.prevent="setSchema( key )"
Expand Down Expand Up @@ -236,7 +236,7 @@
<button @click="newFilter = { }" class="btn btn-outline-secondary mr-1">
Add Filter
</button>
<button @click="showAddItem()" class="btn btn-outline-secondary ml-auto"
<button @click="showAddItem()" id="add-item-btn" class="btn btn-outline-secondary ml-auto"
:class="{active:addingItem}"
>
Add Item
Expand All @@ -248,7 +248,7 @@
<h4 class="alert-heading">Error Adding Item</h4>
<p class="mb-0">Error from server: {{ error.addItem }}</p>
</div>
<item-form v-model="newItem"
<item-form id="new-item-form" v-model="newItem"
:schema="currentOperations['add'].schema"
@close="cancelAddItem" @input="addItem"
:error="formError"
Expand All @@ -257,7 +257,7 @@
</div>

<div class="filters mt-1">
<form class="form-inline" v-if="newFilter"
<form id="filter-form" class="form-inline" v-if="newFilter"
@submit.prevent="addFilter()"
>
<select class="custom-select col-md-4 col-lg-4" v-model="newFilter.field">
Expand Down Expand Up @@ -438,7 +438,7 @@
{{ conf.title }} {{ isRequired( conf.name ) ? '*' : '' }}
</label>
<edit-field v-model="$data._value[conf.name]"
:example="example[conf.name]" :schema="conf"
:name="conf.name" :example="example[conf.name]" :schema="conf"
:required="isRequired( conf.name )" :valid="!error[conf.name]"
:aria-labelledby="'field-' + conf.name + '-desc-' + _uid"
/></edit-field>
Expand All @@ -459,6 +459,7 @@
<template id="edit-field">
<div :class="!valid ? 'is-invalid' : ''">
<input v-if="fieldType != 'select' && fieldType != 'checkbox' && fieldType != 'markdown' && fieldType != 'textarea'"
:name="name"
:type="fieldType" :pattern="pattern" :required="required"
:inputmode="inputMode"
:minlength="minlength" :maxlength="maxlength"
Expand All @@ -469,10 +470,11 @@
:class="!valid ? 'is-invalid' : ''"
/>
<textarea v-if="fieldType == 'textarea'" :required="required"
:disabled="readonly" v-model="$data._value" @change="input"
:class="!valid ? 'is-invalid' : ''" rows="5"></textarea>
:name="name" :disabled="readonly" v-model="$data._value"
@change="input" :class="!valid ? 'is-invalid' : ''"
rows="5"></textarea>
<select v-if="fieldType == 'select'"
:required="required" :disabled="readonly"
:name="name" :required="required" :disabled="readonly"
v-model="$data._value" @change="input"
class="custom-select"
:class="!valid ? 'is-invalid' : ''"
Expand Down
3 changes: 1 addition & 2 deletions lib/Yancy/Controller/Yancy.pm
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,7 @@ sub set {
return;
}

my $data = $c->req->headers->content_type eq 'application/json'
? $c->req->json : $c->req->params->to_hash;
my $data = eval { $c->req->json } || $c->req->params->to_hash;
delete $data->{csrf_token};
#; use Data::Dumper;
#; $c->app->log->debug( Dumper $data );
Expand Down
2 changes: 2 additions & 0 deletions t/selenium/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Ignore screenshots
*.png
91 changes: 91 additions & 0 deletions t/selenium/editor.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@

=head1 DESCRIPTION
This tests the Yancy editor application JavaScript.
To run this test, you must install Test::Mojo::Role::Selenium and
Selenium::Chrome. Then you must set the C<TEST_SELENIUM> environment
variable to C<1>.
Additionally, setting C<YANCY_SELENIUM_CAPTURE=1> in the environment
will add screenshots to the C<t/selenium> directory. Each screenshot
begins with a counter so you can see the application state as the test
runs.
=head2 NOTES
The editor item form uses focus/blur events to update the data, so
using C<submit_ok> does not work correctly. Using
C<send_keys_ok( undef, \'return' )> works, and also tests that the
submit event is properly handled.
=cut

use Mojo::Base -strict;
use Test::More;
use Test::Mojo;
use FindBin qw( $Bin );
use Mojo::File qw( path );
use lib "".path( $Bin, '..', 'lib' );
use Local::Test qw( init_backend );
use Mojolicious;

BEGIN {
eval { require Test::Mojo::Role::Selenium; 1 }
or plan skip_all => 'Test::Mojo::Role::Selenium required to run this test';
};

$ENV{MOJO_SELENIUM_DRIVER} ||= 'Selenium::Chrome';
$ENV{YANCY_SELENIUM_CAPTURE} ||= 0; # Disable screenshots by default

my $schema = \%Yancy::Backend::Test::SCHEMA;
my ( $backend_url, $backend, %items ) = init_backend( $schema );
my $app = Mojolicious->new;
$app->log->level( 'debug' )->handle( \*STDERR )
->format( sub {
my ( $time, $level, @lines ) = @_;
return sprintf '# [%s] %s', $level, join "\n", @lines, "";
} );
$app->plugin( 'Yancy', {
backend => $backend_url,
read_schema => 1,
} );

my $t = Test::Mojo->with_roles("+Selenium")->new( $app )->setup_or_skip_all;
$t->navigate_ok("/yancy")
->screenshot_directory( $Bin )
->status_is(200)
->wait_for( '#sidebar-schema-list' )
->click_ok( '#sidebar-schema-list li:nth-child(2) a' )
->wait_for( 'table' )
->main::capture( 'after-people-clicked' )
->click_ok( '#add-item-btn' )
->main::capture( 'after-new-item-clicked' )
->send_keys_ok( '#new-item-form [name=name]', 'Doug Bell' )
->send_keys_ok( '#new-item-form [name=email]', '[email protected]' )
->send_keys_ok( undef, \'return' )
->wait_for( '.toast, .alert', 'save toast banner or error' )
->main::capture( 'new-item-added' )
->live_text_like( 'tbody tr:nth-child(1) td:nth-child(2)', qr{^\d+$}, 'id is a number' )
->live_text_is( 'tbody tr:nth-child(1) td:nth-child(3)', 'Doug Bell', 'name is correct' )
;

#=head2 capture
#
# $t->main::capture( $name )
#
# Capture a screenshow and increment a counter so we can see the
# screenshots in order to help debugging.
#
# This sub only runs if the YANCY_SELENIUM_CAPTURE environment variable
# is set.
sub capture {
my ( $t, $name ) = @_;
return $t unless $ENV{YANCY_SELENIUM_CAPTURE};
state $i = 1;
$t->capture_screenshot( sprintf '%03d-%s', $i, $name );
$i++;
return $t;
}

done_testing;

0 comments on commit 81b8e5a

Please sign in to comment.