Dan Brown

Added further tests, Fixed speed_update issues, improved search result query count

...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
3 3
4 class Chapter extends Entity 4 class Chapter extends Entity
5 { 5 {
6 -
7 protected $fillable = ['name', 'description', 'priority', 'book_id']; 6 protected $fillable = ['name', 'description', 'priority', 'book_id'];
8 7
9 public function book() 8 public function book()
...@@ -18,7 +17,7 @@ class Chapter extends Entity ...@@ -18,7 +17,7 @@ class Chapter extends Entity
18 17
19 public function getUrl() 18 public function getUrl()
20 { 19 {
21 - $bookSlug = isset($this->bookSlug) ? $this->bookSlug : $this->book->slug; 20 + $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug;
22 return '/books/' . $bookSlug. '/chapter/' . $this->slug; 21 return '/books/' . $bookSlug. '/chapter/' . $this->slug;
23 } 22 }
24 23
......
...@@ -69,19 +69,18 @@ abstract class Entity extends Model ...@@ -69,19 +69,18 @@ abstract class Entity extends Model
69 * @param $type 69 * @param $type
70 * @return bool 70 * @return bool
71 */ 71 */
72 - public function isA($type) 72 + public static function isA($type)
73 { 73 {
74 - return $this->getName() === strtolower($type); 74 + return static::getName() === strtolower($type);
75 } 75 }
76 76
77 /** 77 /**
78 * Gets the class name. 78 * Gets the class name.
79 * @return string 79 * @return string
80 */ 80 */
81 - public function getName() 81 + public static function getName()
82 { 82 {
83 - $fullClassName = get_class($this); 83 + return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]);
84 - return strtolower(array_slice(explode('\\', $fullClassName), -1, 1)[0]);
85 } 84 }
86 85
87 /** 86 /**
...@@ -102,6 +101,15 @@ abstract class Entity extends Model ...@@ -102,6 +101,15 @@ abstract class Entity extends Model
102 foreach ($wheres as $whereTerm) { 101 foreach ($wheres as $whereTerm) {
103 $search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]); 102 $search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]);
104 } 103 }
104 +
105 + if (!static::isA('book')) {
106 + $search = $search->with('book');
107 + }
108 +
109 + if(static::isA('page')) {
110 + $search = $search->with('chapter');
111 + }
112 +
105 return $search->get(); 113 return $search->get();
106 } 114 }
107 115
......
...@@ -150,14 +150,16 @@ class BookController extends Controller ...@@ -150,14 +150,16 @@ class BookController extends Controller
150 { 150 {
151 $this->checkPermission('book-update'); 151 $this->checkPermission('book-update');
152 $book = $this->bookRepo->getBySlug($bookSlug); 152 $book = $this->bookRepo->getBySlug($bookSlug);
153 + $bookChildren = $this->bookRepo->getChildren($book);
153 $books = $this->bookRepo->getAll(); 154 $books = $this->bookRepo->getAll();
154 - return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books]); 155 + return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]);
155 } 156 }
156 157
157 public function getSortItem($bookSlug) 158 public function getSortItem($bookSlug)
158 { 159 {
159 $book = $this->bookRepo->getBySlug($bookSlug); 160 $book = $this->bookRepo->getBySlug($bookSlug);
160 - return view('books/sort-box', ['book' => $book]); 161 + $bookChildren = $this->bookRepo->getChildren($book);
162 + return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
161 } 163 }
162 164
163 /** 165 /**
......
...@@ -33,7 +33,6 @@ class ChapterController extends Controller ...@@ -33,7 +33,6 @@ class ChapterController extends Controller
33 33
34 /** 34 /**
35 * Show the form for creating a new chapter. 35 * Show the form for creating a new chapter.
36 - *
37 * @param $bookSlug 36 * @param $bookSlug
38 * @return Response 37 * @return Response
39 */ 38 */
...@@ -46,7 +45,6 @@ class ChapterController extends Controller ...@@ -46,7 +45,6 @@ class ChapterController extends Controller
46 45
47 /** 46 /**
48 * Store a newly created chapter in storage. 47 * Store a newly created chapter in storage.
49 - *
50 * @param $bookSlug 48 * @param $bookSlug
51 * @param Request $request 49 * @param Request $request
52 * @return Response 50 * @return Response
...@@ -62,8 +60,8 @@ class ChapterController extends Controller ...@@ -62,8 +60,8 @@ class ChapterController extends Controller
62 $chapter = $this->chapterRepo->newFromInput($request->all()); 60 $chapter = $this->chapterRepo->newFromInput($request->all());
63 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id); 61 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id);
64 $chapter->priority = $this->bookRepo->getNewPriority($book); 62 $chapter->priority = $this->bookRepo->getNewPriority($book);
65 - $chapter->created_by = Auth::user()->id; 63 + $chapter->created_by = auth()->user()->id;
66 - $chapter->updated_by = Auth::user()->id; 64 + $chapter->updated_by = auth()->user()->id;
67 $book->chapters()->save($chapter); 65 $book->chapters()->save($chapter);
68 Activity::add($chapter, 'chapter_create', $book->id); 66 Activity::add($chapter, 'chapter_create', $book->id);
69 return redirect($chapter->getUrl()); 67 return redirect($chapter->getUrl());
...@@ -71,7 +69,6 @@ class ChapterController extends Controller ...@@ -71,7 +69,6 @@ class ChapterController extends Controller
71 69
72 /** 70 /**
73 * Display the specified chapter. 71 * Display the specified chapter.
74 - *
75 * @param $bookSlug 72 * @param $bookSlug
76 * @param $chapterSlug 73 * @param $chapterSlug
77 * @return Response 74 * @return Response
...@@ -87,7 +84,6 @@ class ChapterController extends Controller ...@@ -87,7 +84,6 @@ class ChapterController extends Controller
87 84
88 /** 85 /**
89 * Show the form for editing the specified chapter. 86 * Show the form for editing the specified chapter.
90 - *
91 * @param $bookSlug 87 * @param $bookSlug
92 * @param $chapterSlug 88 * @param $chapterSlug
93 * @return Response 89 * @return Response
...@@ -102,7 +98,6 @@ class ChapterController extends Controller ...@@ -102,7 +98,6 @@ class ChapterController extends Controller
102 98
103 /** 99 /**
104 * Update the specified chapter in storage. 100 * Update the specified chapter in storage.
105 - *
106 * @param Request $request 101 * @param Request $request
107 * @param $bookSlug 102 * @param $bookSlug
108 * @param $chapterSlug 103 * @param $chapterSlug
...@@ -115,7 +110,7 @@ class ChapterController extends Controller ...@@ -115,7 +110,7 @@ class ChapterController extends Controller
115 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 110 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
116 $chapter->fill($request->all()); 111 $chapter->fill($request->all());
117 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id); 112 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id);
118 - $chapter->updated_by = Auth::user()->id; 113 + $chapter->updated_by = auth()->user()->id;
119 $chapter->save(); 114 $chapter->save();
120 Activity::add($chapter, 'chapter_update', $book->id); 115 Activity::add($chapter, 'chapter_update', $book->id);
121 return redirect($chapter->getUrl()); 116 return redirect($chapter->getUrl());
...@@ -137,7 +132,6 @@ class ChapterController extends Controller ...@@ -137,7 +132,6 @@ class ChapterController extends Controller
137 132
138 /** 133 /**
139 * Remove the specified chapter from storage. 134 * Remove the specified chapter from storage.
140 - *
141 * @param $bookSlug 135 * @param $bookSlug
142 * @param $chapterSlug 136 * @param $chapterSlug
143 * @return Response 137 * @return Response
......
...@@ -141,7 +141,7 @@ class BookRepo ...@@ -141,7 +141,7 @@ class BookRepo
141 */ 141 */
142 public function getNewPriority($book) 142 public function getNewPriority($book)
143 { 143 {
144 - $lastElem = $book->children()->pop(); 144 + $lastElem = $this->getChildren($book)->pop();
145 return $lastElem ? $lastElem->priority + 1 : 0; 145 return $lastElem ? $lastElem->priority + 1 : 0;
146 } 146 }
147 147
......
...@@ -13,33 +13,35 @@ ...@@ -13,33 +13,35 @@
13 13
14 $factory->define(BookStack\User::class, function ($faker) { 14 $factory->define(BookStack\User::class, function ($faker) {
15 return [ 15 return [
16 - 'name' => $faker->name, 16 + 'name' => $faker->name,
17 - 'email' => $faker->email, 17 + 'email' => $faker->email,
18 - 'password' => str_random(10), 18 + 'password' => str_random(10),
19 'remember_token' => str_random(10), 19 'remember_token' => str_random(10),
20 ]; 20 ];
21 }); 21 });
22 22
23 $factory->define(BookStack\Book::class, function ($faker) { 23 $factory->define(BookStack\Book::class, function ($faker) {
24 return [ 24 return [
25 - 'name' => $faker->sentence, 25 + 'name' => $faker->sentence,
26 - 'slug' => str_random(10), 26 + 'slug' => str_random(10),
27 'description' => $faker->paragraph 27 'description' => $faker->paragraph
28 ]; 28 ];
29 }); 29 });
30 30
31 $factory->define(BookStack\Chapter::class, function ($faker) { 31 $factory->define(BookStack\Chapter::class, function ($faker) {
32 return [ 32 return [
33 - 'name' => $faker->sentence, 33 + 'name' => $faker->sentence,
34 - 'slug' => str_random(10), 34 + 'slug' => str_random(10),
35 'description' => $faker->paragraph 35 'description' => $faker->paragraph
36 ]; 36 ];
37 }); 37 });
38 38
39 $factory->define(BookStack\Page::class, function ($faker) { 39 $factory->define(BookStack\Page::class, function ($faker) {
40 + $html = '<p>' . implode('</p>', $faker->paragraphs(5)) . '</p>';
40 return [ 41 return [
41 'name' => $faker->sentence, 42 'name' => $faker->sentence,
42 - 'slug' => str_random(10), 43 + 'slug' => str_random(10),
43 - 'html' => '<p>' . implode('</p>', $faker->paragraphs(5)) . '</p>' 44 + 'html' => $html,
45 + 'text' => strip_tags($html)
44 ]; 46 ];
45 }); 47 });
......
...@@ -54,36 +54,36 @@ class AddEntityIndexes extends Migration ...@@ -54,36 +54,36 @@ class AddEntityIndexes extends Migration
54 public function down() 54 public function down()
55 { 55 {
56 Schema::table('books', function (Blueprint $table) { 56 Schema::table('books', function (Blueprint $table) {
57 - $table->dropIndex('slug'); 57 + $table->dropIndex('books_slug_index');
58 - $table->dropIndex('created_by'); 58 + $table->dropIndex('books_created_by_index');
59 - $table->dropIndex('updated_by'); 59 + $table->dropIndex('books_updated_by_index');
60 }); 60 });
61 Schema::table('pages', function (Blueprint $table) { 61 Schema::table('pages', function (Blueprint $table) {
62 - $table->dropIndex('slug'); 62 + $table->dropIndex('pages_slug_index');
63 - $table->dropIndex('book_id'); 63 + $table->dropIndex('pages_book_id_index');
64 - $table->dropIndex('chapter_id'); 64 + $table->dropIndex('pages_chapter_id_index');
65 - $table->dropIndex('priority'); 65 + $table->dropIndex('pages_priority_index');
66 - $table->dropIndex('created_by'); 66 + $table->dropIndex('pages_created_by_index');
67 - $table->dropIndex('updated_by'); 67 + $table->dropIndex('pages_updated_by_index');
68 }); 68 });
69 Schema::table('page_revisions', function (Blueprint $table) { 69 Schema::table('page_revisions', function (Blueprint $table) {
70 - $table->dropIndex('page_id'); 70 + $table->dropIndex('page_revisions_page_id_index');
71 }); 71 });
72 Schema::table('chapters', function (Blueprint $table) { 72 Schema::table('chapters', function (Blueprint $table) {
73 - $table->dropIndex('slug'); 73 + $table->dropIndex('chapters_slug_index');
74 - $table->dropIndex('book_id'); 74 + $table->dropIndex('chapters_book_id_index');
75 - $table->dropIndex('priority'); 75 + $table->dropIndex('chapters_priority_index');
76 - $table->dropIndex('created_by'); 76 + $table->dropIndex('chapters_created_by_index');
77 - $table->dropIndex('updated_by'); 77 + $table->dropIndex('chapters_updated_by_index');
78 }); 78 });
79 Schema::table('activities', function (Blueprint $table) { 79 Schema::table('activities', function (Blueprint $table) {
80 - $table->dropIndex('book_id'); 80 + $table->dropIndex('activities_book_id_index');
81 - $table->dropIndex('user_id'); 81 + $table->dropIndex('activities_user_id_index');
82 - $table->dropIndex('entity_id'); 82 + $table->dropIndex('activities_entity_id_index');
83 }); 83 });
84 Schema::table('views', function (Blueprint $table) { 84 Schema::table('views', function (Blueprint $table) {
85 - $table->dropIndex('user_id'); 85 + $table->dropIndex('views_user_id_index');
86 - $table->dropIndex('entity_id'); 86 + $table->dropIndex('views_viewable_id_index');
87 }); 87 });
88 } 88 }
89 } 89 }
......
...@@ -55,6 +55,18 @@ location / { ...@@ -55,6 +55,18 @@ location / {
55 } 55 }
56 ``` 56 ```
57 57
58 +## Testing
59 +
60 +BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. To use you will need PHPUnit installed and accessible via command line. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the following database name, user name and password defined as `bookstack-test`. You will have to create that database and credentials before testing.
61 +
62 +The testing database will also need migrating and seeding beforehand. This can be done with the following commands:
63 +
64 +```
65 +php artisan migrate --database=mysql_testing
66 +php artisan db:seed --class=DummyContentSeeder --database=mysql_testing
67 +```
68 +
69 +Once done you can run `phpunit` in the application root directory to run all tests.
58 70
59 ## License 71 ## License
60 72
......
...@@ -47,8 +47,8 @@ ...@@ -47,8 +47,8 @@
47 </div> 47 </div>
48 <div class="col-lg-4 col-sm-3 text-center"> 48 <div class="col-lg-4 col-sm-3 text-center">
49 <form action="/search/all" method="GET" class="search-box"> 49 <form action="/search/all" method="GET" class="search-box">
50 - <input type="text" name="term" tabindex="2" value="{{ isset($searchTerm) ? $searchTerm : '' }}"> 50 + <input id="header-search-box-input" type="text" name="term" tabindex="2" value="{{ isset($searchTerm) ? $searchTerm : '' }}">
51 - <button class="text-button"><i class="zmdi zmdi-search"></i></button> 51 + <button id="header-search-box-button" type="submit" class="text-button"><i class="zmdi zmdi-search"></i></button>
52 </form> 52 </form>
53 </div> 53 </div>
54 <div class="col-lg-4 col-sm-5"> 54 <div class="col-lg-4 col-sm-5">
......
1 <div class="sort-box" data-type="book" data-id="{{ $book->id }}"> 1 <div class="sort-box" data-type="book" data-id="{{ $book->id }}">
2 <h3 class="text-book"><i class="zmdi zmdi-book"></i>{{ $book->name }}</h3> 2 <h3 class="text-book"><i class="zmdi zmdi-book"></i>{{ $book->name }}</h3>
3 <ul class="sortable-page-list sort-list"> 3 <ul class="sortable-page-list sort-list">
4 - @foreach($book->children() as $bookChild) 4 + @foreach($bookChildren as $bookChild)
5 <li data-id="{{$bookChild->id}}" data-type="{{ $bookChild->getName() }}" class="text-{{ $bookChild->getName() }}"> 5 <li data-id="{{$bookChild->id}}" data-type="{{ $bookChild->getName() }}" class="text-{{ $bookChild->getName() }}">
6 <i class="zmdi {{ $bookChild->isA('chapter') ? 'zmdi-collection-bookmark':'zmdi-file-text'}}"></i>{{ $bookChild->name }} 6 <i class="zmdi {{ $bookChild->isA('chapter') ? 'zmdi-collection-bookmark':'zmdi-file-text'}}"></i>{{ $bookChild->name }}
7 @if($bookChild->isA('chapter')) 7 @if($bookChild->isA('chapter'))
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
7 <div class="row"> 7 <div class="row">
8 <div class="col-md-8" id="sort-boxes"> 8 <div class="col-md-8" id="sort-boxes">
9 9
10 - @include('books/sort-box', ['book' => $book]) 10 + @include('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren])
11 11
12 </div> 12 </div>
13 13
......
...@@ -51,6 +51,34 @@ class EntityTest extends TestCase ...@@ -51,6 +51,34 @@ class EntityTest extends TestCase
51 return \BookStack\Book::find($book->id); 51 return \BookStack\Book::find($book->id);
52 } 52 }
53 53
54 + public function testBookSortPageShows()
55 + {
56 + $books = \BookStack\Book::all();
57 + $bookToSort = $books[0];
58 + $this->asAdmin()
59 + ->visit($bookToSort->getUrl())
60 + ->click('Sort')
61 + ->seePageIs($bookToSort->getUrl() . '/sort')
62 + ->seeStatusCode(200)
63 + ->see($bookToSort->name)
64 + // Ensure page shows other books
65 + ->see($books[1]->name);
66 + }
67 +
68 + public function testBookSortItemReturnsBookContent()
69 + {
70 + $books = \BookStack\Book::all();
71 + $bookToSort = $books[0];
72 + $firstPage = $bookToSort->pages[0];
73 + $firstChapter = $bookToSort->chapters[0];
74 + $this->asAdmin()
75 + ->visit($bookToSort->getUrl() . '/sort-item')
76 + // Ensure book details are returned
77 + ->see($bookToSort->name)
78 + ->see($firstPage->name)
79 + ->see($firstChapter->name);
80 + }
81 +
54 public function pageCreation($chapter) 82 public function pageCreation($chapter)
55 { 83 {
56 $page = factory(\BookStack\Page::class)->make([ 84 $page = factory(\BookStack\Page::class)->make([
...@@ -118,12 +146,29 @@ class EntityTest extends TestCase ...@@ -118,12 +146,29 @@ class EntityTest extends TestCase
118 // Ensure duplicate names are given different slugs 146 // Ensure duplicate names are given different slugs
119 $this->asAdmin() 147 $this->asAdmin()
120 ->visit('/books/create') 148 ->visit('/books/create')
121 - ->submitForm('Save Book', $book->toArray()) 149 + ->type($book->name, '#name')
150 + ->type($book->description, '#description')
151 + ->press('Save Book')
122 ->seePageIs('/books/my-first-book-2'); 152 ->seePageIs('/books/my-first-book-2');
123 153
124 $book = \BookStack\Book::where('slug', '=', 'my-first-book')->first(); 154 $book = \BookStack\Book::where('slug', '=', 'my-first-book')->first();
125 return $book; 155 return $book;
126 } 156 }
127 157
158 + public function testPageSearch()
159 + {
160 + $book = \BookStack\Book::all()->first();
161 + $page = $book->pages->first();
162 +
163 + $this->asAdmin()
164 + ->visit('/')
165 + ->type($page->name, 'term')
166 + ->press('header-search-box-button')
167 + ->see('Search Results')
168 + ->see($page->name)
169 + ->click($page->name)
170 + ->seePageIs($page->getUrl());
171 + }
172 +
128 173
129 } 174 }
......