Added testing coverage for tag index

Also:
- Extracted out index table row to its own view.
- Added empty state.
- Ensured query params are set on pagination links.
This commit is contained in:
Dan Brown 2021-11-06 21:54:02 +00:00
parent f8f9e74992
commit 899349c4b4
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
6 changed files with 172 additions and 47 deletions

View File

@ -26,7 +26,11 @@ class TagController extends Controller
$nameFilter = $request->get('name', '');
$tags = $this->tagRepo
->queryWithTotals($search, $nameFilter)
->paginate(20);
->paginate(50)
->appends(array_filter([
'search' => $search,
'name' => $nameFilter
]));
return view('tags.index', [
'tags' => $tags,

View File

@ -267,6 +267,7 @@ return [
'tags_all_values' => 'All values',
'tags_view_tags' => 'View Tags',
'tags_view_existing_tags' => 'View existing tags',
'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.',
'attachments' => 'Attachments',
'attachments_explain' => 'Upload some files or attach some links to display on your page. These are visible in the page sidebar.',
'attachments_explain_instant_save' => 'Changes here are saved instantly.',

View File

@ -11,7 +11,7 @@
<div>
<div class="block inline mr-xs">
<form method="get" action="{{ url("/tags") }}">
@include('form.request-query-inputs', ['params' => ['page', 'name']])
@include('form.request-query-inputs', ['params' => ['name']])
<input type="text"
name="search"
placeholder="{{ trans('common.search') }}"
@ -32,52 +32,23 @@
</div>
@endif
@if(count($tags) > 0)
<table class="table expand-to-padding mt-m">
@foreach($tags as $tag)
<tr>
<td>
<span class="text-bigger mr-xl">@include('entities.tag', ['tag' => $tag])</span>
</td>
<td width="60" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() }}"
title="{{ trans('entities.tags_usages') }}"
class="pill text-muted">@icon('leaderboard'){{ $tag->usages }}</a>
</td>
<td width="60" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:page}' }}"
title="{{ trans('entities.tags_assigned_pages') }}"
class="pill text-page">@icon('page'){{ $tag->page_count }}</a>
</td>
<td width="60" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:chapter}' }}"
title="{{ trans('entities.tags_assigned_chapters') }}"
class="pill text-chapter">@icon('chapter'){{ $tag->chapter_count }}</a>
</td>
<td width="60" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:book}' }}"
title="{{ trans('entities.tags_assigned_books') }}"
class="pill text-book">@icon('book'){{ $tag->book_count }}</a>
</td>
<td width="60" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:bookshelf}' }}"
title="{{ trans('entities.tags_assigned_shelves') }}"
class="pill text-bookshelf">@icon('bookshelf'){{ $tag->shelf_count }}</a>
</td>
<td class="text-right text-muted">
@if($tag->values ?? false)
<a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_x_unique_values', ['count' => $tag->values]) }}</a>
@elseif(empty($nameFilter))
<a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_all_values') }}</a>
@endif
</td>
</tr>
@include('tags.parts.table-row', ['tag' => $tag, 'nameFilter' => $nameFilter])
@endforeach
</table>
<div>
{{ $tags->links() }}
</div>
@else
<p class="text-muted italic my-xl">
{{ trans('common.no_items') }}.
<br>
{{ trans('entities.tags_list_empty_hint') }}
</p>
@endif
</main>
</div>

View File

@ -0,0 +1,37 @@
<tr>
<td>
<span class="text-bigger mr-xl">@include('entities.tag', ['tag' => $tag])</span>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() }}"
title="{{ trans('entities.tags_usages') }}"
class="pill text-muted">@icon('leaderboard'){{ $tag->usages }}</a>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:page}' }}"
title="{{ trans('entities.tags_assigned_pages') }}"
class="pill text-page">@icon('page'){{ $tag->page_count }}</a>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:chapter}' }}"
title="{{ trans('entities.tags_assigned_chapters') }}"
class="pill text-chapter">@icon('chapter'){{ $tag->chapter_count }}</a>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:book}' }}"
title="{{ trans('entities.tags_assigned_books') }}"
class="pill text-book">@icon('book'){{ $tag->book_count }}</a>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:bookshelf}' }}"
title="{{ trans('entities.tags_assigned_shelves') }}"
class="pill text-bookshelf">@icon('bookshelf'){{ $tag->shelf_count }}</a>
</td>
<td class="text-right text-muted">
@if($tag->values ?? false)
<a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_x_unique_values', ['count' => $tag->values]) }}</a>
@elseif(empty($nameFilter))
<a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_all_values') }}</a>
@endif
</td>
</tr>

View File

@ -3,6 +3,7 @@
namespace Tests\Entity;
use BookStack\Actions\Tag;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use Tests\TestCase;
@ -98,4 +99,95 @@ class TagTest extends TestCase
$resp->assertElementContains('[href="' . $page->getUrl() . '"]', 'color');
$resp->assertElementContains('[href="' . $page->getUrl() . '"]', 'red');
}
public function test_tags_index_shows_tag_name_as_expected_with_right_counts()
{
/** @var Page $page */
$page = Page::query()->first();
$page->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']);
$page->tags()->create(['name' => 'Category', 'value' => 'OtherTestContent']);
$resp = $this->asEditor()->get('/tags');
$resp->assertSee('Category');
$resp->assertElementCount('.tag-item', 1);
$resp->assertDontSee('GreatTestContent');
$resp->assertDontSee('OtherTestContent');
$resp->assertElementContains('a[title="Total tag usages"]', '2');
$resp->assertElementContains('a[title="Assigned to Pages"]', '2');
$resp->assertElementContains('a[title="Assigned to Books"]', '0');
$resp->assertElementContains('a[title="Assigned to Chapters"]', '0');
$resp->assertElementContains('a[title="Assigned to Shelves"]', '0');
$resp->assertElementContains('a[href$="/tags?name=Category"]', '2 unique values');
/** @var Book $book */
$book = Book::query()->first();
$book->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']);
$resp = $this->asEditor()->get('/tags');
$resp->assertElementContains('a[title="Total tag usages"]', '3');
$resp->assertElementContains('a[title="Assigned to Books"]', '1');
$resp->assertElementContains('a[href$="/tags?name=Category"]', '2 unique values');
}
public function test_tag_index_can_be_searched()
{
/** @var Page $page */
$page = Page::query()->first();
$page->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']);
$resp = $this->asEditor()->get('/tags?search=cat');
$resp->assertElementContains('.tag-item .tag-name', 'Category');
$resp = $this->asEditor()->get('/tags?search=content');
$resp->assertElementContains('.tag-item .tag-name', 'Category');
$resp->assertElementContains('.tag-item .tag-value', 'GreatTestContent');
$resp = $this->asEditor()->get('/tags?search=other');
$resp->assertElementNotExists('.tag-item .tag-name');
}
public function test_tag_index_can_be_scoped_to_specific_tag_name()
{
/** @var Page $page */
$page = Page::query()->first();
$page->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']);
$page->tags()->create(['name' => 'Category', 'value' => 'OtherTestContent']);
$page->tags()->create(['name' => 'OtherTagName', 'value' => 'OtherValue']);
$resp = $this->asEditor()->get('/tags?name=Category');
$resp->assertSee('Category');
$resp->assertSee('GreatTestContent');
$resp->assertSee('OtherTestContent');
$resp->assertDontSee('OtherTagName');
$resp->assertElementCount('table .tag-item', 2);
$resp->assertSee('Active Filter:');
$resp->assertElementContains('form[action$="/tags"]', 'Clear Filter');
}
public function test_tags_index_adheres_to_page_permissions()
{
/** @var Page $page */
$page = Page::query()->first();
$page->tags()->create(['name' => 'SuperCategory', 'value' => 'GreatTestContent']);
$resp = $this->asEditor()->get('/tags');
$resp->assertSee('SuperCategory');
$resp = $this->get('/tags?name=SuperCategory');
$resp->assertSee('GreatTestContent');
$page->restricted = true;
$this->regenEntityPermissions($page);
$resp = $this->asEditor()->get('/tags');
$resp->assertDontSee('SuperCategory');
$resp = $this->get('/tags?name=SuperCategory');
$resp->assertDontSee('GreatTestContent');
}
public function test_tag_index_shows_message_on_no_results()
{
/** @var Page $page */
$resp = $this->asEditor()->get('/tags?search=testingval');
$resp->assertSee('No items available');
$resp->assertSee('Tags can be assigned via the page editor sidebar');
}
}

View File

@ -53,6 +53,26 @@ class TestResponse extends BaseTestResponse
return $this;
}
/**
* Assert the response contains the given count of elements
* that match the given css selector.
*
* @return $this
*/
public function assertElementCount(string $selector, int $count)
{
$elements = $this->crawler()->filter($selector);
PHPUnit::assertTrue(
$elements->count() === $count,
'Unable to ' . $count . ' element(s) matching the selector: ' . PHP_EOL . PHP_EOL .
"[{$selector}]" . PHP_EOL . PHP_EOL .
'found ' . $elements->count() . ' within' . PHP_EOL . PHP_EOL .
"[{$this->getContent()}]."
);
return $this;
}
/**
* Assert the response does not contain the specified element.
*