bibcite_footnotes-8.x-1.0-beta3/tests/src/Functional/ReferenceFootnotesFilterTest.php
tests/src/Functional/ReferenceFootnotesFilterTest.php
<?php
namespace Drupal\Tests\bibcite_footnotes\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\bibcite_entity\Traits\EntityCreationTrait;
/**
* Functional tests for the ReferenceFootnotesFilter.
*
* @group bibcite_footnotes
*/
class ReferenceFootnotesFilterTest extends BrowserTestBase
{
use EntityCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'filter',
'bibcite',
'bibcite_entity',
'bibcite_footnotes',
'text',
];
protected $allowedHtml = '<p> <bibcite-footnote> <div class> <h2> <h3><span class> <a id href class> <sup>';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* A user with administrative privileges.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Test reference entities.
*
* @var \Drupal\bibcite_entity\Entity\ReferenceInterface[]
*/
protected $references = [];
/**
* Test filter format.
*
* @var \Drupal\filter\FilterFormatInterface
*/
protected $filterFormat;
/**
* {@inheritdoc}
*/
protected function setUp(): void
{
parent::setUp();
// Create a content type.
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
// Create test reference entities.
$this->references[] = $this->createReference([
'title' => 'Anne of Green Gables',
'bibcite_year' => 1908,
'type' => 'book',
]);
$this->references[] = $this->createReference([
'title' => 'The Fort Bragg Cartel',
'bibcite_year' => 2025,
'type' => 'book',
]);
// Create a text format with our filter.
$this->filterFormat = FilterFormat::create([
'format' => 'test_format',
'name' => 'Test format',
'filters' => [
'filter_reference_footnotes' => [
'status' => 1,
'weight' => 0,
'settings' => [
'enable_bidirectional_links' => TRUE,
'backlink_symbol' => '↑',
'backlink_position' => 'after',
],
],
'filter_html' => [
'status' => 1,
'weight' => 1,
'settings' => [
'allowed_html' => $this->allowedHtml,
],
],
],
]);
$this->filterFormat->save();
// Create an admin user.
$this->adminUser = $this->drupalCreateUser([
'administer filters',
'create article content',
'edit any article content',
'use text format test_format',
]);
$this->drupalLogin($this->adminUser);
}
/**
* Gets the expected citation label for a reference.
*/
private function getCitationLabel($reference)
{
// The citation label is typically 'bibcite_' followed by the reference ID.
return 'bibcite_' . $reference->id();
}
/**
* Tests filter configuration.
*/
public function testFilterConfiguration()
{
// Go to the filter administration page.
$this->drupalGet('admin/config/content/formats/manage/test_format');
$this->assertSession()->statusCodeEquals(200);
// Check that our filter is enabled.
$this->assertSession()->checkboxChecked('filters[filter_reference_footnotes][status]');
// Verify the filter description.
$this->assertSession()->pageTextContains('Convert bibcite-footnote tags to rendered citations.');
// Check that bidirectional links setting is present and enabled by default.
$this->assertSession()->fieldExists('filters[filter_reference_footnotes][settings][enable_bidirectional_links]');
$this->assertSession()->checkboxChecked('filters[filter_reference_footnotes][settings][enable_bidirectional_links]');
// Check that backlink symbol field is present with default value.
$this->assertSession()->fieldValueEquals('filters[filter_reference_footnotes][settings][backlink_symbol]', '↑');
// Check that backlink position field is present with default value.
$this->assertSession()->fieldValueEquals('filters[filter_reference_footnotes][settings][backlink_position]', 'after');
}
/**
* Tests bidirectional linking functionality.
*/
public function testBidirectionalLinking()
{
$reference = $this->references[0];
$citation_label = $this->getCitationLabel($reference);
// Create a test node with multiple footnotes to the same reference.
$body = [
'value' => '<p>First citation <bibcite-footnote data-entity-id="' . $reference->id() . '" data-page-range="12"></bibcite-footnote>.</p><p>Second citation <bibcite-footnote data-entity-id="' . $reference->id() . '" data-page-range="34"></bibcite-footnote>.</p>',
'format' => 'test_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Bidirectional Links',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Check that citations have unique IDs and link to bibliography.
$this->assertSession()->responseMatches('/<a[^>]*id="' . preg_quote($citation_label) . '_cite_1"[^>]*href="#' . preg_quote($citation_label) . '"[^>]*>.*?<\/a>/');
$this->assertSession()->responseMatches('/<a[^>]*id="' . preg_quote($citation_label) . '_cite_2"[^>]*href="#' . preg_quote($citation_label) . '"[^>]*>.*?<\/a>/');
// Check that bibliography has backlinks to citations.
$this->assertSession()->responseContains('bibcite-backlinks');
$this->assertSession()->responseContains('bibcite-backlink');
$this->assertSession()->responseContains('↑');
// Verify backlinks point to the correct citation IDs.
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label) . '_cite_1"[^>]*>↑<\/a>/');
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label) . '_cite_2"[^>]*>↑<\/a>/');
}
/**
* Tests bidirectional linking with multiple references.
*/
public function testBidirectionalLinkingMultipleReferences()
{
$reference1 = $this->references[0];
$reference2 = $this->references[1];
$citation_label1 = $this->getCitationLabel($reference1);
$citation_label2 = $this->getCitationLabel($reference2);
$body = [
'value' => '<p>First reference <bibcite-footnote data-entity-id="' . $reference1->id() . '"></bibcite-footnote> and second reference <bibcite-footnote data-entity-id="' . $reference2->id() . '"></bibcite-footnote>.</p><p>Another citation to first <bibcite-footnote data-entity-id="' . $reference1->id() . '"></bibcite-footnote>.</p>',
'format' => 'test_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Multiple References Bidirectional',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Check that each reference has correct backlinks.
// First reference should have 2 backlinks.
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label1) . '_cite_1"[^>]*>↑<\/a>/');
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label1) . '_cite_2"[^>]*>↑<\/a>/');
// Second reference should have 1 backlink.
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label2) . '_cite_1"[^>]*>↑<\/a>/');
}
/**
* Tests bidirectional linking disabled.
*/
public function testBidirectionalLinkingDisabled()
{
// Create a text format with bidirectional links disabled.
$disabledFormat = FilterFormat::create([
'format' => 'disabled_format',
'name' => 'Disabled bidirectional format',
'filters' => [
'filter_reference_footnotes' => [
'status' => 1,
'weight' => 0,
'settings' => [
'enable_bidirectional_links' => FALSE,
'backlink_symbol' => '↑',
'backlink_position' => 'after',
],
],
'filter_html' => [
'status' => 1,
'weight' => 1,
'settings' => [
'allowed_html' => $this->allowedHtml,
],
],
],
]);
$disabledFormat->save();
// Grant permission to use the new format.
$this->adminUser->addRole('administrator');
$this->adminUser->save();
$reference = $this->references[0];
$citation_label = $this->getCitationLabel($reference);
$body = [
'value' => '<p>Citation <bibcite-footnote data-entity-id="' . $reference->id() . '" data-page-range="12"></bibcite-footnote>.</p>',
'format' => 'disabled_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Disabled Bidirectional',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Citations should still link to bibliography.
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label) . '"[^>]*>.*?<\/a>/');
// But should not have unique IDs for bidirectional linking.
$this->assertSession()->responseNotMatches('/<a[^>]*id="' . preg_quote($citation_label) . '_cite_1"[^>]*>/');
// Bibliography should not have backlinks.
$this->assertSession()->responseNotContains('bibcite-backlinks');
$this->assertSession()->responseNotContains('bibcite-backlink');
}
/**
* Tests custom backlink symbol.
*/
public function testCustomBacklinkSymbol()
{
// Create a text format with custom backlink symbol.
$customFormat = FilterFormat::create([
'format' => 'custom_format',
'name' => 'Custom symbol format',
'filters' => [
'filter_reference_footnotes' => [
'status' => 1,
'weight' => 0,
'settings' => [
'enable_bidirectional_links' => TRUE,
'backlink_symbol' => '↩',
'backlink_position' => 'after',
],
],
'filter_html' => [
'status' => 1,
'weight' => 1,
'settings' => [
'allowed_html' => $this->allowedHtml,
],
],
],
]);
$customFormat->save();
// Grant permission to use the new format.
$this->adminUser->addRole('administrator');
$this->adminUser->save();
$reference = $this->references[0];
$body = [
'value' => '<p>Citation <bibcite-footnote data-entity-id="' . $reference->id() . '"></bibcite-footnote>.</p>',
'format' => 'custom_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Custom Backlink Symbol',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Should use custom backlink symbol.
$this->assertSession()->responseContains('↩');
$this->assertSession()->responseNotContains('↑');
}
/**
* Tests backlink position setting.
*/
public function testBacklinkPosition()
{
// Create a text format with backlinks before entries.
$beforeFormat = FilterFormat::create([
'format' => 'before_format',
'name' => 'Backlinks before format',
'filters' => [
'filter_reference_footnotes' => [
'status' => 1,
'weight' => 0,
'settings' => [
'enable_bidirectional_links' => TRUE,
'backlink_symbol' => '↑',
'backlink_position' => 'before',
],
],
'filter_html' => [
'status' => 1,
'weight' => 1,
'settings' => [
'allowed_html' => $this->allowedHtml,
],
],
],
]);
$beforeFormat->save();
// Grant permission to use the new format.
$this->adminUser->addRole('administrator');
$this->adminUser->save();
$reference = $this->references[0];
$body = [
'value' => '<p>Citation <bibcite-footnote data-entity-id="' . $reference->id() . '"></bibcite-footnote>.</p>',
'format' => 'before_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Backlink Position',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Backlinks should be present (we can't easily test position in functional test,
// but we can verify the feature doesn't break when position is set to 'before').
$this->assertSession()->responseContains('bibcite-backlinks');
$this->assertSession()->responseContains('↑');
}
/**
* Tests that bidirectional links work with complex content.
*/
public function testBidirectionalLinksComplexContent()
{
$reference1 = $this->references[0];
$reference2 = $this->references[1];
$citation_label1 = $this->getCitationLabel($reference1);
$citation_label2 = $this->getCitationLabel($reference2);
// Create complex content with multiple paragraphs and citations.
$body = [
'value' => '
<h2>Introduction</h2>
<p>First mention of reference one<bibcite-footnote data-entity-id="' . $reference1->id() . '" data-page-range="10"></bibcite-footnote>.</p>
<p>Some intermediate text without citations.</p>
<h2>Methods</h2>
<p>Reference two mentioned here<bibcite-footnote data-entity-id="' . $reference2->id() . '" data-page-range="25"></bibcite-footnote>.</p>
<p>Another citation to reference one<bibcite-footnote data-entity-id="' . $reference1->id() . '" data-page-range="15"></bibcite-footnote>.</p>
<p>And another to reference two<bibcite-footnote data-entity-id="' . $reference2->id() . '" data-page-range="30"></bibcite-footnote>.</p>
<h2>Conclusion</h2>
<p>Final citation to reference one<bibcite-footnote data-entity-id="' . $reference1->id() . '" data-page-range="20"></bibcite-footnote>.</p>
',
'format' => 'test_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Complex Content',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Should have 5 total citations.
$citation_count = substr_count($this->getSession()->getPage()->getHtml(), 'bibcite-citation');
$this->assertEquals(5, $citation_count, "Expected 5 citations but found $citation_count");
// Reference 1 should have 3 backlinks.
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label1) . '_cite_1"[^>]*>↑<\/a>/');
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label1) . '_cite_3"[^>]*>↑<\/a>/');
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label1) . '_cite_3"[^>]*>↑<\/a>/');
// Reference 2 should have 2 backlinks.
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label2) . '_cite_1"[^>]*>↑<\/a>/');
$this->assertSession()->responseMatches('/<a[^>]*href="#' . preg_quote($citation_label2) . '_cite_2"[^>]*>↑<\/a>/');
}
/**
* Tests filter settings form validation.
*/
public function testFilterSettingsValidation()
{
$this->drupalGet('admin/config/content/formats/manage/test_format');
// Test that valid values are accepted.
$edit = [
'filters[filter_reference_footnotes][settings][backlink_symbol]' => '↖',
'filters[filter_reference_footnotes][settings][backlink_position]' => 'before',
];
$this->submitForm($edit, 'Save configuration');
// Should save successfully.
$this->assertSession()->pageTextContains('The text format Test format has been updated.');
}
/**
* Tests basic footnote rendering with real entities.
*/
public function testBasicFootnoteRendering()
{
$reference = $this->references[0];
// Create a test node with footnote markup.
$body = [
'value' => '<p>Test content with <bibcite-footnote data-entity-id="' . $reference->id() . '" data-page-range="12"></bibcite-footnote> a citation.</p>',
'format' => 'test_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Footnote Article',
'body' => $body,
]);
// View the node.
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Check that the footnote markup is processed.
$this->assertSession()->responseNotContains('<bibcite-footnote');
$this->assertSession()->responseContains('bibcite-citation');
// Check that bibliography section is present.
$this->assertSession()->responseContains('bibcite-footnotes-section');
$this->assertSession()->responseContains('Footnotes');
}
/**
* Tests multiple footnotes with same reference.
*/
public function testMultipleFootnotesSameReference()
{
$reference = $this->references[0];
$body = [
'value' => '<p>First citation <bibcite-footnote data-entity-id="' . $reference->id() . '" data-page-range="12"></bibcite-footnote> and second citation <bibcite-footnote data-entity-id="' . $reference->id() . '" data-page-range="34"></bibcite-footnote>.</p>',
'format' => 'test_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Multiple Footnotes',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Should have two citations rendered.
$this->assertEquals(2, substr_count($this->getSession()->getPage()->getHtml(), 'bibcite-citation'));
}
/**
* Tests footnotes with different references.
*/
public function testFootnotesDifferentReferences()
{
$reference1 = $this->references[0];
$reference2 = $this->references[1];
$body = [
'value' => '<p>First <bibcite-footnote data-entity-id="' . $reference1->id() . '" data-page-range="12"></bibcite-footnote> and second <bibcite-footnote data-entity-id="' . $reference2->id() . '" data-page-range="22"></bibcite-footnote> citation.</p>',
'format' => 'test_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Different References',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Should have two citations and bibliography entries.
$this->assertEquals(2, substr_count($this->getSession()->getPage()->getHtml(), 'bibcite-citation'));
}
/**
* Tests footnote without entity-id.
*/
public function testFootnoteWithoutEntityId()
{
$body = [
'value' => '<p>Test <bibcite-footnote data-page-range="12"></bibcite-footnote> invalid citation.</p>',
'format' => 'test_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test Invalid Footnote',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Invalid footnote should be ignored/removed.
$this->assertSession()->responseNotContains('bibcite-footnote');
$this->assertSession()->responseNotContains('bibcite-citation');
}
/**
* Tests text without footnotes.
*/
public function testTextWithoutFootnotes()
{
$body = [
'value' => '<p>This is a simple paragraph without any footnotes.</p>',
'format' => 'test_format',
];
$node = $this->drupalCreateNode([
'type' => 'article',
'title' => 'Test No Footnotes',
'body' => $body,
]);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->statusCodeEquals(200);
// Should not contain any citation markup.
$this->assertSession()->responseNotContains('bibcite-citation');
$this->assertSession()->responseNotContains('bibcite-footnotes-section');
}
}
