View source
<?php
namespace Drupal\Tests\book\Functional;
use Drupal\Core\Cache\Cache;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\RoleInterface;
class BookTest extends BrowserTestBase {
use BookTestTrait;
protected static $modules = [
'content_moderation',
'book',
'block',
'node_access_test',
'book_test',
];
protected $defaultTheme = 'stark';
protected $webUser;
protected $adminUser;
protected $webUserWithoutNodeAccess;
protected function setUp() : void {
parent::setUp();
$this
->drupalPlaceBlock('system_breadcrumb_block');
$this
->drupalPlaceBlock('page_title_block');
node_access_rebuild();
$this->bookAuthor = $this
->drupalCreateUser([
'create new books',
'create book content',
'edit own book content',
'add content to books',
'view own unpublished content',
]);
$this->webUser = $this
->drupalCreateUser([
'access printer-friendly version',
'node test view',
]);
$this->webUserWithoutNodeAccess = $this
->drupalCreateUser([
'access printer-friendly version',
]);
$this->adminUser = $this
->drupalCreateUser([
'create new books',
'create book content',
'edit any book content',
'delete any book content',
'add content to books',
'administer blocks',
'administer permissions',
'administer book outlines',
'node test view',
'administer content types',
'administer site configuration',
'view any unpublished content',
]);
}
public function testBookNavigationCacheContext() {
$this
->drupalCreateContentType([
'type' => 'page',
]);
$page = $this
->drupalCreateNode();
$book_nodes = $this
->createBook();
\Drupal::state()
->set('book_test.debug_book_navigation_cache_context', TRUE);
Cache::invalidateTags([
'book_test.debug_book_navigation_cache_context',
]);
$this
->drupalLogin($this->bookAuthor);
$this
->drupalGet($this->adminUser
->toUrl());
$this
->assertSession()
->responseContains('[route.book_navigation]=book.none');
$this
->drupalGet($page
->toUrl());
$this
->assertSession()
->responseContains('[route.book_navigation]=book.none');
$this
->drupalGet($book_nodes[0]
->toUrl());
$this
->assertSession()
->responseContains('[route.book_navigation]=0|2|3');
$this
->drupalGet($book_nodes[1]
->toUrl());
$this
->assertSession()
->responseContains('[route.book_navigation]=0|2|3|4');
$this
->drupalGet($book_nodes[2]
->toUrl());
$this
->assertSession()
->responseContains('[route.book_navigation]=0|2|3|5');
$this
->drupalGet($book_nodes[3]
->toUrl());
$this
->assertSession()
->responseContains('[route.book_navigation]=0|2|6');
$this
->drupalGet($book_nodes[4]
->toUrl());
$this
->assertSession()
->responseContains('[route.book_navigation]=0|2|7');
}
public function testEmptyBook() {
$this
->drupalLogin($this->bookAuthor);
$book = $this
->createBookNode('new');
$this
->drupalLogout();
$this
->drupalLogin($this->adminUser);
$this
->drupalGet('admin/structure/book/' . $book
->id());
$this
->submitForm([], 'Save book pages');
$this
->assertSession()
->pageTextContains('Updated book ' . $book
->label() . '.');
}
public function testBook() {
$nodes = $this
->createBook();
$book = $this->book;
$this
->drupalLogin($this->webUser);
$this
->checkBookNode($book, [
$nodes[0],
$nodes[3],
$nodes[4],
], FALSE, FALSE, $nodes[0], []);
$this
->checkBookNode($nodes[0], [
$nodes[1],
$nodes[2],
], $book, $book, $nodes[1], [
$book,
]);
$this
->checkBookNode($nodes[1], NULL, $nodes[0], $nodes[0], $nodes[2], [
$book,
$nodes[0],
]);
$this
->checkBookNode($nodes[2], NULL, $nodes[1], $nodes[0], $nodes[3], [
$book,
$nodes[0],
]);
$this
->checkBookNode($nodes[3], NULL, $nodes[2], $book, $nodes[4], [
$book,
]);
$this
->checkBookNode($nodes[4], NULL, $nodes[3], $book, FALSE, [
$book,
]);
$this
->drupalLogout();
$this
->drupalLogin($this->bookAuthor);
$this
->drupalGet('node/add/book');
$this
->assertSession()
->responseHeaderContains('X-Drupal-Cache-Tags', 'config:book.settings');
$nodes[] = $this
->createBookNode($book
->id(), $nodes[3]->book['nid']);
$this
->drupalLogout();
$this
->drupalLogin($this->webUser);
$this
->checkBookNode($nodes[3], [
$nodes[5],
], $nodes[2], $book, $nodes[5], [
$book,
]);
$this
->checkBookNode($nodes[4], NULL, $nodes[5], $book, FALSE, [
$book,
]);
$this
->drupalLogout();
$this
->drupalLogin($this->bookAuthor);
$other_book = $this
->createBookNode('new');
$node = $this
->createBookNode($book
->id());
$edit = [
'book[bid]' => $other_book
->id(),
];
$this
->drupalGet('node/' . $node
->id() . '/edit');
$this
->submitForm($edit, 'Save');
$this
->drupalLogout();
$this
->drupalLogin($this->webUser);
$this->book = $other_book;
$this
->checkBookNode($other_book, [
$node,
], FALSE, FALSE, $node, []);
$this
->checkBookNode($node, NULL, $other_book, $other_book, FALSE, [
$other_book,
]);
$this
->drupalLogin($this->bookAuthor);
$book = $this
->createBookNode('new');
$book
->save();
$this
->drupalGet('node/' . $nodes[4]
->id());
$this
->assertSession()
->linkExists('Add child page');
$nodes[4]
->setUnPublished();
$nodes[4]
->save();
$this
->drupalGet('node/' . $nodes[4]
->id());
$this
->assertSession()
->linkExists('Add child page');
}
public function testBookExport() {
$nodes = $this
->createBook();
$this
->drupalLogin($this->webUser);
$this
->drupalGet('node/' . $this->book
->id());
$this
->clickLink('Printer-friendly version');
foreach ($nodes as $node) {
$this
->assertSession()
->pageTextContains($node
->label());
$this
->assertSession()
->responseContains($node->body->processed);
}
$this
->drupalGet('book/export/foobar/' . $this->book
->id());
$this
->assertSession()
->statusCodeEquals(404);
$this
->drupalGet('book/export/html/123');
$this
->assertSession()
->statusCodeEquals(404);
$this
->drupalLogout();
$this
->drupalGet('node/' . $this->book
->id());
$this
->assertSession()
->linkNotExists('Printer-friendly version', 'Anonymous user is not shown link to printer-friendly version.');
$this
->drupalGet('book/export/html/' . $this->book
->id());
$this
->assertSession()
->statusCodeEquals(403);
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, [
'access printer-friendly version',
]);
$this
->drupalGet('book/export/html/' . $this->book
->id());
$this
->assertSession()
->statusCodeEquals(403);
}
public function testBookNavigationBlock() {
$this
->drupalLogin($this->adminUser);
$block = $this
->drupalPlaceBlock('book_navigation');
$edit = [];
$edit[RoleInterface::ANONYMOUS_ID . '[node test view]'] = TRUE;
$this
->drupalGet('admin/people/permissions/' . RoleInterface::ANONYMOUS_ID);
$this
->submitForm($edit, 'Save permissions');
$this
->assertSession()
->pageTextContains('The changes have been saved.');
$nodes = $this
->createBook();
$this
->drupalGet('<front>');
$this
->assertSession()
->pageTextContains($block
->label());
$this
->assertSession()
->pageTextContains($this->book
->label());
$this
->assertSession()
->pageTextNotContains($nodes[0]
->label());
$installer = \Drupal::service('module_installer');
$installer
->uninstall([
'node_access_test',
]);
node_access_rebuild();
$nodes[0]
->setUnPublished();
$nodes[0]
->save();
$this
->assertFalse($nodes[0]
->access('view', $this->webUser));
$this
->drupalLogin($this->webUser);
$this
->drupalGet($nodes[0]
->toUrl());
$this
->assertSession()
->statusCodeEquals(403);
$this
->drupalGet($this->book
->toUrl());
$this
->assertSession()
->responseNotContains($nodes[0]
->getTitle());
$this
->assertSession()
->responseNotContains($nodes[1]
->getTitle());
$this
->assertSession()
->responseNotContains($nodes[2]
->getTitle());
}
public function testGetTableOfContents() {
$nodes = $this
->createBook();
$book = $this->book;
$this
->drupalLogin($this->bookAuthor);
foreach ([
5 => 2,
6 => 3,
7 => 6,
8 => 7,
9 => 8,
10 => 9,
11 => 10,
] as $child => $parent) {
$nodes[$child] = $this
->createBookNode($book
->id(), $nodes[$parent]
->id());
}
$this
->drupalGet($nodes[0]
->toUrl('edit-form'));
$this
->assertSession()
->optionNotExists('edit-book-pid', $nodes[10]
->id());
$this
->assertSession()
->optionNotExists('edit-book-pid', $nodes[11]
->id());
$this
->assertSession()
->optionExists('edit-book-pid', $nodes[9]
->id());
$manager = $this->container
->get('book.manager');
$options = $manager
->getTableOfContents($book
->id(), 3);
$expected_nids = [
$book
->id(),
$nodes[0]
->id(),
$nodes[1]
->id(),
$nodes[2]
->id(),
$nodes[3]
->id(),
$nodes[6]
->id(),
$nodes[4]
->id(),
];
$this
->assertEquals($expected_nids, array_keys($options));
$options = $manager
->getTableOfContents($book
->id(), 3, [
$nodes[3]
->id(),
]);
$expected_nids = [
$book
->id(),
$nodes[0]
->id(),
$nodes[1]
->id(),
$nodes[2]
->id(),
$nodes[4]
->id(),
];
$this
->assertEquals($expected_nids, array_keys($options));
}
public function testNavigationBlockOnAccessModuleInstalled() {
$this
->drupalLogin($this->adminUser);
$block = $this
->drupalPlaceBlock('book_navigation', [
'block_mode' => 'book pages',
]);
$edit = [];
$edit[RoleInterface::ANONYMOUS_ID . '[node test view]'] = TRUE;
$this
->drupalGet('admin/people/permissions/' . RoleInterface::ANONYMOUS_ID);
$this
->submitForm($edit, 'Save permissions');
$this
->assertSession()
->pageTextContains('The changes have been saved.');
$this
->createBook();
$this
->drupalLogin($this->webUser);
$this
->drupalGet('node/' . $this->book
->id());
$this
->assertSession()
->pageTextContains($block
->label());
$this
->drupalLogout();
$this
->drupalGet('node/' . $this->book
->id());
$this
->assertSession()
->pageTextContains($block
->label());
$this
->drupalGet('<front>');
$this
->assertSession()
->pageTextNotContains($block
->label());
}
public function testBookDelete() {
$node_storage = $this->container
->get('entity_type.manager')
->getStorage('node');
$nodes = $this
->createBook();
$this
->drupalLogin($this->adminUser);
$edit = [];
$this
->drupalGet('node/' . $this->book
->id() . '/outline/remove');
$this
->assertSession()
->statusCodeEquals(403);
$this
->drupalGet('node/' . $nodes[4]
->id() . '/outline/remove');
$this
->submitForm($edit, 'Remove');
$node_storage
->resetCache([
$nodes[4]
->id(),
]);
$node4 = $node_storage
->load($nodes[4]
->id());
$this
->assertEmpty($node4->book, 'Deleting child book node properly allowed.');
$node4
->delete();
unset($nodes[4]);
$node_storage
->delete($nodes);
$this
->drupalGet('node/' . $this->book
->id() . '/outline/remove');
$this
->submitForm($edit, 'Remove');
$node_storage
->resetCache([
$this->book
->id(),
]);
$node = $node_storage
->load($this->book
->id());
$this
->assertEmpty($node->book, 'Deleting childless top-level book node properly allowed.');
$nodes = $this
->createBook();
$this
->drupalLogin($this->adminUser);
$this
->drupalGet($this->book
->toUrl('delete-form'));
$this
->assertSession()
->pageTextContains($this->book
->label() . ' is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.');
$this
->drupalGet($this->book
->toUrl('delete-form'));
$this
->submitForm([], 'Delete');
$this
->drupalGet($nodes[0]
->toUrl());
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextContains($nodes[0]
->label());
$node_storage = \Drupal::entityTypeManager()
->getStorage('node');
$node_storage
->resetCache();
$child = $node_storage
->load($nodes[0]
->id());
$this
->assertEquals($child
->id(), $child->book['bid'], 'Child node book ID updated when parent is deleted.');
$second = $node_storage
->load($nodes[1]
->id());
$this
->assertEquals($child
->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.');
}
public function testBookOutline() {
$this
->drupalLogin($this->bookAuthor);
$empty_book = $this
->drupalCreateNode([
'type' => 'book',
]);
$this
->drupalGet('node/' . $empty_book
->id() . '/outline');
$this
->assertSession()
->linkNotExists('Book outline', 'Book Author is not allowed to outline');
$this
->drupalLogin($this->adminUser);
$this
->drupalGet('node/' . $empty_book
->id() . '/outline');
$this
->assertSession()
->pageTextContains('Book outline');
$this
->assertTrue($this
->assertSession()
->optionExists('edit-book-bid', 0)
->isSelected());
$this
->assertSession()
->linkNotExists('Remove from book outline');
$edit = [];
$edit['book[bid]'] = '1';
$this
->drupalGet('node/' . $empty_book
->id() . '/outline');
$this
->submitForm($edit, 'Add to book outline');
$node = \Drupal::entityTypeManager()
->getStorage('node')
->load($empty_book
->id());
$this
->assertEquals($empty_book
->id(), $node->book['nid']);
$this
->assertEquals($empty_book
->id(), $node->book['bid']);
$this
->assertEquals(1, $node->book['depth']);
$this
->assertEquals($empty_book
->id(), $node->book['p1']);
$this
->assertEquals('0', $node->book['pid']);
$this
->drupalLogin($this->bookAuthor);
$book = $this
->createBookNode('new');
$this
->drupalLogin($this->adminUser);
$this
->drupalGet('node/' . $book
->id() . '/outline');
$this
->assertSession()
->pageTextContains('Book outline');
$this
->clickLink('Remove from book outline');
$this
->assertSession()
->pageTextContains('Are you sure you want to remove ' . $book
->label() . ' from the book hierarchy?');
$node = $this
->drupalCreateNode([
'type' => 'book',
]);
$edit = [];
$edit['book[bid]'] = $node
->id();
$this
->drupalGet('node/' . $node
->id() . '/edit');
$this
->submitForm($edit, 'Save');
$node = \Drupal::entityTypeManager()
->getStorage('node')
->load($node
->id());
$this
->assertEquals($node
->id(), $node->book['nid']);
$this
->assertEquals($node
->id(), $node->book['bid']);
$this
->assertEquals(1, $node->book['depth']);
$this
->assertEquals($node
->id(), $node->book['p1']);
$this
->assertEquals('0', $node->book['pid']);
$this
->drupalGet('node/' . $node
->id() . '/edit');
$this
->assertTrue($this
->assertSession()
->optionExists('edit-book-bid', $node
->id())
->isSelected());
}
public function testSaveBookLink() {
$book_manager = \Drupal::service('book.manager');
$link = [
'nid' => 1,
'has_children' => 0,
'original_bid' => 0,
'pid' => 0,
'weight' => 0,
'bid' => 0,
];
$new = TRUE;
$return = $book_manager
->saveBookLink($link, $new);
$link = $book_manager
->getLinkDefaults($link['nid']);
$this
->assertEquals($return, $link);
}
public function testBookListing() {
\Drupal::service('module_installer')
->uninstall([
'node_access_test',
]);
$nodes = $this
->createBook();
$this
->drupalGet('book');
$this
->assertSession()
->pageTextContains($this->book
->label());
$this->book
->setUnpublished();
$this->book
->save();
$this
->drupalGet('book');
$this
->assertSession()
->pageTextNotContains($this->book
->label());
$this->book
->setPublished();
$this->book
->save();
$nodes[0]
->setUnpublished();
$nodes[0]
->save();
$this
->drupalGet('book');
$this
->assertSession()
->pageTextContains($this->book
->label());
$this
->drupalLogin($this->bookAuthor);
$this->book
->setUnpublished();
$this->book
->save();
$this
->drupalGet('book');
$this
->assertSession()
->pageTextContains($this->book
->label());
$this->book
->setOwner($this->webUser)
->save();
$this
->drupalGet('book');
$this
->assertSession()
->pageTextNotContains($this->book
->label());
$this
->drupalLogin($this->adminUser);
$this
->drupalGet('book');
$this
->assertSession()
->pageTextContains($this->book
->label());
}
public function testAdminBookListing() {
$nodes = $this
->createBook();
$this
->drupalLogin($this->adminUser);
$this
->drupalGet('admin/structure/book');
$this
->assertSession()
->pageTextContains($this->book
->label());
}
public function testAdminBookNodeListing() {
$nodes = $this
->createBook();
$this
->drupalLogin($this->adminUser);
$this
->drupalGet('admin/structure/book/' . $this->book
->id());
$this
->assertSession()
->pageTextContains($this->book
->label());
$this
->assertSession()
->elementTextEquals('xpath', '//table//ul[@class="dropbutton"]/li/a', 'View');
$this
->assertSession()
->elementsCount('xpath', '//table//ul[@class="dropbutton"]/li/a', count($nodes));
$nodes[0]
->setUnPublished();
$nodes[0]
->save();
$this
->drupalGet('admin/structure/book/' . $this->book
->id());
$this
->assertSession()
->elementsCount('xpath', '//table//ul[@class="dropbutton"]/li/a', count($nodes));
$old_title = $nodes[1]
->getTitle();
$new_title = $this->randomGenerator
->name();
$nodes[1]
->isDefaultRevision(FALSE);
$nodes[1]
->setNewRevision(TRUE);
$nodes[1]
->setTitle($new_title);
$nodes[1]
->save();
$this
->drupalGet('admin/structure/book/' . $this->book
->id());
$this
->assertSession()
->elementsCount('xpath', '//table//ul[@class="dropbutton"]/li/a', count($nodes));
$this
->assertSession()
->responseNotContains($new_title);
$this
->assertSession()
->responseContains($old_title);
}
public function testHookNodeLoadAccess() {
\Drupal::service('module_installer')
->install([
'node_access_test',
]);
$this
->drupalLogin($this->bookAuthor);
$this->book = $this
->createBookNode('new');
$node_storage = \Drupal::entityTypeManager()
->getStorage('node');
$node_storage
->resetCache();
$this
->drupalLogin($this->webUserWithoutNodeAccess);
$book_node = $node_storage
->load($this->book
->id());
$this
->assertNotEmpty($book_node->book);
$this
->assertEquals($this->book
->id(), $book_node->book['bid']);
$node_storage
->resetCache();
$this
->drupalLogin($this->webUser);
$book_node = $node_storage
->load($this->book
->id());
$this
->assertNotEmpty($book_node->book);
$this
->assertEquals($this->book
->id(), $book_node->book['bid']);
}
public function testBookNavigationBlockOnUnpublishedBook() {
$this
->createBook();
$administratorUser = $this
->drupalCreateUser([
'administer blocks',
'administer nodes',
'bypass node access',
]);
$this
->drupalLogin($administratorUser);
$this
->drupalPlaceBlock('book_navigation', [
'block_mode' => 'book pages',
]);
$edit = [
'status[value]' => FALSE,
];
$this
->drupalGet('node/' . $this->book
->id() . '/edit');
$this
->submitForm($edit, 'Save');
$this
->drupalGet('node/' . $this->book
->id());
$this
->assertSession()
->pageTextContains($this->book
->label());
}
public function testSettingsForm() {
$this
->drupalLogin($this->adminUser);
$this
->drupalGet('admin/structure/book/settings');
$this
->submitForm([], 'Save configuration');
}
}