From 8fd4c07e76cd5b768a4869692ee3869b79fb89c1 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 17 Apr 2026 14:30:22 +1000 Subject: [PATCH 1/2] Excluded D6/D7 from coverage and added tests to raise D8+ coverage to 39%. --- .github/workflows/ci.yml | 2 +- phpunit.xml.dist | 8 + .../Tests/Driver/BlackboxDriverTest.php | 105 ++++++++++ .../UnsupportedDriverActionExceptionTest.php | 50 +++++ .../Fields/Drupal8/AddressHandlerTest.php | 188 ++++++++++++++++++ .../Fields/Drupal8/DaterangeHandlerTest.php | 66 ++++++ .../Fields/Drupal8/DatetimeHandlerTest.php | 82 ++++++++ .../Fields/Drupal8/DefaultHandlerTest.php | 25 +++ .../Drupal8/EntityReferenceHandlerTest.php | 136 +++++++++++++ .../Driver/Fields/Drupal8/FileHandlerTest.php | 108 ++++++++++ .../Fields/Drupal8/ImageHandlerTest.php | 92 +++++++++ .../Driver/Fields/Drupal8/ListHandlerTest.php | 102 ++++++++++ .../Drupal8/SupportedImageHandlerTest.php | 147 ++++++++++++++ .../TaxonomyTermReferenceHandlerTest.php | 80 ++++++++ .../Drupal8/TextWithSummaryHandlerTest.php | 26 +++ 15 files changed, 1216 insertions(+), 1 deletion(-) create mode 100644 tests/Drupal/Tests/Driver/BlackboxDriverTest.php create mode 100644 tests/Drupal/Tests/Driver/Exception/UnsupportedDriverActionExceptionTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/AddressHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/DaterangeHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/DatetimeHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/DefaultHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/EntityReferenceHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/FileHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/ImageHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/ListHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/SupportedImageHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/TaxonomyTermReferenceHandlerTest.php create mode 100644 tests/Drupal/Tests/Driver/Fields/Drupal8/TextWithSummaryHandlerTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd47ab60..e0028639 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: - master env: - CI_COVERAGE_THRESHOLD: '10' + CI_COVERAGE_THRESHOLD: '35' jobs: tests: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6afc9e22..d4ad9046 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,6 +16,14 @@ ./src/Drupal/ + + ./src/Drupal/Driver/Cores/Drupal6 + ./src/Drupal/Driver/Cores/Drupal7 + ./src/Drupal/Driver/Fields/Drupal6 + ./src/Drupal/Driver/Fields/Drupal7 + ./src/Drupal/Driver/Cores/Drupal6.php + ./src/Drupal/Driver/Cores/Drupal7.php + diff --git a/tests/Drupal/Tests/Driver/BlackboxDriverTest.php b/tests/Drupal/Tests/Driver/BlackboxDriverTest.php new file mode 100644 index 00000000..a7ae53a7 --- /dev/null +++ b/tests/Drupal/Tests/Driver/BlackboxDriverTest.php @@ -0,0 +1,105 @@ +assertTrue($driver->isBootstrapped()); + } + + /** + * Tests that bootstrap() is a no-op. + */ + public function testBootstrapIsNoop() { + $driver = new BlackboxDriver(); + $this->assertNull($driver->bootstrap()); + } + + /** + * Tests that isField() always returns FALSE in the blackbox driver. + */ + public function testIsFieldReturnsFalse() { + $driver = new BlackboxDriver(); + $this->assertFalse($driver->isField('node', 'field_body')); + } + + /** + * Tests that isBaseField() always returns FALSE in the blackbox driver. + */ + public function testIsBaseFieldReturnsFalse() { + $driver = new BlackboxDriver(); + $this->assertFalse($driver->isBaseField('node', 'title')); + } + + /** + * Tests that unsupported driver actions throw the expected exception. + * + * @param string $method + * The BaseDriver method name to invoke. + * @param array $args + * Positional arguments to pass to the method. + * @param string $message_fragment + * A substring that must appear in the exception message. + * + * @dataProvider dataProviderUnsupportedActionsThrow + */ + public function testUnsupportedActionsThrow($method, array $args, $message_fragment) { + $driver = new BlackboxDriver(); + + $this->expectException(UnsupportedDriverActionException::class); + $this->expectExceptionMessageMatches('/' . preg_quote($message_fragment, '/') . '/'); + + $driver->$method(...$args); + } + + /** + * Data provider listing every BaseDriver method that must be unsupported. + */ + public static function dataProviderUnsupportedActionsThrow() { + $user = new \stdClass(); + $term = new \stdClass(); + $entity = new \stdClass(); + + return [ + 'getRandom' => ['getRandom', [], 'generate random'], + 'userCreate' => ['userCreate', [$user], 'create users'], + 'userDelete' => ['userDelete', [$user], 'delete users'], + 'processBatch' => ['processBatch', [], 'process batch actions'], + 'userAddRole' => ['userAddRole', [$user, 'editor'], 'add roles'], + 'fetchWatchdog' => ['fetchWatchdog', [], 'access watchdog entries'], + 'clearCache' => ['clearCache', [], 'clear Drupal caches'], + 'clearStaticCaches' => ['clearStaticCaches', [], 'clear static caches'], + 'createNode' => ['createNode', [new \stdClass()], 'create nodes'], + 'nodeDelete' => ['nodeDelete', [new \stdClass()], 'delete nodes'], + 'runCron' => ['runCron', [], 'run cron'], + 'createTerm' => ['createTerm', [$term], 'create terms'], + 'termDelete' => ['termDelete', [$term], 'delete terms'], + 'roleCreate' => ['roleCreate', [[]], 'create roles'], + 'roleDelete' => ['roleDelete', [1], 'delete roles'], + 'configGet' => ['configGet', ['system.site', 'name'], 'config get'], + 'configSet' => ['configSet', ['system.site', 'name', 'v'], 'config set'], + 'createEntity' => ['createEntity', ['node', $entity], 'create entities using the generic Entity API'], + 'entityDelete' => ['entityDelete', ['node', $entity], 'delete entities using the generic Entity API'], + 'startCollectingMail' => ['startCollectingMail', [], 'work with mail'], + 'stopCollectingMail' => ['stopCollectingMail', [], 'work with mail'], + 'getMail' => ['getMail', [], 'work with mail'], + 'clearMail' => ['clearMail', [], 'work with mail'], + 'sendMail' => ['sendMail', ['body', 'subject', 'to', 'en'], 'work with mail'], + 'moduleInstall' => ['moduleInstall', ['node'], 'install modules'], + 'moduleUninstall' => ['moduleUninstall', ['node'], 'uninstall modules'], + ]; + } + +} diff --git a/tests/Drupal/Tests/Driver/Exception/UnsupportedDriverActionExceptionTest.php b/tests/Drupal/Tests/Driver/Exception/UnsupportedDriverActionExceptionTest.php new file mode 100644 index 00000000..37b0a925 --- /dev/null +++ b/tests/Drupal/Tests/Driver/Exception/UnsupportedDriverActionExceptionTest.php @@ -0,0 +1,50 @@ +createMock(DriverInterface::class); + $driver_class = get_class($driver); + + $exception = new UnsupportedDriverActionException('Action %s is not supported.', $driver); + + $this->assertSame(sprintf('Action %s is not supported.', $driver_class), $exception->getMessage()); + } + + /** + * Tests that the driver is accessible via getDriver(). + */ + public function testGetDriverReturnsConstructorArgument() { + $driver = $this->createMock(DriverInterface::class); + + $exception = new UnsupportedDriverActionException('%s', $driver); + + $this->assertSame($driver, $exception->getDriver()); + } + + /** + * Tests that code and previous exception are propagated to the parent. + */ + public function testCodeAndPreviousArePropagated() { + $driver = $this->createMock(DriverInterface::class); + $previous = new \RuntimeException('root cause'); + + $exception = new UnsupportedDriverActionException('%s', $driver, 42, $previous); + + $this->assertSame(42, $exception->getCode()); + $this->assertSame($previous, $exception->getPrevious()); + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/AddressHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/AddressHandlerTest.php new file mode 100644 index 00000000..1d05508f --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/AddressHandlerTest.php @@ -0,0 +1,188 @@ +createHandler(); + + $result = $handler->expand(['Just a name']); + + $this->assertSame([['given_name' => 'Just a name']], $result); + } + + /** + * Tests that keyed values are preserved and defaults filled in. + */ + public function testKeyedValuesAreKeptAndDefaultCountryApplied() { + $handler = $this->createHandler(); + + $result = $handler->expand([ + [ + 'given_name' => 'John', + 'family_name' => 'Doe', + ], + ]); + + $this->assertSame([ + [ + 'given_name' => 'John', + 'family_name' => 'Doe', + 'country_code' => 'AU', + ], + ], $result); + } + + /** + * Tests that numeric indices are assigned in the order of visible fields. + */ + public function testNumericIndicesMapToVisibleFieldOrder() { + $handler = $this->createHandler(); + + $result = $handler->expand([ + ['John', 'Doe'], + ]); + + $this->assertSame([ + [ + 'given_name' => 'John', + 'additional_name' => 'Doe', + 'country_code' => 'AU', + ], + ], $result); + } + + /** + * Tests that hidden fields are removed from the visible field list. + */ + public function testHiddenFieldsAreSkippedForNumericIndices() { + $handler = $this->createHandler([ + 'givenName' => ['override' => 'hidden'], + 'additionalName' => ['override' => 'hidden'], + ]); + + $result = $handler->expand([ + ['Doe'], + ]); + + $this->assertSame([ + [ + 'family_name' => 'Doe', + 'country_code' => 'AU', + ], + ], $result); + } + + /** + * Tests that non-hidden overrides do not alter the visible field list. + */ + public function testNonHiddenOverridesAreIgnored() { + $handler = $this->createHandler([ + 'givenName' => ['override' => 'optional'], + ]); + + $result = $handler->expand([ + ['John'], + ]); + + $this->assertSame([ + [ + 'given_name' => 'John', + 'country_code' => 'AU', + ], + ], $result); + } + + /** + * Tests that excess numeric indices trigger an exception. + */ + public function testTooManyNumericIndicesThrows() { + $handler = $this->createHandler([ + 'additionalName' => ['override' => 'hidden'], + 'familyName' => ['override' => 'hidden'], + 'organization' => ['override' => 'hidden'], + 'addressLine1' => ['override' => 'hidden'], + 'addressLine2' => ['override' => 'hidden'], + 'postalCode' => ['override' => 'hidden'], + 'sortingCode' => ['override' => 'hidden'], + 'locality' => ['override' => 'hidden'], + 'administrativeArea' => ['override' => 'hidden'], + 'countryCode' => ['override' => 'hidden'], + ]); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Too many address sub-field values supplied; only 1 visible fields available.'); + + $handler->expand([ + ['John', 'Extra'], + ]); + } + + /** + * Tests that a non-numeric, unknown sub-field key throws an exception. + */ + public function testUnknownKeyThrows() { + $handler = $this->createHandler(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Invalid address sub-field key: unknown_key.'); + + $handler->expand([ + ['unknown_key' => 'value'], + ]); + } + + /** + * Tests that an explicit country_code is not overridden by the default. + */ + public function testExplicitCountryCodeIsPreserved() { + $handler = $this->createHandler(); + + $result = $handler->expand([ + ['country_code' => 'US'], + ]); + + $this->assertSame([['country_code' => 'US']], $result); + } + + /** + * Creates an AddressHandler with an injected fieldConfig mock. + * + * @param array $field_overrides + * Address field override settings. + * @param array $available_countries + * Available countries keyed by code. + * + * @return \Drupal\Driver\Fields\Drupal8\AddressHandler + * Handler instance with fieldConfig populated. + */ + protected function createHandler(array $field_overrides = [], array $available_countries = ['AU' => 'AU']) { + $field_config = $this->getMockBuilder(\stdClass::class) + ->addMethods(['getSettings']) + ->getMock(); + $field_config->method('getSettings')->willReturn([ + 'field_overrides' => $field_overrides, + 'available_countries' => $available_countries, + ]); + + $reflection = new \ReflectionClass(AddressHandler::class); + $handler = $reflection->newInstanceWithoutConstructor(); + + $property = new \ReflectionProperty(AddressHandler::class, 'fieldConfig'); + $property->setAccessible(TRUE); + $property->setValue($handler, $field_config); + + return $handler; + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/DaterangeHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/DaterangeHandlerTest.php new file mode 100644 index 00000000..b4265ce5 --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/DaterangeHandlerTest.php @@ -0,0 +1,66 @@ +createMock(ImmutableConfig::class); + $config->method('get')->with('timezone.default')->willReturn('UTC'); + + $config_factory = $this->createMock(ConfigFactoryInterface::class); + $config_factory->method('get')->with('system.date')->willReturn($config); + + $container = new ContainerBuilder(); + $container->set('config.factory', $config_factory); + \Drupal::setContainer($container); + } + + /** + * {@inheritdoc} + */ + protected function tearDown(): void { + \Drupal::unsetContainer(); + parent::tearDown(); + } + + /** + * Tests that empty start/end values produce NULL entries. + */ + public function testExpandHandlesEmptyValuesAsNull() { + $reflection = new \ReflectionClass(DaterangeHandler::class); + $handler = $reflection->newInstanceWithoutConstructor(); + + $result = $handler->expand([ + ['value' => NULL, 'end_value' => NULL], + [NULL, NULL], + ]); + + $this->assertSame([ + ['value' => NULL, 'end_value' => NULL], + ['value' => NULL, 'end_value' => NULL], + ], $result); + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/DatetimeHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/DatetimeHandlerTest.php new file mode 100644 index 00000000..eb23f742 --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/DatetimeHandlerTest.php @@ -0,0 +1,82 @@ +createMock(ImmutableConfig::class); + $config->method('get')->with('timezone.default')->willReturn('UTC'); + + $config_factory = $this->createMock(ConfigFactoryInterface::class); + $config_factory->method('get')->with('system.date')->willReturn($config); + + $container = new ContainerBuilder(); + $container->set('config.factory', $config_factory); + \Drupal::setContainer($container); + } + + /** + * {@inheritdoc} + */ + protected function tearDown(): void { + \Drupal::unsetContainer(); + parent::tearDown(); + } + + /** + * Tests that empty strings and NULLs pass through as NULL values. + */ + public function testExpandPreservesEmptyValuesAsNull() { + $handler = $this->createHandler('datetime'); + + $result = $handler->expand(['', NULL]); + + $this->assertSame([NULL, NULL], $result); + } + + /** + * Creates a DatetimeHandler with a fieldInfo mock returning datetime_type. + */ + protected function createHandler($datetime_type) { + $field_info = $this->getMockBuilder(\stdClass::class) + ->addMethods(['getSetting']) + ->getMock(); + $field_info->method('getSetting') + ->with('datetime_type') + ->willReturn($datetime_type); + + $reflection = new \ReflectionClass(DatetimeHandler::class); + $handler = $reflection->newInstanceWithoutConstructor(); + + $property = new \ReflectionProperty(DatetimeHandler::class, 'fieldInfo'); + $property->setAccessible(TRUE); + $property->setValue($handler, $field_info); + + return $handler; + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/DefaultHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/DefaultHandlerTest.php new file mode 100644 index 00000000..8ffc4e80 --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/DefaultHandlerTest.php @@ -0,0 +1,25 @@ +newInstanceWithoutConstructor(); + + $values = ['one', 'two', 3]; + + $this->assertSame($values, $handler->expand($values)); + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/EntityReferenceHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/EntityReferenceHandlerTest.php new file mode 100644 index 00000000..4f66aa5a --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/EntityReferenceHandlerTest.php @@ -0,0 +1,136 @@ +createHandler('node', []); + $this->setUpEmptyQueryContainer('node'); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage("No entity 'Missing' of type 'node' exists."); + + $handler->expand(['Missing']); + } + + /** + * Tests getTargetBundles() returns configured bundles. + */ + public function testGetTargetBundlesReturnsConfiguredBundles() { + $handler = $this->createHandler('node', ['article', 'page']); + + $reflection = new \ReflectionMethod(EntityReferenceHandler::class, 'getTargetBundles'); + $reflection->setAccessible(TRUE); + + $this->assertSame(['article', 'page'], $reflection->invoke($handler)); + } + + /** + * Tests getTargetBundles() returns NULL when none configured. + */ + public function testGetTargetBundlesReturnsNullWhenEmpty() { + $handler = $this->createHandler('node', []); + + $reflection = new \ReflectionMethod(EntityReferenceHandler::class, 'getTargetBundles'); + $reflection->setAccessible(TRUE); + + $this->assertNull($reflection->invoke($handler)); + } + + /** + * Creates an EntityReferenceHandler with mocked fieldInfo and fieldConfig. + */ + protected function createHandler($target_type, array $target_bundles) { + $field_info = $this->getMockBuilder(\stdClass::class) + ->addMethods(['getSetting']) + ->getMock(); + $field_info->method('getSetting') + ->with('target_type') + ->willReturn($target_type); + + $handler_settings = $target_bundles !== [] ? ['target_bundles' => $target_bundles] : []; + $field_config = $this->getMockBuilder(\stdClass::class) + ->addMethods(['getSettings']) + ->getMock(); + $field_config->method('getSettings') + ->willReturn(['handler_settings' => $handler_settings]); + + $reflection = new \ReflectionClass(EntityReferenceHandler::class); + $handler = $reflection->newInstanceWithoutConstructor(); + + $info_property = new \ReflectionProperty(EntityReferenceHandler::class, 'fieldInfo'); + $info_property->setAccessible(TRUE); + $info_property->setValue($handler, $field_info); + + $config_property = new \ReflectionProperty(EntityReferenceHandler::class, 'fieldConfig'); + $config_property->setAccessible(TRUE); + $config_property->setValue($handler, $field_config); + + return $handler; + } + + /** + * Sets up a Drupal container whose queries always return no results. + */ + protected function setUpEmptyQueryContainer($entity_type_id) { + $definition = $this->createMock(EntityTypeInterface::class); + $definition->method('getKey')->willReturnMap([ + ['id', 'nid'], + ['label', 'title'], + ['bundle', 'type'], + ]); + + $query = $this->createMock(QueryInterface::class); + $query->method('orConditionGroup')->willReturn($query); + $query->method('condition')->willReturnSelf(); + $query->method('accessCheck')->willReturnSelf(); + $query->method('execute')->willReturn([]); + + $storage = new class($query) { + + public function __construct(private $query) {} + + /** + * Returns the injected entity query. + */ + public function getQuery() { + return $this->query; + } + + }; + + $entity_type_manager = $this->createMock(EntityTypeManagerInterface::class); + $entity_type_manager->method('getDefinition')->willReturn($definition); + $entity_type_manager->method('getStorage')->willReturn($storage); + + $container = new ContainerBuilder(); + $container->set('entity_type.manager', $entity_type_manager); + \Drupal::setContainer($container); + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/FileHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/FileHandlerTest.php new file mode 100644 index 00000000..ae781a9e --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/FileHandlerTest.php @@ -0,0 +1,108 @@ +createHandler(); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Error reading file /tmp/drupal-driver-nonexistent-file.bin.'); + + @$handler->expand(['/tmp/drupal-driver-nonexistent-file.bin']); + } + + /** + * Tests that string paths produce a single target entry with defaults. + */ + public function testExpandHandlesStringValueWithDefaults() { + $path = $this->createTempFile('png'); + $this->setFileRepositoryWithReturnId(99); + + $handler = $this->createHandler(); + + $result = $handler->expand([$path]); + + $this->assertSame([ + ['target_id' => 99, 'display' => 1, 'description' => ''], + ], $result); + } + + /** + * Tests that keyed array values honour their explicit overrides. + */ + public function testExpandHandlesArrayValueWithOverrides() { + $path = $this->createTempFile('pdf'); + $this->setFileRepositoryWithReturnId(42); + + $handler = $this->createHandler(); + + $result = $handler->expand([ + [ + 'target_id' => $path, + 'display' => 0, + 'description' => 'Spec sheet', + ], + ]); + + $this->assertSame([ + ['target_id' => 42, 'display' => 0, 'description' => 'Spec sheet'], + ], $result); + } + + /** + * Creates a FileHandler that bypasses the parent constructor. + */ + protected function createHandler() { + $reflection = new \ReflectionClass(FileHandler::class); + return $reflection->newInstanceWithoutConstructor(); + } + + /** + * Creates a temporary file and returns its path. + */ + protected function createTempFile($extension) { + $path = tempnam(sys_get_temp_dir(), 'drupal-driver-') . '.' . $extension; + file_put_contents($path, 'fixture'); + return $path; + } + + /** + * Registers a mocked file.repository service returning a file with an ID. + */ + protected function setFileRepositoryWithReturnId($file_id) { + $file = $this->getMockBuilder(\stdClass::class) + ->addMethods(['id', 'save']) + ->getMock(); + $file->method('id')->willReturn($file_id); + + $repository = $this->getMockBuilder(\stdClass::class) + ->addMethods(['writeData']) + ->getMock(); + $repository->method('writeData')->willReturn($file); + + $container = new ContainerBuilder(); + $container->set('file.repository', $repository); + \Drupal::setContainer($container); + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/ImageHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/ImageHandlerTest.php new file mode 100644 index 00000000..5779dded --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/ImageHandlerTest.php @@ -0,0 +1,92 @@ +createHandler(); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Error reading file /tmp/drupal-driver-nonexistent-image.jpg.'); + + @$handler->expand(['/tmp/drupal-driver-nonexistent-image.jpg']); + } + + /** + * Tests that a readable path is expanded into an image field value. + */ + public function testExpandReturnsImageValueWithDefaultAltAndTitle() { + $path = tempnam(sys_get_temp_dir(), 'drupal-driver-') . '.jpg'; + file_put_contents($path, 'fixture'); + $this->setFileRepositoryWithReturnId(7); + + $handler = $this->createHandler(); + + $result = $handler->expand([$path]); + + $this->assertSame(['target_id' => 7, 'alt' => NULL, 'title' => NULL], $result); + } + + /** + * Tests that alt and title extras are propagated when provided. + */ + public function testExpandPropagatesAltAndTitleExtras() { + $path = tempnam(sys_get_temp_dir(), 'drupal-driver-') . '.jpg'; + file_put_contents($path, 'fixture'); + $this->setFileRepositoryWithReturnId(12); + + $handler = $this->createHandler(); + + $values = [$path, 'alt' => 'Alt text', 'title' => 'Title text']; + $result = $handler->expand($values); + + $this->assertSame(['target_id' => 12, 'alt' => 'Alt text', 'title' => 'Title text'], $result); + } + + /** + * Creates an ImageHandler that bypasses the parent constructor. + */ + protected function createHandler() { + $reflection = new \ReflectionClass(ImageHandler::class); + return $reflection->newInstanceWithoutConstructor(); + } + + /** + * Registers a mocked file.repository service returning a file with an ID. + */ + protected function setFileRepositoryWithReturnId($file_id) { + $file = $this->getMockBuilder(\stdClass::class) + ->addMethods(['id', 'save']) + ->getMock(); + $file->method('id')->willReturn($file_id); + + $repository = $this->getMockBuilder(\stdClass::class) + ->addMethods(['writeData']) + ->getMock(); + $repository->method('writeData')->willReturn($file); + + $container = new ContainerBuilder(); + $container->set('file.repository', $repository); + \Drupal::setContainer($container); + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/ListHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/ListHandlerTest.php new file mode 100644 index 00000000..a312c08e --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/ListHandlerTest.php @@ -0,0 +1,102 @@ +createHandler(ListStringHandler::class, [ + 'red' => 'Red', + 'green' => 'Green', + 'blue' => 'Blue', + ]); + + $this->assertSame(['green', 'blue'], $handler->expand(['Green', 'Blue'])); + } + + /** + * Tests that unmatched values fall through unchanged. + */ + public function testExpandReturnsOriginalValuesWhenNoMatch() { + $handler = $this->createHandler(ListStringHandler::class, [ + 'a' => 'Alpha', + ]); + + $this->assertSame(['Unknown'], $handler->expand(['Unknown'])); + } + + /** + * Tests that integer list values are mapped to keys. + */ + public function testIntegerListMapsLabelsToKeys() { + $handler = $this->createHandler(ListIntegerHandler::class, [ + 1 => 'One', + 2 => 'Two', + ]); + + $this->assertSame([2], $handler->expand(['Two'])); + } + + /** + * Tests that float list values are mapped to keys. + */ + public function testFloatListMapsLabelsToKeys() { + $handler = $this->createHandler(ListFloatHandler::class, [ + '1.5' => 'One and a half', + ]); + + $this->assertSame(['1.5'], $handler->expand(['One and a half'])); + } + + /** + * Tests that a scalar value is cast to an array before lookup. + */ + public function testExpandCastsScalarToArray() { + $handler = $this->createHandler(ListStringHandler::class, [ + 'k' => 'Label', + ]); + + $this->assertSame(['k'], $handler->expand('Label')); + } + + /** + * Creates a list handler with an injected field storage definition. + * + * @param string $class_name + * The handler class to instantiate. + * @param array $allowed_values + * The allowed_values map to inject via the fieldInfo setting. + * + * @return object + * The handler instance with fieldInfo populated. + */ + protected function createHandler($class_name, array $allowed_values) { + $field_info = $this->getMockBuilder(\stdClass::class) + ->addMethods(['getSetting']) + ->getMock(); + $field_info->method('getSetting') + ->with('allowed_values') + ->willReturn($allowed_values); + + $reflection = new \ReflectionClass($class_name); + $handler = $reflection->newInstanceWithoutConstructor(); + + $property = new \ReflectionProperty($class_name, 'fieldInfo'); + $property->setAccessible(TRUE); + $property->setValue($handler, $field_info); + + return $handler; + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/SupportedImageHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/SupportedImageHandlerTest.php new file mode 100644 index 00000000..ab5a1f86 --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/SupportedImageHandlerTest.php @@ -0,0 +1,147 @@ +createHandler(); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Error reading file'); + + @$handler->expand('/tmp/drupal-driver-nonexistent-supported-image.jpg'); + } + + /** + * Tests that a string input is normalised into a single-item result. + */ + public function testExpandNormalisesStringInputToSingleItem() { + $path = $this->createTempFile('jpg'); + $this->setFileRepositoryWithReturnId(3); + + $handler = $this->createHandler(); + + $result = $handler->expand($path); + + $this->assertSame([ + [ + 'target_id' => 3, + 'alt' => NULL, + 'title' => NULL, + 'caption_value' => NULL, + 'caption_format' => NULL, + 'attribution_value' => NULL, + 'attribution_format' => NULL, + ], + ], $result); + } + + /** + * Tests that caption and attribution metadata are preserved. + */ + public function testExpandPreservesCaptionAndAttributionMetadata() { + $path = $this->createTempFile('png'); + $this->setFileRepositoryWithReturnId(5); + + $handler = $this->createHandler(); + + $result = $handler->expand([ + [ + 'target_id' => $path, + 'alt' => 'Alt', + 'title' => 'Title', + 'caption_value' => 'Caption body', + 'caption_format' => 'basic_html', + 'attribution_value' => 'Photographer', + 'attribution_format' => 'plain_text', + ], + ]); + + $this->assertSame([ + [ + 'target_id' => 5, + 'alt' => 'Alt', + 'title' => 'Title', + 'caption_value' => 'Caption body', + 'caption_format' => 'basic_html', + 'attribution_value' => 'Photographer', + 'attribution_format' => 'plain_text', + ], + ], $result); + } + + /** + * Tests that a single array with target_id is wrapped as a single item. + */ + public function testExpandWrapsSingleKeyedArrayInput() { + $path = $this->createTempFile('jpg'); + $this->setFileRepositoryWithReturnId(8); + + $handler = $this->createHandler(); + + $result = $handler->expand([ + 'target_id' => $path, + 'alt' => 'An image', + ]); + + $this->assertCount(1, $result); + $this->assertSame(8, $result[0]['target_id']); + $this->assertSame('An image', $result[0]['alt']); + } + + /** + * Creates a SupportedImageHandler that bypasses the parent constructor. + */ + protected function createHandler() { + $reflection = new \ReflectionClass(SupportedImageHandler::class); + return $reflection->newInstanceWithoutConstructor(); + } + + /** + * Creates a temporary file with the given extension. + */ + protected function createTempFile($extension) { + $path = tempnam(sys_get_temp_dir(), 'drupal-driver-') . '.' . $extension; + file_put_contents($path, 'fixture'); + return $path; + } + + /** + * Registers a mocked file.repository service returning a file with an ID. + */ + protected function setFileRepositoryWithReturnId($file_id) { + $file = $this->getMockBuilder(\stdClass::class) + ->addMethods(['id', 'save']) + ->getMock(); + $file->method('id')->willReturn($file_id); + + $repository = $this->getMockBuilder(\stdClass::class) + ->addMethods(['writeData']) + ->getMock(); + $repository->method('writeData')->willReturn($file); + + $container = new ContainerBuilder(); + $container->set('file.repository', $repository); + \Drupal::setContainer($container); + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/TaxonomyTermReferenceHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/TaxonomyTermReferenceHandlerTest.php new file mode 100644 index 00000000..3859ebaf --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/TaxonomyTermReferenceHandlerTest.php @@ -0,0 +1,80 @@ +getMockBuilder(\stdClass::class) + ->addMethods(['id']) + ->getMock(); + $term->method('id')->willReturn(17); + + $this->setUpStorageWithResult(['Tag A' => [$term]]); + + $handler = $this->createHandler(); + + $this->assertSame([17], $handler->expand(['Tag A'])); + } + + /** + * Tests that an unknown term name raises an exception. + */ + public function testExpandThrowsWhenTermNotFound() { + $this->setUpStorageWithResult([]); + + $handler = $this->createHandler(); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage("No term 'Unknown' exists."); + + $handler->expand(['Unknown']); + } + + /** + * Creates a handler that bypasses the parent constructor. + */ + protected function createHandler() { + $reflection = new \ReflectionClass(TaxonomyTermReferenceHandler::class); + return $reflection->newInstanceWithoutConstructor(); + } + + /** + * Sets up a Drupal container that returns the supplied lookup results. + */ + protected function setUpStorageWithResult(array $lookup) { + $storage = $this->createMock(EntityStorageInterface::class); + $storage->method('loadByProperties') + ->willReturnCallback(fn($properties) => $lookup[$properties['name']] ?? []); + + $entity_type_manager = $this->createMock(EntityTypeManagerInterface::class); + $entity_type_manager->method('getStorage') + ->with('taxonomy_term') + ->willReturn($storage); + + $container = new ContainerBuilder(); + $container->set('entity_type.manager', $entity_type_manager); + \Drupal::setContainer($container); + } + +} diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/TextWithSummaryHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/TextWithSummaryHandlerTest.php new file mode 100644 index 00000000..c2238d1d --- /dev/null +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/TextWithSummaryHandlerTest.php @@ -0,0 +1,26 @@ + 'body text', 'summary' => 'short'], + ]; + + $this->assertSame($values, $handler->expand($values)); + } + +} From 1eb648cd0f78f47bbfb52598e3e7ee484e9ff75f Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 17 Apr 2026 14:55:35 +1000 Subject: [PATCH 2/2] Addressed code review: resolved drupal/core path via Composer and dropped deprecated addMethods mocks. --- .../Fields/Drupal8/AddressHandlerTest.php | 5 +- .../Fields/Drupal8/DaterangeHandlerTest.php | 37 ++++++++++++-- .../Fields/Drupal8/DatetimeHandlerTest.php | 50 ++++++++++++++++--- .../Drupal8/EntityReferenceHandlerTest.php | 10 ++-- .../Driver/Fields/Drupal8/FileHandlerTest.php | 44 ++++++++++++---- .../Fields/Drupal8/ImageHandlerTest.php | 44 ++++++++++++---- .../Driver/Fields/Drupal8/ListHandlerTest.php | 5 +- .../Drupal8/SupportedImageHandlerTest.php | 44 ++++++++++++---- .../TaxonomyTermReferenceHandlerTest.php | 14 ++++-- 9 files changed, 198 insertions(+), 55 deletions(-) diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/AddressHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/AddressHandlerTest.php index 1d05508f..614209e7 100644 --- a/tests/Drupal/Tests/Driver/Fields/Drupal8/AddressHandlerTest.php +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/AddressHandlerTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\Driver\Fields\Drupal8; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Driver\Fields\Drupal8\AddressHandler; use PHPUnit\Framework\TestCase; @@ -167,9 +168,7 @@ public function testExplicitCountryCodeIsPreserved() { * Handler instance with fieldConfig populated. */ protected function createHandler(array $field_overrides = [], array $available_countries = ['AU' => 'AU']) { - $field_config = $this->getMockBuilder(\stdClass::class) - ->addMethods(['getSettings']) - ->getMock(); + $field_config = $this->createMock(FieldDefinitionInterface::class); $field_config->method('getSettings')->willReturn([ 'field_overrides' => $field_overrides, 'available_countries' => $available_countries, diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/DaterangeHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/DaterangeHandlerTest.php index b4265ce5..01a989dc 100644 --- a/tests/Drupal/Tests/Driver/Fields/Drupal8/DaterangeHandlerTest.php +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/DaterangeHandlerTest.php @@ -2,16 +2,14 @@ namespace Drupal\Tests\Driver\Fields\Drupal8; +use Composer\InstalledVersions; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; use Drupal\Driver\Fields\Drupal8\DaterangeHandler; use PHPUnit\Framework\TestCase; -// The datetime module constants used downstream are not part of the default -// Drupal core autoload, so they must be loaded explicitly here. -require_once __DIR__ . '/../../../../../../drupal/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItemInterface.php'; - /** * Tests the DaterangeHandler field handler. * @@ -26,6 +24,10 @@ class DaterangeHandlerTest extends TestCase { protected function setUp(): void { parent::setUp(); + if (!$this->loadDatetimeModuleInterface()) { + $this->markTestSkipped('drupal/core datetime module classes are not available.'); + } + $config = $this->createMock(ImmutableConfig::class); $config->method('get')->with('timezone.default')->willReturn('UTC'); @@ -63,4 +65,31 @@ public function testExpandHandlesEmptyValuesAsNull() { ], $result); } + /** + * Loads the datetime module interface from the Composer-resolved core path. + */ + protected function loadDatetimeModuleInterface() { + if (interface_exists(DateTimeItemInterface::class)) { + return TRUE; + } + + if (!class_exists(InstalledVersions::class)) { + return FALSE; + } + + $core_path = InstalledVersions::getInstallPath('drupal/core'); + if ($core_path === NULL) { + return FALSE; + } + + $interface_file = $core_path . '/modules/datetime/src/Plugin/Field/FieldType/DateTimeItemInterface.php'; + if (!is_file($interface_file)) { + return FALSE; + } + + require_once $interface_file; + + return interface_exists(DateTimeItemInterface::class); + } + } diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/DatetimeHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/DatetimeHandlerTest.php index eb23f742..19df514b 100644 --- a/tests/Drupal/Tests/Driver/Fields/Drupal8/DatetimeHandlerTest.php +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/DatetimeHandlerTest.php @@ -2,17 +2,15 @@ namespace Drupal\Tests\Driver\Fields\Drupal8; +use Composer\InstalledVersions; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; use Drupal\Driver\Fields\Drupal8\DatetimeHandler; use PHPUnit\Framework\TestCase; -// The datetime module constants used by DatetimeHandler live outside the -// default Drupal core autoload, so they must be loaded explicitly here. -require_once __DIR__ . '/../../../../../../drupal/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItemInterface.php'; -require_once __DIR__ . '/../../../../../../drupal/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php'; - /** * Tests the DatetimeHandler field handler. * @@ -28,6 +26,10 @@ class DatetimeHandlerTest extends TestCase { protected function setUp(): void { parent::setUp(); + if (!$this->loadDatetimeModuleInterface()) { + $this->markTestSkipped('drupal/core datetime module classes are not available.'); + } + $config = $this->createMock(ImmutableConfig::class); $config->method('get')->with('timezone.default')->willReturn('UTC'); @@ -62,9 +64,7 @@ public function testExpandPreservesEmptyValuesAsNull() { * Creates a DatetimeHandler with a fieldInfo mock returning datetime_type. */ protected function createHandler($datetime_type) { - $field_info = $this->getMockBuilder(\stdClass::class) - ->addMethods(['getSetting']) - ->getMock(); + $field_info = $this->createMock(FieldStorageDefinitionInterface::class); $field_info->method('getSetting') ->with('datetime_type') ->willReturn($datetime_type); @@ -79,4 +79,38 @@ protected function createHandler($datetime_type) { return $handler; } + /** + * Loads datetime module classes from the Composer-resolved drupal/core path. + * + * The datetime module lives outside the default drupal/core PSR-4 namespace + * coverage, so the relevant files are loaded explicitly. Returns TRUE when + * DateTimeItemInterface is available after loading. + */ + protected function loadDatetimeModuleInterface() { + if (interface_exists(DateTimeItemInterface::class)) { + return TRUE; + } + + if (!class_exists(InstalledVersions::class)) { + return FALSE; + } + + $core_path = InstalledVersions::getInstallPath('drupal/core'); + if ($core_path === NULL) { + return FALSE; + } + + $interface_file = $core_path . '/modules/datetime/src/Plugin/Field/FieldType/DateTimeItemInterface.php'; + $item_file = $core_path . '/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php'; + + if (!is_file($interface_file) || !is_file($item_file)) { + return FALSE; + } + + require_once $interface_file; + require_once $item_file; + + return interface_exists(DateTimeItemInterface::class); + } + } diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/EntityReferenceHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/EntityReferenceHandlerTest.php index 4f66aa5a..551a4d04 100644 --- a/tests/Drupal/Tests/Driver/Fields/Drupal8/EntityReferenceHandlerTest.php +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/EntityReferenceHandlerTest.php @@ -6,6 +6,8 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\Query\QueryInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Driver\Fields\Drupal8\EntityReferenceHandler; use PHPUnit\Framework\TestCase; @@ -66,17 +68,13 @@ public function testGetTargetBundlesReturnsNullWhenEmpty() { * Creates an EntityReferenceHandler with mocked fieldInfo and fieldConfig. */ protected function createHandler($target_type, array $target_bundles) { - $field_info = $this->getMockBuilder(\stdClass::class) - ->addMethods(['getSetting']) - ->getMock(); + $field_info = $this->createMock(FieldStorageDefinitionInterface::class); $field_info->method('getSetting') ->with('target_type') ->willReturn($target_type); $handler_settings = $target_bundles !== [] ? ['target_bundles' => $target_bundles] : []; - $field_config = $this->getMockBuilder(\stdClass::class) - ->addMethods(['getSettings']) - ->getMock(); + $field_config = $this->createMock(FieldDefinitionInterface::class); $field_config->method('getSettings') ->willReturn(['handler_settings' => $handler_settings]); diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/FileHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/FileHandlerTest.php index ae781a9e..24a2d953 100644 --- a/tests/Drupal/Tests/Driver/Fields/Drupal8/FileHandlerTest.php +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/FileHandlerTest.php @@ -88,17 +88,43 @@ protected function createTempFile($extension) { /** * Registers a mocked file.repository service returning a file with an ID. + * + * Uses inline anonymous classes because FileInterface and + * FileRepositoryInterface ship with the file module rather than drupal/core + * and are therefore not guaranteed to be autoloadable in isolation. */ protected function setFileRepositoryWithReturnId($file_id) { - $file = $this->getMockBuilder(\stdClass::class) - ->addMethods(['id', 'save']) - ->getMock(); - $file->method('id')->willReturn($file_id); - - $repository = $this->getMockBuilder(\stdClass::class) - ->addMethods(['writeData']) - ->getMock(); - $repository->method('writeData')->willReturn($file); + $file = new class($file_id) { + + public function __construct(private $file_id) {} + + /** + * Returns the stored file entity ID. + */ + public function id() { + return $this->file_id; + } + + /** + * Saves the file entity (no-op in the test double). + */ + public function save() { + } + + }; + + $repository = new class($file) { + + public function __construct(private $file) {} + + /** + * Writes data to a destination and returns the stored file entity. + */ + public function writeData($data, $destination) { + return $this->file; + } + + }; $container = new ContainerBuilder(); $container->set('file.repository', $repository); diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/ImageHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/ImageHandlerTest.php index 5779dded..b1990ea0 100644 --- a/tests/Drupal/Tests/Driver/Fields/Drupal8/ImageHandlerTest.php +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/ImageHandlerTest.php @@ -72,17 +72,43 @@ protected function createHandler() { /** * Registers a mocked file.repository service returning a file with an ID. + * + * Uses inline anonymous classes because FileInterface and + * FileRepositoryInterface ship with the file module rather than drupal/core + * and are therefore not guaranteed to be autoloadable in isolation. */ protected function setFileRepositoryWithReturnId($file_id) { - $file = $this->getMockBuilder(\stdClass::class) - ->addMethods(['id', 'save']) - ->getMock(); - $file->method('id')->willReturn($file_id); - - $repository = $this->getMockBuilder(\stdClass::class) - ->addMethods(['writeData']) - ->getMock(); - $repository->method('writeData')->willReturn($file); + $file = new class($file_id) { + + public function __construct(private $file_id) {} + + /** + * Returns the stored file entity ID. + */ + public function id() { + return $this->file_id; + } + + /** + * Saves the file entity (no-op in the test double). + */ + public function save() { + } + + }; + + $repository = new class($file) { + + public function __construct(private $file) {} + + /** + * Writes data to a destination and returns the stored file entity. + */ + public function writeData($data, $destination) { + return $this->file; + } + + }; $container = new ContainerBuilder(); $container->set('file.repository', $repository); diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/ListHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/ListHandlerTest.php index a312c08e..a4816788 100644 --- a/tests/Drupal/Tests/Driver/Fields/Drupal8/ListHandlerTest.php +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/ListHandlerTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\Driver\Fields\Drupal8; +use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Driver\Fields\Drupal8\ListFloatHandler; use Drupal\Driver\Fields\Drupal8\ListIntegerHandler; use Drupal\Driver\Fields\Drupal8\ListStringHandler; @@ -82,9 +83,7 @@ public function testExpandCastsScalarToArray() { * The handler instance with fieldInfo populated. */ protected function createHandler($class_name, array $allowed_values) { - $field_info = $this->getMockBuilder(\stdClass::class) - ->addMethods(['getSetting']) - ->getMock(); + $field_info = $this->createMock(FieldStorageDefinitionInterface::class); $field_info->method('getSetting') ->with('allowed_values') ->willReturn($allowed_values); diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/SupportedImageHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/SupportedImageHandlerTest.php index ab5a1f86..3b9d7de4 100644 --- a/tests/Drupal/Tests/Driver/Fields/Drupal8/SupportedImageHandlerTest.php +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/SupportedImageHandlerTest.php @@ -127,17 +127,43 @@ protected function createTempFile($extension) { /** * Registers a mocked file.repository service returning a file with an ID. + * + * Uses inline anonymous classes because FileInterface and + * FileRepositoryInterface ship with the file module rather than drupal/core + * and are therefore not guaranteed to be autoloadable in isolation. */ protected function setFileRepositoryWithReturnId($file_id) { - $file = $this->getMockBuilder(\stdClass::class) - ->addMethods(['id', 'save']) - ->getMock(); - $file->method('id')->willReturn($file_id); - - $repository = $this->getMockBuilder(\stdClass::class) - ->addMethods(['writeData']) - ->getMock(); - $repository->method('writeData')->willReturn($file); + $file = new class($file_id) { + + public function __construct(private $file_id) {} + + /** + * Returns the stored file entity ID. + */ + public function id() { + return $this->file_id; + } + + /** + * Saves the file entity (no-op in the test double). + */ + public function save() { + } + + }; + + $repository = new class($file) { + + public function __construct(private $file) {} + + /** + * Writes data to a destination and returns the stored file entity. + */ + public function writeData($data, $destination) { + return $this->file; + } + + }; $container = new ContainerBuilder(); $container->set('file.repository', $repository); diff --git a/tests/Drupal/Tests/Driver/Fields/Drupal8/TaxonomyTermReferenceHandlerTest.php b/tests/Drupal/Tests/Driver/Fields/Drupal8/TaxonomyTermReferenceHandlerTest.php index 3859ebaf..03ca8f99 100644 --- a/tests/Drupal/Tests/Driver/Fields/Drupal8/TaxonomyTermReferenceHandlerTest.php +++ b/tests/Drupal/Tests/Driver/Fields/Drupal8/TaxonomyTermReferenceHandlerTest.php @@ -25,10 +25,16 @@ protected function tearDown(): void { * Tests that a matching term returns its ID. */ public function testExpandReturnsTermId() { - $term = $this->getMockBuilder(\stdClass::class) - ->addMethods(['id']) - ->getMock(); - $term->method('id')->willReturn(17); + $term = new class { + + /** + * Returns the term entity ID. + */ + public function id() { + return 17; + } + + }; $this->setUpStorageWithResult(['Tag A' => [$term]]);