summaryrefslogtreecommitdiffstatshomepage
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/lib/Drupal/Component/Gettext/PoItem.php2
-rw-r--r--core/lib/Drupal/Core/Config/DatabaseStorage.php2
-rw-r--r--core/lib/Drupal/Core/Render/Renderer.php33
-rw-r--r--core/modules/block_content/src/Hook/BlockContentHooks.php10
-rw-r--r--core/modules/file/src/Plugin/Field/FieldType/FileItem.php5
-rw-r--r--core/modules/file/tests/src/Kernel/FileItemTest.php44
-rw-r--r--core/modules/image/src/Plugin/Field/FieldType/ImageItem.php4
-rw-r--r--core/modules/image/tests/src/Kernel/ImageItemTest.php42
-rw-r--r--core/modules/locale/locale.install6
-rw-r--r--core/modules/locale/tests/src/Functional/LocaleInstallTest.php48
-rw-r--r--core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php24
-rw-r--r--core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php2
-rw-r--r--core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php3
-rw-r--r--core/tests/Drupal/Tests/Component/Gettext/PoItemTest.php85
-rw-r--r--core/tests/Drupal/Tests/Core/Render/RendererTest.php62
15 files changed, 285 insertions, 87 deletions
diff --git a/core/lib/Drupal/Component/Gettext/PoItem.php b/core/lib/Drupal/Component/Gettext/PoItem.php
index 7cc32568ad23..89dee6cc0858 100644
--- a/core/lib/Drupal/Component/Gettext/PoItem.php
+++ b/core/lib/Drupal/Component/Gettext/PoItem.php
@@ -271,7 +271,7 @@ class PoItem {
private function formatSingular() {
$output = '';
$output .= 'msgid ' . $this->formatString($this->source);
- $output .= 'msgstr ' . (isset($this->translation) ? $this->formatString($this->translation) : '""');
+ $output .= 'msgstr ' . (isset($this->translation) ? $this->formatString($this->translation) : '""' . "\n");
return $output;
}
diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index 60da9de12470..299693f2c779 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -272,7 +272,7 @@ class DatabaseStorage implements StorageInterface {
* be unserialized.
*/
public function decode($raw) {
- $data = @unserialize($raw);
+ $data = @unserialize($raw, ['allowed_classes' => FALSE]);
return is_array($data) ? $data : FALSE;
}
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 1f26381bb181..0a28501ae71c 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -615,12 +615,39 @@ class Renderer implements RendererInterface {
* {@inheritdoc}
*/
public function executeInRenderContext(RenderContext $context, callable $callable) {
- // Store the current render context.
+ // When executing in a render context, we need to isolate any bubbled
+ // context within this method. To allow for async rendering, it's necessary
+ // to detect if a fiber suspends within a render context. When this happens,
+ // we swap the previous render context in before suspending upwards, then
+ // back out again before resuming.
$previous_context = $this->getCurrentRenderContext();
-
// Set the provided context and call the callable, it will use that context.
$this->setCurrentRenderContext($context);
- $result = $callable();
+
+ $fiber = new \Fiber(static fn () => $callable());
+ $fiber->start();
+ while (!$fiber->isTerminated()) {
+ if ($fiber->isSuspended()) {
+ // When ::executeInRenderContext() is executed within a Fiber, which is
+ // always the case when rendering placeholders, if the callback results
+ // in this fiber being suspended, we need to suspend again up to the
+ // parent Fiber. Doing so allows other placeholders to be rendered
+ // before returning here.
+ if (\Fiber::getCurrent() !== NULL) {
+ $this->setCurrentRenderContext($previous_context);
+ \Fiber::suspend();
+ $this->setCurrentRenderContext($context);
+ }
+ $fiber->resume();
+ }
+ if (!$fiber->isTerminated()) {
+ // If we've reached this point, then the fiber has already been started
+ // and resumed at least once, so may be suspending repeatedly. Avoid
+ // a spin-lock by waiting for 0.5ms prior to continuing the while loop.
+ usleep(500);
+ }
+ }
+ $result = $fiber->getReturn();
assert($context->count() <= 1, 'Bubbling failed.');
// Restore the original render context.
diff --git a/core/modules/block_content/src/Hook/BlockContentHooks.php b/core/modules/block_content/src/Hook/BlockContentHooks.php
index bd1cfd608f14..4eef9bb8580c 100644
--- a/core/modules/block_content/src/Hook/BlockContentHooks.php
+++ b/core/modules/block_content/src/Hook/BlockContentHooks.php
@@ -29,11 +29,11 @@ class BlockContentHooks {
$field_ui = \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('help.page', ['name' => 'field_ui'])->toString() : '#';
$output = '';
$output .= '<h2>' . $this->t('About') . '</h2>';
- $output .= '<p>' . $this->t('The Block Content module allows you to create and manage custom <em>block types</em> and <em>content-containing blocks</em>. For more information, see the <a href=":online-help">online documentation for the Block Content module</a>.', [':online-help' => 'https://www.drupal.org/documentation/modules/block_content']) . '</p>';
+ $output .= '<p>' . $this->t('The Block Content module manages the creation, editing, and deletion of content blocks. Content blocks are field-able content entities managed by the <a href=":field">Field module</a>. For more information, see the <a href=":block-content">online documentation for the Block Content module</a>.', [':block-content' => 'https://www.drupal.org/documentation/modules/block_content', ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</p>';
$output .= '<h2>' . $this->t('Uses') . '</h2>';
$output .= '<dl>';
$output .= '<dt>' . $this->t('Creating and managing block types') . '</dt>';
- $output .= '<dd>' . $this->t('Users with the <em>Administer blocks</em> permission can create and edit block types with fields and display settings, from the <a href=":types">Block types</a> page under the Structure menu. For more information about managing fields and display settings, see the <a href=":field-ui">Field UI module help</a> and <a href=":field">Field module help</a>.', [
+ $output .= '<dd>' . $this->t('Users with the <em>Administer block types</em> permission can create and edit block types with fields and display settings, from the <a href=":types">Block types</a> page under the Structure menu. For more information about managing fields and display settings, see the <a href=":field-ui">Field UI module help</a> and <a href=":field">Field module help</a>.', [
':types' => Url::fromRoute('entity.block_content_type.collection')->toString(),
':field-ui' => $field_ui,
':field' => Url::fromRoute('help.page', [
@@ -41,9 +41,9 @@ class BlockContentHooks {
])->toString(),
]) . '</dd>';
$output .= '<dt>' . $this->t('Creating content blocks') . '</dt>';
- $output .= '<dd>' . $this->t('Users with the <em>Administer blocks</em> permission can create, edit, and delete content blocks of each defined block type, from the <a href=":block-library">Content blocks page</a>. After creating a block, place it in a region from the <a href=":blocks">Block layout page</a>, just like blocks provided by other modules.', [
- ':blocks' => Url::fromRoute('block.admin_display')->toString(),
- ':block-library' => Url::fromRoute('entity.block_content.collection')->toString(),
+ $output .= '<dd>' . $this->t('Users with the <em>Administer block content</em> or <em>Create new content block</em> permissions for an individual block type are able to add content blocks. These can be created on the <a href=":add-content-block">Add content block page</a> or on the <em>Place block</em> modal on the <a href=":block-layout">Block Layout page</a> and are reusable across the entire site. Content blocks created in Layout Builder for a content type or individual node layouts are not reusable and also called inline blocks.', [
+ ':add-content-block' => Url::fromRoute('block_content.add_page')->toString(),
+ ':block-layout' => Url::fromRoute('block.admin_display')->toString(),
]) . '</dd>';
$output .= '</dl>';
return $output;
diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
index f09740667e32..2e9be38dacb6 100644
--- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
+++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
@@ -360,8 +360,11 @@ class FileItem extends EntityReferenceItem {
$dirname = static::doGetUploadLocation($settings);
\Drupal::service('file_system')->prepareDirectory($dirname, FileSystemInterface::CREATE_DIRECTORY);
+ // Ensure directory ends with a slash.
+ $dirname .= str_ends_with($dirname, '/') ? '' : '/';
+
// Generate a file entity.
- $destination = $dirname . '/' . $random->name(10, TRUE) . '.txt';
+ $destination = $dirname . $random->name(10) . '.txt';
$data = $random->paragraphs(3);
/** @var \Drupal\file\FileRepositoryInterface $file_repository */
$file_repository = \Drupal::service('file.repository');
diff --git a/core/modules/file/tests/src/Kernel/FileItemTest.php b/core/modules/file/tests/src/Kernel/FileItemTest.php
index 09a28b68f0f1..c01cf28a1151 100644
--- a/core/modules/file/tests/src/Kernel/FileItemTest.php
+++ b/core/modules/file/tests/src/Kernel/FileItemTest.php
@@ -6,12 +6,14 @@ namespace Drupal\Tests\file\Kernel;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
+use Drupal\file\Plugin\Field\FieldType\FileItem;
use Drupal\user\Entity\Role;
/**
@@ -155,6 +157,48 @@ class FileItemTest extends FieldKernelTestBase {
\Drupal::service('renderer')->renderRoot($output);
$this->assertTrue(!empty($entity->file_test->entity));
$this->assertEquals($uri, $entity->file_test->entity->getFileUri());
+
+ // Test file URIs with empty and custom directories.
+ $this->validateFileUriForDirectory(
+ '', 'public://'
+ );
+ $this->validateFileUriForDirectory(
+ 'custom_directory/subdir', 'public://custom_directory/subdir/'
+ );
+ }
+
+ /**
+ * Tests file URIs generated for a given file directory.
+ *
+ * @param string $file_directory
+ * The file directory to test (e.g., empty or 'custom_directory/subdir').
+ * @param string $expected_start
+ * The expected starting string of the file URI (e.g., 'public://').
+ */
+ private function validateFileUriForDirectory(string $file_directory, string $expected_start): void {
+ // Mock the field definition with the specified file directory.
+ $definition = $this->createMock(FieldDefinitionInterface::class);
+ $definition->expects($this->any())
+ ->method('getSettings')
+ ->willReturn([
+ 'file_extensions' => 'txt',
+ 'file_directory' => $file_directory,
+ 'uri_scheme' => 'public',
+ 'display_default' => TRUE,
+ ]);
+
+ // Generate a sample file value.
+ $value = FileItem::generateSampleValue($definition);
+ $this->assertNotEmpty($value);
+
+ // Load the file entity and get its URI.
+ $fid = $value['target_id'];
+ $file = File::load($fid);
+ $fileUri = $file->getFileUri();
+
+ // Verify the file URI starts with the expected protocol and structure.
+ $this->assertStringStartsWith($expected_start, $fileUri);
+ $this->assertMatchesRegularExpression('#^' . preg_quote($expected_start, '#') . '[^/]+#', $fileUri);
}
}
diff --git a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
index 7ed6b0d3371c..72937d4e79a3 100644
--- a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
+++ b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
@@ -389,7 +389,9 @@ class ImageItem extends FileItem {
$image->setFileName($file_system->basename($path));
$destination_dir = static::doGetUploadLocation($settings);
$file_system->prepareDirectory($destination_dir, FileSystemInterface::CREATE_DIRECTORY);
- $destination = $destination_dir . '/' . basename($path);
+ // Ensure directory ends with a slash.
+ $destination_dir .= str_ends_with($destination_dir, '/') ? '' : '/';
+ $destination = $destination_dir . basename($path);
$file = \Drupal::service('file.repository')->move($image, $destination);
$images[$extension][$min_resolution][$max_resolution][$file->id()] = $file;
}
diff --git a/core/modules/image/tests/src/Kernel/ImageItemTest.php b/core/modules/image/tests/src/Kernel/ImageItemTest.php
index 0598b25e9db8..55f2503686d4 100644
--- a/core/modules/image/tests/src/Kernel/ImageItemTest.php
+++ b/core/modules/image/tests/src/Kernel/ImageItemTest.php
@@ -9,6 +9,7 @@ use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
@@ -19,6 +20,7 @@ use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
+use Drupal\image\Plugin\Field\FieldType\ImageItem;
use Drupal\user\Entity\Role;
/**
@@ -208,6 +210,14 @@ class ImageItemTest extends FieldKernelTestBase {
}
/**
+ * Tests image URIs for empty and custom directories.
+ */
+ public function testImageUriDirectories(): void {
+ $this->validateImageUriForDirectory('', 'public://');
+ $this->validateImageUriForDirectory('custom_directory/subdir', 'public://custom_directory/subdir/');
+ }
+
+ /**
* Tests display_default.
*/
public function testDisplayDefaultValue(): void {
@@ -225,4 +235,36 @@ class ImageItemTest extends FieldKernelTestBase {
self::assertEquals(1, $form_state->getValue(['image_test', 0, 'display']));
}
+ /**
+ * Validates the image file URI generated for a given file directory.
+ *
+ * @param string $file_directory
+ * The file directory to test (e.g., empty or 'custom_directory/subdir').
+ * @param string $expected_start
+ * The expected starting string of the file URI (e.g., 'public://').
+ */
+ private function validateImageUriForDirectory(string $file_directory, string $expected_start): void {
+ // Mock the field definition with the specified file directory.
+ $definition = $this->createMock(FieldDefinitionInterface::class);
+ $definition->expects($this->any())
+ ->method('getSettings')
+ ->willReturn([
+ 'file_extensions' => 'jpg',
+ 'file_directory' => $file_directory,
+ 'uri_scheme' => 'public',
+ ]);
+ // Generate sample value and check the URI format.
+ $value = ImageItem::generateSampleValue($definition);
+ $this->assertNotEmpty($value);
+
+ // Load the file entity and get its URI.
+ $fid = $value['target_id'];
+ $file = File::load($fid);
+ $fileUri = $file->getFileUri();
+
+ // Verify the file URI starts with the expected protocol and structure.
+ $this->assertStringStartsWith($expected_start, $fileUri);
+ $this->assertMatchesRegularExpression('#^' . preg_quote($expected_start, '#') . '[^/]+#', $fileUri);
+ }
+
}
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index 457d37890fa0..d3377f3773eb 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -21,12 +21,6 @@ function locale_install(): void {
\Drupal::configFactory()->getEditable('locale.settings')->set('translation.path', $directory)->save();
}
\Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
-
- $t_args = [
- ':translate_status' => base_path() . 'admin/reports/translations/check?destination=' . urlencode(base_path() . 'admin/reports/translations'),
- ];
- $message = t('Check <a href=":translate_status">available translations</a> for your language(s).', $t_args);
- \Drupal::messenger()->addStatus($message);
}
/**
diff --git a/core/modules/locale/tests/src/Functional/LocaleInstallTest.php b/core/modules/locale/tests/src/Functional/LocaleInstallTest.php
deleted file mode 100644
index fc81fdb19b52..000000000000
--- a/core/modules/locale/tests/src/Functional/LocaleInstallTest.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\locale\Functional;
-
-use Drupal\Tests\BrowserTestBase;
-
-/**
- * Test installation of Locale module.
- *
- * @group locale
- */
-class LocaleInstallTest extends BrowserTestBase {
-
- /**
- * {@inheritdoc}
- */
- protected static $modules = [
- 'system',
- 'file',
- 'language',
- ];
-
- /**
- * {@inheritdoc}
- */
- protected $defaultTheme = 'stark';
-
- /**
- * Tests Locale install message.
- */
- public function testLocaleInstallMessage(): void {
- $admin_user = $this->drupalCreateUser([
- 'access administration pages',
- 'administer modules',
- ]);
- $this->drupalLogin($admin_user);
-
- $edit = [];
- $edit['modules[locale][enable]'] = 'locale';
- $this->drupalGet('admin/modules');
- $this->submitForm($edit, 'Install');
-
- $this->assertSession()->statusMessageContains('available translations', 'status');
- }
-
-}
diff --git a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php
index ae157ab56624..fa6bda652deb 100644
--- a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php
+++ b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php
@@ -88,26 +88,10 @@ class FileTransferTest extends BrowserTestBase {
*/
public function testJail(): void {
$source = $this->_buildFakeModule();
-
- // This convoluted piece of code is here because our testing framework does
- // not support expecting exceptions.
- $got_it = FALSE;
- try {
- $this->testConnection->copyDirectory($source, sys_get_temp_dir());
- }
- catch (FileTransferException) {
- $got_it = TRUE;
- }
- $this->assertTrue($got_it, 'Was not able to copy a directory outside of the jailed area.');
-
- $got_it = TRUE;
- try {
- $this->testConnection->copyDirectory($source, $this->root . '/' . PublicStream::basePath());
- }
- catch (FileTransferException) {
- $got_it = FALSE;
- }
- $this->assertTrue($got_it, 'Was able to copy a directory inside of the jailed area');
+ $this->testConnection->copyDirectory($source, $this->root . '/' . PublicStream::basePath());
+ $this->expectException(FileTransferException::class);
+ $this->expectExceptionMessage('@directory is outside of the @jail');
+ $this->testConnection->copyDirectory($source, sys_get_temp_dir());
}
}
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
index 071202256fbc..027d2933a995 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
@@ -50,7 +50,7 @@ class OpenTelemetryFrontPagePerformanceTest extends PerformanceTestBase {
$expected = [
'QueryCount' => 381,
- 'CacheGetCount' => 471,
+ 'CacheGetCount' => 472,
'CacheSetCount' => 467,
'CacheDeleteCount' => 0,
'CacheTagLookupQueryCount' => 49,
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
index dd26c2f12638..3045b218bc41 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
@@ -136,9 +136,12 @@ class OpenTelemetryNodePagePerformanceTest extends PerformanceTestBase {
protected function testNodePageWarmCache(): void {
// First of all visit the node page to ensure the image style exists.
$this->drupalGet('node/1');
+ // Allow time for the image style and asset aggregate requests to finish.
+ sleep(1);
$this->clearCaches();
// Now visit a different node page to warm non-path-specific caches.
$this->drupalGet('node/2');
+ sleep(1);
$performance_data = $this->collectPerformanceData(function () {
$this->drupalGet('node/1');
}, 'umamiNodePageWarmCache');
diff --git a/core/tests/Drupal/Tests/Component/Gettext/PoItemTest.php b/core/tests/Drupal/Tests/Component/Gettext/PoItemTest.php
new file mode 100644
index 000000000000..7026a2ceac5a
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Gettext/PoItemTest.php
@@ -0,0 +1,85 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\Component\Gettext;
+
+use Drupal\Component\Gettext\PoItem;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Gettext\PoItem
+ * @group Gettext
+ */
+class PoItemTest extends TestCase {
+
+ /**
+ * @return array
+ * - Source string
+ * - Context (optional)
+ * - Translated string (optional)
+ * - Expected value
+ */
+ public static function providerStrings(): array {
+ // cSpell:disable
+ return [
+ [
+ '',
+ NULL,
+ NULL,
+ 'msgid ""' . "\n" . 'msgstr ""' . "\n\n",
+ ],
+ // Translated String without contesxt.
+ [
+ 'Next',
+ NULL,
+ 'Suivant',
+ 'msgid "Next"' . "\n" . 'msgstr "Suivant"' . "\n\n",
+ ],
+ // Translated string with context.
+ [
+ 'Apr',
+ 'Abbreviated month name',
+ 'Avr',
+ 'msgctxt "Abbreviated month name"' . "\n" . 'msgid "Apr"' . "\n" . 'msgstr "Avr"' . "\n\n",
+ ],
+ // Translated string with placeholder.
+ [
+ '%email is not a valid email address.',
+ NULL,
+ '%email n\'est pas une adresse de courriel valide.',
+ 'msgid "%email is not a valid email address."' . "\n" . 'msgstr "%email n\'est pas une adresse de courriel valide."' . "\n\n",
+ ],
+ // Translated Plural String without context.
+ [
+ ['Installed theme', 'Installed themes'],
+ NULL,
+ ['Thème installé', 'Thèmes installés'],
+ 'msgid "Installed theme"' . "\n" . 'msgid_plural "Installed themes"' . "\n" . 'msgstr[0] "Thème installé"' . "\n" . 'msgstr[1] "Thèmes installés"' . "\n\n",
+ ],
+ ];
+ // cSpell:enable
+ }
+
+ /**
+ * @dataProvider providerStrings
+ */
+ public function testFormat($source, $context, $translation, $expected): void {
+ $item = new PoItem();
+
+ $item->setSource($source);
+
+ if (is_array($source)) {
+ $item->setPlural(TRUE);
+ }
+ if (!empty($context)) {
+ $item->setContext($context);
+ }
+ if (!empty($translation)) {
+ $item->setTranslation($translation);
+ }
+
+ $this->assertEquals($expected, (string) $item);
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 9c68273365b3..e4fbfa60caf7 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1126,6 +1126,68 @@ class RendererTest extends RendererTestBase {
$this->assertFalse($this->renderer->hasRenderContext());
}
+ /**
+ * @covers ::executeInRenderContext
+ */
+ public function testExecuteInRenderContext(): void {
+ $return = $this->renderer->executeInRenderContext(new RenderContext(), function () {
+ $fiber_callback = function () {
+
+ // Create a #pre_render callback that renders a render array in
+ // isolation. This has its own #pre_render callback that calls
+ // Fiber::suspend(). This ensures that suspending a Fiber within
+ // multiple nested calls to ::executeInRenderContext() doesn't
+ // allow render context to get out of sync. This simulates similar
+ // conditions to BigPipe placeholder rendering.
+ $fiber_suspend_pre_render = function ($elements) {
+ $fiber_suspend = function ($elements) {
+ \Fiber::suspend();
+ return $elements;
+ };
+ $build = [
+ 'foo' => [
+ '#markup' => 'foo',
+ '#pre_render' => [$fiber_suspend],
+ ],
+ ];
+ $markup = $this->renderer->renderInIsolation($build);
+ $elements['#markup'] = $markup;
+ return $elements;
+ };
+ $build = [
+ 'foo' => [
+ '#pre_render' => [$fiber_suspend_pre_render],
+ ],
+ ];
+ return $this->renderer->render($build);
+ };
+
+ // Build an array of two fibers that executes the code defined above. This
+ // ensures that Fiber::suspend() is called from within two
+ // ::renderInIsolation() calls without either having been completed.
+ $fibers = [];
+ foreach ([0, 1] as $key) {
+ $fibers[] = new \Fiber(static fn () => $fiber_callback());
+ }
+ while ($fibers) {
+ foreach ($fibers as $key => $fiber) {
+ if ($fiber->isTerminated()) {
+ unset($fibers[$key]);
+ continue;
+ }
+ if ($fiber->isSuspended()) {
+ $fiber->resume();
+ }
+ else {
+ $fiber->start();
+ }
+ }
+ }
+ return $fiber->getReturn();
+ });
+ $this->assertEquals(Markup::create('foo'), $return);
+ }
+
}
/**