Skip to content

Commit a208c46

Browse files
committed
Sorting: Covered sort set management with tests
1 parent a657012 commit a208c46

File tree

5 files changed

+206
-4
lines changed

5 files changed

+206
-4
lines changed

app/Sorting/SortSetController.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44

55
use BookStack\Activity\ActivityType;
66
use BookStack\Http\Controller;
7-
use BookStack\Http\Request;
7+
use Illuminate\Http\Request;
88

99
class SortSetController extends Controller
1010
{
1111
public function __construct()
1212
{
1313
$this->middleware('can:settings-manage');
14-
// TODO - Test
1514
}
1615

1716
public function create()

database/factories/Entities/Models/BookFactory.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ public function definition()
2626
'name' => $this->faker->sentence(),
2727
'slug' => Str::random(10),
2828
'description' => $description,
29-
'description_html' => '<p>' . e($description) . '</p>'
29+
'description_html' => '<p>' . e($description) . '</p>',
30+
'sort_set_id' => null,
31+
'default_template_id' => null,
3032
];
3133
}
3234
}

lang/en/settings.php

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
'sorting_book_default_desc' => 'Select the default sort set to apply to new books. This won\'t affect existing books, and can be overridden per-book.',
8181
'sorting_sets' => 'Sort Sets',
8282
'sorting_sets_desc' => 'These are predefined sorting operations which can be applied to content in the system.',
83+
'sort_set_assigned_to_x_books' => 'Assigned to :count Book|Assigned to :count Books',
8384
'sort_set_create' => 'Create Sort Set',
8485
'sort_set_edit' => 'Edit Sort Set',
8586
'sort_set_delete' => 'Delete Sort Set',

resources/views/settings/sort-sets/parts/sort-set-list-item.blade.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{{ implode(', ', array_map(fn ($op) => $op->getLabel(), $set->getOperations())) }}
77
</div>
88
<div>
9-
<span title="{{ trans('entities.tags_assigned_books') }}"
9+
<span title="{{ trans_choice('settings.sort_set_assigned_to_x_books', $set->books_count ?? 0) }}"
1010
class="flex fill-area min-width-xxs bold text-right text-book"><span class="opacity-60">@icon('book')</span>{{ $set->books_count ?? 0 }}</span>
1111
</div>
1212
</div>

tests/Sorting/SortSetTest.php

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
<?php
2+
3+
namespace Sorting;
4+
5+
use BookStack\Activity\ActivityType;
6+
use BookStack\Entities\Models\Book;
7+
use BookStack\Sorting\SortSet;
8+
use Tests\Api\TestsApi;
9+
use Tests\TestCase;
10+
11+
class SortSetTest extends TestCase
12+
{
13+
use TestsApi;
14+
15+
public function test_manage_settings_permission_required()
16+
{
17+
$set = SortSet::factory()->create();
18+
$user = $this->users->viewer();
19+
$this->actingAs($user);
20+
21+
$actions = [
22+
['GET', '/settings/sorting'],
23+
['POST', '/settings/sorting/sets'],
24+
['GET', "/settings/sorting/sets/{$set->id}"],
25+
['PUT', "/settings/sorting/sets/{$set->id}"],
26+
['DELETE', "/settings/sorting/sets/{$set->id}"],
27+
];
28+
29+
foreach ($actions as [$method, $path]) {
30+
$resp = $this->call($method, $path);
31+
$this->assertPermissionError($resp);
32+
}
33+
34+
$this->permissions->grantUserRolePermissions($user, ['settings-manage']);
35+
36+
foreach ($actions as [$method, $path]) {
37+
$resp = $this->call($method, $path);
38+
$this->assertNotPermissionError($resp);
39+
}
40+
}
41+
42+
public function test_create_flow()
43+
{
44+
$resp = $this->asAdmin()->get('/settings/sorting');
45+
$this->withHtml($resp)->assertLinkExists(url('/settings/sorting/sets/new'));
46+
47+
$resp = $this->get('/settings/sorting/sets/new');
48+
$this->withHtml($resp)->assertElementExists('form[action$="/settings/sorting/sets"] input[name="name"]');
49+
$resp->assertSeeText('Name - Alphabetical (Asc)');
50+
51+
$details = ['name' => 'My new sort', 'sequence' => 'name_asc'];
52+
$resp = $this->post('/settings/sorting/sets', $details);
53+
$resp->assertRedirect('/settings/sorting');
54+
55+
$this->assertActivityExists(ActivityType::SORT_SET_CREATE);
56+
$this->assertDatabaseHas('sort_sets', $details);
57+
}
58+
59+
public function test_listing_in_settings()
60+
{
61+
$set = SortSet::factory()->create(['name' => 'My super sort set', 'sequence' => 'name_asc']);
62+
$books = Book::query()->limit(5)->get();
63+
foreach ($books as $book) {
64+
$book->sort_set_id = $set->id;
65+
$book->save();
66+
}
67+
68+
$resp = $this->asAdmin()->get('/settings/sorting');
69+
$resp->assertSeeText('My super sort set');
70+
$resp->assertSeeText('Name - Alphabetical (Asc)');
71+
$this->withHtml($resp)->assertElementContains('.item-list-row [title="Assigned to 5 Books"]', '5');
72+
}
73+
74+
public function test_update_flow()
75+
{
76+
$set = SortSet::factory()->create(['name' => 'My sort set to update', 'sequence' => 'name_asc']);
77+
78+
$resp = $this->asAdmin()->get("/settings/sorting/sets/{$set->id}");
79+
$respHtml = $this->withHtml($resp);
80+
$respHtml->assertElementContains('.configured-option-list', 'Name - Alphabetical (Asc)');
81+
$respHtml->assertElementNotContains('.available-option-list', 'Name - Alphabetical (Asc)');
82+
83+
$updateData = ['name' => 'My updated sort', 'sequence' => 'name_desc,chapters_last'];
84+
$resp = $this->put("/settings/sorting/sets/{$set->id}", $updateData);
85+
86+
$resp->assertRedirect('/settings/sorting');
87+
$this->assertActivityExists(ActivityType::SORT_SET_UPDATE);
88+
$this->assertDatabaseHas('sort_sets', $updateData);
89+
}
90+
91+
public function test_update_triggers_resort_on_assigned_books()
92+
{
93+
$book = $this->entities->bookHasChaptersAndPages();
94+
$chapter = $book->chapters()->first();
95+
$set = SortSet::factory()->create(['name' => 'My sort set to update', 'sequence' => 'name_asc']);
96+
$book->sort_set_id = $set->id;
97+
$book->save();
98+
$chapter->priority = 10000;
99+
$chapter->save();
100+
101+
$resp = $this->asAdmin()->put("/settings/sorting/sets/{$set->id}", ['name' => $set->name, 'sequence' => 'chapters_last']);
102+
$resp->assertRedirect('/settings/sorting');
103+
104+
$chapter->refresh();
105+
$this->assertNotEquals(10000, $chapter->priority);
106+
}
107+
108+
public function test_delete_flow()
109+
{
110+
$set = SortSet::factory()->create();
111+
112+
$resp = $this->asAdmin()->get("/settings/sorting/sets/{$set->id}");
113+
$resp->assertSeeText('Delete Sort Set');
114+
115+
$resp = $this->delete("settings/sorting/sets/{$set->id}");
116+
$resp->assertRedirect('/settings/sorting');
117+
118+
$this->assertActivityExists(ActivityType::SORT_SET_DELETE);
119+
$this->assertDatabaseMissing('sort_sets', ['id' => $set->id]);
120+
}
121+
122+
public function test_delete_requires_confirmation_if_books_assigned()
123+
{
124+
$set = SortSet::factory()->create();
125+
$books = Book::query()->limit(5)->get();
126+
foreach ($books as $book) {
127+
$book->sort_set_id = $set->id;
128+
$book->save();
129+
}
130+
131+
$resp = $this->asAdmin()->get("/settings/sorting/sets/{$set->id}");
132+
$resp->assertSeeText('Delete Sort Set');
133+
134+
$resp = $this->delete("settings/sorting/sets/{$set->id}");
135+
$resp->assertRedirect("/settings/sorting/sets/{$set->id}#delete");
136+
$resp = $this->followRedirects($resp);
137+
138+
$resp->assertSeeText('This sort set is currently used on 5 book(s). Are you sure you want to delete this?');
139+
$this->assertDatabaseHas('sort_sets', ['id' => $set->id]);
140+
141+
$resp = $this->delete("settings/sorting/sets/{$set->id}", ['confirm' => 'true']);
142+
$resp->assertRedirect('/settings/sorting');
143+
$this->assertDatabaseMissing('sort_sets', ['id' => $set->id]);
144+
$this->assertDatabaseMissing('books', ['sort_set_id' => $set->id]);
145+
}
146+
147+
public function test_page_create_triggers_book_sort()
148+
{
149+
$book = $this->entities->bookHasChaptersAndPages();
150+
$set = SortSet::factory()->create(['sequence' => 'name_asc,chapters_first']);
151+
$book->sort_set_id = $set->id;
152+
$book->save();
153+
154+
$resp = $this->actingAsApiEditor()->post("/api/pages", [
155+
'book_id' => $book->id,
156+
'name' => '1111 page',
157+
'markdown' => 'Hi'
158+
]);
159+
$resp->assertOk();
160+
161+
$this->assertDatabaseHas('pages', [
162+
'book_id' => $book->id,
163+
'name' => '1111 page',
164+
'priority' => $book->chapters()->count() + 1,
165+
]);
166+
}
167+
168+
public function test_name_numeric_ordering()
169+
{
170+
$book = Book::factory()->create();
171+
$set = SortSet::factory()->create(['sequence' => 'name_numeric_asc']);
172+
$book->sort_set_id = $set->id;
173+
$book->save();
174+
$this->permissions->regenerateForEntity($book);
175+
176+
$namesToAdd = [
177+
"1 - Pizza",
178+
"2.0 - Tomato",
179+
"2.5 - Beans",
180+
"10 - Bread",
181+
"20 - Milk",
182+
];
183+
184+
foreach ($namesToAdd as $name) {
185+
$this->actingAsApiEditor()->post("/api/pages", [
186+
'book_id' => $book->id,
187+
'name' => $name,
188+
'markdown' => 'Hello'
189+
]);
190+
}
191+
192+
foreach ($namesToAdd as $index => $name) {
193+
$this->assertDatabaseHas('pages', [
194+
'book_id' => $book->id,
195+
'name' => $name,
196+
'priority' => $index + 1,
197+
]);
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)