Tweaked some styles and started automated testing. Fixes #11.

This commit is contained in:
Dan Brown 2015-09-02 18:26:33 +01:00
parent 30d2edddda
commit 713827f941
23 changed files with 254 additions and 55 deletions

View File

@ -70,7 +70,7 @@ class BookController extends Controller
$book->updated_by = Auth::user()->id; $book->updated_by = Auth::user()->id;
$book->save(); $book->save();
Activity::add($book, 'book_create', $book->id); Activity::add($book, 'book_create', $book->id);
return redirect('/books'); return redirect($book->getUrl());
} }
/** /**

View File

@ -65,7 +65,7 @@ class ChapterController extends Controller
$chapter->updated_by = Auth::user()->id; $chapter->updated_by = Auth::user()->id;
$book->chapters()->save($chapter); $book->chapters()->save($chapter);
Activity::add($chapter, 'chapter_create', $book->id); Activity::add($chapter, 'chapter_create', $book->id);
return redirect($book->getUrl()); return redirect($chapter->getUrl());
} }
/** /**

View File

@ -35,6 +35,11 @@ class BookRepo
return $this->book->where('slug', '=', $slug)->first(); return $this->book->where('slug', '=', $slug)->first();
} }
/**
* Get a new book instance from request input.
* @param $input
* @return Book
*/
public function newFromInput($input) public function newFromInput($input)
{ {
return $this->book->fill($input); return $this->book->fill($input);

View File

@ -64,6 +64,18 @@ return [
'strict' => false, 'strict' => false,
], ],
'mysql_testing' => [
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'bookstack-test',
'username' => 'bookstack-test',
'password' => 'bookstack-test',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
],
'pgsql' => [ 'pgsql' => [
'driver' => 'pgsql', 'driver' => 'pgsql',
'host' => env('DB_HOST', 'localhost'), 'host' => env('DB_HOST', 'localhost'),

View File

@ -19,3 +19,24 @@ $factory->define(Oxbow\User::class, function ($faker) {
'remember_token' => str_random(10), 'remember_token' => str_random(10),
]; ];
}); });
$factory->define(Oxbow\Book::class, function ($faker) {
return [
'name' => $faker->sentence,
'description' => $faker->paragraph
];
});
$factory->define(Oxbow\Chapter::class, function ($faker) {
return [
'name' => $faker->sentence,
'description' => $faker->paragraph
];
});
$factory->define(Oxbow\Page::class, function ($faker) {
return [
'name' => $faker->sentence,
'html' => '<p>' . implode('</p>', $faker->paragraphs(5)) . '</p>'
];
});

View File

@ -24,5 +24,6 @@
<env name="CACHE_DRIVER" value="array"/> <env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/> <env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/> <env name="QUEUE_DRIVER" value="sync"/>
<env name="DB_CONNECTION" value="mysql_testing"/>
</php> </php>
</phpunit> </phpunit>

View File

@ -51,6 +51,17 @@ $button-border-radius: 2px;
} }
} }
.text-button {
@extend .link;
background-color: transparent;
padding: 0;
margin: 0;
border: none;
&:focus, &:active {
outline: 0;
}
}
.button-group { .button-group {
@include clearfix; @include clearfix;
.button, button[type="button"] { .button, button[type="button"] {

View File

@ -43,11 +43,13 @@ h1, h2, h3, h4 {
/* /*
* Link styling * Link styling
*/ */
a { a, .link {
color: $primary; color: $primary;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
transition: color ease-in-out 80ms; transition: color ease-in-out 80ms;
font-family: $text;
line-height: 1.6;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
color: darken($primary, 20%); color: darken($primary, 20%);

View File

@ -309,8 +309,10 @@ h1, h2, h3, h4, h5, h6 {
} }
.faded { .faded {
a { a, button, span {
color: #666; color: #666;
}
.text-button {
opacity: 0.5; opacity: 0.5;
transition: all ease-in-out 120ms; transition: all ease-in-out 120ms;
&:hover { &:hover {
@ -324,12 +326,9 @@ h1, h2, h3, h4, h5, h6 {
color: #000; color: #000;
font-size: 0.9em; font-size: 0.9em;
background-color: rgba(21, 101, 192, 0.15); background-color: rgba(21, 101, 192, 0.15);
a, span {
color: #000;
}
} }
.breadcrumbs a, .action-buttons a { .breadcrumbs .text-button, .action-buttons .text-button {
display: inline-block; display: inline-block;
padding: $-s; padding: $-s;
&:last-child { &:last-child {
@ -340,7 +339,7 @@ h1, h2, h3, h4, h5, h6 {
text-align: right; text-align: right;
&.text-left { &.text-left {
text-align: left; text-align: left;
a { .text-button {
padding-right: $-m; padding-right: $-m;
padding-left: 0; padding-left: 0;
} }

View File

@ -9,7 +9,7 @@
<div class="col-md-6 faded"> <div class="col-md-6 faded">
<div class="action-buttons"> <div class="action-buttons">
@if($currentUser->can('book-create')) @if($currentUser->can('book-create'))
<a href="/books/create" class="text-pos"><i class="zmdi zmdi-plus"></i>Add new book</a> <a href="/books/create" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a>
@endif @endif
</div> </div>
</div> </div>

View File

@ -8,17 +8,17 @@
<div class="col-md-12"> <div class="col-md-12">
<div class="action-buttons faded"> <div class="action-buttons faded">
@if($currentUser->can('page-create')) @if($currentUser->can('page-create'))
<a href="{{$book->getUrl() . '/page/create'}}" class="text-pos"><i class="zmdi zmdi-plus"></i> New Page</a> <a href="{{$book->getUrl() . '/page/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a>
@endif @endif
@if($currentUser->can('chapter-create')) @if($currentUser->can('chapter-create'))
<a href="{{$book->getUrl() . '/chapter/create'}}" class="text-pos"><i class="zmdi zmdi-plus"></i> New Chapter</a> <a href="{{$book->getUrl() . '/chapter/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a>
@endif @endif
@if($currentUser->can('book-update')) @if($currentUser->can('book-update'))
<a href="{{$book->getEditUrl()}}" class="text-primary"><i class="zmdi zmdi-edit"></i>Edit</a> <a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
<a href="{{ $book->getUrl() }}/sort" class="text-primary"><i class="zmdi zmdi-sort"></i>Sort</a> <a href="{{ $book->getUrl() }}/sort" class="text-primary text-button"><i class="zmdi zmdi-sort"></i>Sort</a>
@endif @endif
@if($currentUser->can('book-delete')) @if($currentUser->can('book-delete'))
<a href="{{ $book->getUrl() }}/delete" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a> <a href="{{ $book->getUrl() }}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
@endif @endif
</div> </div>
</div> </div>

View File

@ -13,5 +13,5 @@
<div class="form-group"> <div class="form-group">
<a href="{{ back()->getTargetUrl() }}" class="button muted">Cancel</a> <a href="{{ back()->getTargetUrl() }}" class="button muted">Cancel</a>
<button type="submit" class="button pos">Save</button> <button type="submit" class="button pos">Save Chapter</button>
</div> </div>

View File

@ -7,19 +7,19 @@
<div class="row"> <div class="row">
<div class="col-md-4 faded"> <div class="col-md-4 faded">
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="{{$book->getUrl()}}" class="text-book"><i class="zmdi zmdi-book"></i>{{ $book->name }}</a> <a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->name }}</a>
</div> </div>
</div> </div>
<div class="col-md-8 faded"> <div class="col-md-8 faded">
<div class="action-buttons"> <div class="action-buttons">
@if($currentUser->can('chapter-create')) @if($currentUser->can('chapter-create'))
<a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos"><i class="zmdi zmdi-plus"></i>New Page</a> <a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a>
@endif @endif
@if($currentUser->can('chapter-update')) @if($currentUser->can('chapter-update'))
<a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary"><i class="zmdi zmdi-edit"></i>Edit</a> <a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
@endif @endif
@if($currentUser->can('chapter-delete')) @if($currentUser->can('chapter-delete'))
<a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a> <a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
@endif @endif
</div> </div>
</div> </div>

View File

@ -9,13 +9,13 @@
<div class="row"> <div class="row">
<div class="col-md-4 faded"> <div class="col-md-4 faded">
<div class="action-buttons text-left"> <div class="action-buttons text-left">
<a onclick="$('body>header').slideToggle();" class="text-primary"><i class="zmdi zmdi-swap-vertical"></i>Toggle Header</a> <a onclick="$('body>header').slideToggle();" class="text-button text-primary"><i class="zmdi zmdi-swap-vertical"></i>Toggle Header</a>
</div> </div>
</div> </div>
<div class="col-md-8 faded"> <div class="col-md-8 faded">
<div class="action-buttons"> <div class="action-buttons">
<a href="{{ back()->getTargetUrl() }}" class="text-primary"><i class="zmdi zmdi-close"></i>Cancel</a> <a href="{{ back()->getTargetUrl() }}" class="text-button text-primary"><i class="zmdi zmdi-close"></i>Cancel</a>
<a onclick="$(this).submitForm();" type="submit" class="text-pos"><i class="zmdi zmdi-floppy"></i>Save Page</a> <button type="submit" class="text-button text-pos"><i class="zmdi zmdi-floppy"></i>Save Page</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -7,7 +7,7 @@
<div class="row"> <div class="row">
<div class="col-md-6 faded"> <div class="col-md-6 faded">
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="{{$page->getUrl()}}" class="text-primary"><i class="zmdi zmdi-arrow-left"></i>Back to page</a> <a href="{{$page->getUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-arrow-left"></i>Back to page</a>
</div> </div>
</div> </div>
<div class="col-md-6 faded"> <div class="col-md-6 faded">

View File

@ -7,10 +7,10 @@
<div class="row"> <div class="row">
<div class="col-md-6 faded"> <div class="col-md-6 faded">
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="{{$book->getUrl()}}" class="text-book"><i class="zmdi zmdi-book"></i>{{ $book->name }}</a> <a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->name }}</a>
@if($page->hasChapter()) @if($page->hasChapter())
<span class="sep">&raquo;</span> <span class="sep">&raquo;</span>
<a href="{{ $page->chapter->getUrl() }}" class="text-chapter"> <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
<i class="zmdi zmdi-collection-bookmark"></i> <i class="zmdi zmdi-collection-bookmark"></i>
{{$page->chapter->name}} {{$page->chapter->name}}
</a> </a>
@ -20,11 +20,11 @@
<div class="col-md-6 faded"> <div class="col-md-6 faded">
<div class="action-buttons"> <div class="action-buttons">
@if($currentUser->can('page-update')) @if($currentUser->can('page-update'))
<a href="{{$page->getUrl() . '/revisions'}}" class="text-primary"><i class="zmdi zmdi-replay"></i>Revisions</a> <a href="{{$page->getUrl() . '/revisions'}}" class="text-primary text-button"><i class="zmdi zmdi-replay"></i>Revisions</a>
<a href="{{$page->getUrl() . '/edit'}}" class="text-primary" ><i class="zmdi zmdi-edit"></i>Edit</a> <a href="{{$page->getUrl() . '/edit'}}" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>Edit</a>
@endif @endif
@if($currentUser->can('page-delete')) @if($currentUser->can('page-delete'))
<a href="{{$page->getUrl() . '/delete'}}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a> <a href="{{$page->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
@endif @endif
</div> </div>
</div> </div>

View File

@ -6,7 +6,7 @@
@foreach($book->children() as $bookChild) @foreach($book->children() as $bookChild)
<li class="list-item-{{ $bookChild->getName() }}"> <li class="list-item-{{ $bookChild->getName() }}">
<a href="{{$bookChild->getUrl()}}" class="{{ $bookChild->getName() }} {{ $current->matches($bookChild)? 'selected' : '' }}"> <a href="{{$bookChild->getUrl()}}" class="{{ $bookChild->getName() }} {{ $current->matches($bookChild)? 'selected' : '' }}">
@if($bookChild->isA('chapter'))<i class="zmdi zmdi-collection-bookmark chapter-toggle"></i>@else <i class="zmdi zmdi-file-text"></i>@endif{{ $bookChild->name }} @if($bookChild->isA('chapter'))<i class="zmdi zmdi-collection-bookmark"></i>@else <i class="zmdi zmdi-file-text"></i>@endif{{ $bookChild->name }}
</a> </a>
@if($bookChild->isA('chapter') && count($bookChild->pages) > 0) @if($bookChild->isA('chapter') && count($bookChild->pages) > 0)

View File

@ -3,8 +3,8 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12 setting-nav"> <div class="col-md-12 setting-nav">
<a href="/settings" @if($selected == 'settings') class="selected" @endif><i class="zmdi zmdi-settings"></i>Settings</a> <a href="/settings" @if($selected == 'settings') class="selected text-button" @endif><i class="zmdi zmdi-settings"></i>Settings</a>
<a href="/users" @if($selected == 'users') class="selected" @endif><i class="zmdi zmdi-accounts"></i>Users</a> <a href="/users" @if($selected == 'users') class="selected text-button" @endif><i class="zmdi zmdi-accounts"></i>Users</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@
<div class="col-md-6"></div> <div class="col-md-6"></div>
<div class="col-md-6 faded"> <div class="col-md-6 faded">
<div class="action-buttons"> <div class="action-buttons">
<a href="/users/{{$user->id}}/delete" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete User</a> <a href="/users/{{$user->id}}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete User</a>
</div> </div>
</div> </div>
</div> </div>

32
tests/AuthTest.php Normal file
View File

@ -0,0 +1,32 @@
<?php
class AuthTest extends TestCase
{
public function testAuthWorking()
{
$this->visit('/')
->seePageIs('/login');
}
public function testLogin()
{
$this->visit('/')
->seePageIs('/login')
->type('admin@admin.com', '#email')
->type('password', '#password')
->press('Sign In')
->seePageIs('/')
->see('BookStack');
}
public function testLogout()
{
$this->asAdmin()
->visit('/')
->seePageIs('/')
->visit('/logout')
->visit('/')
->seePageIs('/login');
}
}

121
tests/EntityTest.php Normal file
View File

@ -0,0 +1,121 @@
<?php
class EntityTest extends TestCase
{
public function testEntityCreation()
{
// Test Creation
$book = $this->bookCreation();
$chapter = $this->chapterCreation($book);
$page = $this->pageCreation($chapter);
// Test Updating
$book = $this->bookUpdate($book);
// Test Deletion
$this->bookDelete($book);
}
public function bookDelete(\Oxbow\Book $book)
{
$this->asAdmin()
->visit($book->getUrl())
// Check link works correctly
->click('Delete')
->seePageIs($book->getUrl() . '/delete')
// Ensure the book name is show to user
->see($book->name)
->press('Confirm')
->seePageIs('/books')
->notSeeInDatabase('books', ['id' => $book->id]);
}
public function bookUpdate(\Oxbow\Book $book)
{
$newName = $book->name . ' Updated';
$this->asAdmin()
// Go to edit screen
->visit($book->getUrl() . '/edit')
->see($book->name)
// Submit new name
->type($newName, '#name')
->press('Save Book')
// Check page url and text
->seePageIs($book->getUrl() . '-updated')
->see($newName);
return \Oxbow\Book::find($book->id);
}
public function pageCreation($chapter)
{
$page = factory(\Oxbow\Page::class)->make([
'name' => 'My First Page'
]);
$this->asAdmin()
// Navigate to page create form
->visit($chapter->getUrl())
->click('New Page')
->seePageIs($chapter->getUrl() . '/create-page')
// Fill out form
->type($page->name, '#name')
->type($page->html, '#html')
->press('Save Page')
// Check redirect and page
->seePageIs($chapter->book->getUrl() . '/page/my-first-page')
->see($page->name);
$page = \Oxbow\Page::where('slug', '=', 'my-first-page')->where('chapter_id', '=', $chapter->id)->first();
return $page;
}
public function chapterCreation(\Oxbow\Book $book)
{
$chapter = factory(\Oxbow\Chapter::class)->make([
'name' => 'My First Chapter'
]);
$this->asAdmin()
// Navigate to chapter create page
->visit($book->getUrl())
->click('New Chapter')
->seePageIs($book->getUrl() . '/chapter/create')
// Fill out form
->type($chapter->name, '#name')
->type($chapter->description, '#description')
->press('Save Chapter')
// Check redirect and landing page
->seePageIs($book->getUrl() . '/chapter/my-first-chapter')
->see($chapter->name)->see($chapter->description);
$chapter = \Oxbow\Chapter::where('slug', '=', 'my-first-chapter')->where('book_id', '=', $book->id)->first();
return $chapter;
}
public function bookCreation()
{
$book = factory(\Oxbow\Book::class)->make([
'name' => 'My First Book'
]);
$this->asAdmin()
->visit('/books')
// Choose to create a book
->click('Add new book')
->seePageIs('/books/create')
// Fill out form & save
->type($book->name, '#name')
->type($book->description, '#description')
->press('Save Book')
// Check it redirects correctly
->seePageIs('/books/my-first-book')
->see($book->name)->see($book->description);
$book = \Oxbow\Book::where('slug', '=', 'my-first-book')->first();
return $book;
}
}

View File

@ -1,19 +0,0 @@
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}

View File

@ -1,13 +1,19 @@
<?php <?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
class TestCase extends Illuminate\Foundation\Testing\TestCase class TestCase extends Illuminate\Foundation\Testing\TestCase
{ {
use DatabaseTransactions;
/** /**
* The base URL to use while testing the application. * The base URL to use while testing the application.
* *
* @var string * @var string
*/ */
protected $baseUrl = 'http://localhost'; protected $baseUrl = 'http://localhost';
private $admin;
/** /**
* Creates the application. * Creates the application.
@ -22,4 +28,12 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
return $app; return $app;
} }
public function asAdmin()
{
if($this->admin === null) {
$this->admin = \Oxbow\User::find(1);
}
return $this->actingAs($this->admin);
}
} }