Issue #3493070 by penyaskito, griffynh, wim leers, longwave, pdureau, effulgentsia, xjm, phenaproxima, mradcliffe, danielveza, lauriii, catch: SDC `enum` props should have translatable labels: use `meta:enum`
parent
6bdfd060b1
commit
9d55d1e60e
|
@ -2,6 +2,24 @@
|
|||
"$id": "https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata-full.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$defs": {
|
||||
"propDefinition": {
|
||||
"$ref": "http://json-schema.org/draft-04/schema#",
|
||||
"meta:enum": {
|
||||
"type": "object",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"patternProperties": {
|
||||
"additionalProperties": false,
|
||||
"^[a-zA-Z0-9_-]*$": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-translation-context": {
|
||||
"type": "string",
|
||||
"title": "Translation Context"
|
||||
}
|
||||
},
|
||||
"slotDefinition": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
@ -160,7 +178,7 @@
|
|||
]
|
||||
},
|
||||
"props": {
|
||||
"$ref": "http://json-schema.org/draft-04/schema#"
|
||||
"$ref": "#/$defs/propDefinition"
|
||||
},
|
||||
"slots": {
|
||||
"$ref": "metadata.schema.json#/$defs/slotDefinition"
|
||||
|
|
|
@ -2,6 +2,24 @@
|
|||
"$id": "https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$defs": {
|
||||
"propDefinition": {
|
||||
"$ref": "http://json-schema.org/draft-04/schema#",
|
||||
"meta:enum": {
|
||||
"type": "object",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"patternProperties": {
|
||||
"additionalProperties": false,
|
||||
"^[a-zA-Z0-9_-]*$": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-translation-context": {
|
||||
"type": "string",
|
||||
"title": "Translation Context"
|
||||
}
|
||||
},
|
||||
"slotDefinition": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
@ -208,7 +226,7 @@
|
|||
]
|
||||
},
|
||||
"props": {
|
||||
"$ref": "http://json-schema.org/draft-04/schema#"
|
||||
"$ref": "#/$defs/propDefinition"
|
||||
},
|
||||
"slots": {
|
||||
"$ref": "#/$defs/slotDefinition"
|
||||
|
|
|
@ -13,6 +13,11 @@ class ComponentMetadata {
|
|||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The ID of the component, in the form of provider:machine_name.
|
||||
*/
|
||||
public readonly string $id;
|
||||
|
||||
/**
|
||||
* The absolute path to the component directory.
|
||||
*
|
||||
|
@ -115,6 +120,7 @@ class ComponentMetadata {
|
|||
if (str_starts_with($path, $app_root)) {
|
||||
$path = substr($path, strlen($app_root));
|
||||
}
|
||||
$this->id = $metadata_info['id'];
|
||||
$this->mandatorySchemas = $enforce_schemas;
|
||||
$this->path = $path;
|
||||
|
||||
|
@ -149,7 +155,7 @@ class ComponentMetadata {
|
|||
private function parseSchemaInfo(array $metadata_info): ?array {
|
||||
if (empty($metadata_info['props'])) {
|
||||
if ($this->mandatorySchemas) {
|
||||
throw new InvalidComponentException(sprintf('The component "%s" does not provide schema information. Schema definitions are mandatory for components declared in modules. For components declared in themes, schema definitions are only mandatory if the "enforce_prop_schemas" key is set to "true" in the theme info file.', $metadata_info['id']));
|
||||
throw new InvalidComponentException(sprintf('The component "%s" does not provide schema information. Schema definitions are mandatory for components declared in modules. For components declared in themes, schema definitions are only mandatory if the "enforce_prop_schemas" key is set to "true" in the theme info file.', $this->id));
|
||||
}
|
||||
$schema = NULL;
|
||||
}
|
||||
|
@ -167,6 +173,12 @@ class ComponentMetadata {
|
|||
$schema_props = $metadata_info['props'];
|
||||
foreach ($schema_props['properties'] ?? [] as $name => $prop_schema) {
|
||||
$type = $prop_schema['type'] ?? '';
|
||||
if (isset($prop_schema['enum'], $prop_schema['meta:enum'])) {
|
||||
$enum_keys_diff = array_diff($prop_schema['enum'], array_keys($prop_schema['meta:enum']));
|
||||
if (!empty($enum_keys_diff)) {
|
||||
throw new InvalidComponentException(sprintf('The values for the %s prop enum in component %s must be defined in meta:enum.', $name, $this->id));
|
||||
}
|
||||
}
|
||||
$schema['properties'][$name]['type'] = array_unique([
|
||||
...(array) $type,
|
||||
'object',
|
||||
|
@ -197,6 +209,14 @@ class ComponentMetadata {
|
|||
* The normalized value object.
|
||||
*/
|
||||
public function normalize(): array {
|
||||
$meta = [];
|
||||
if (!empty($this->schema['properties'])) {
|
||||
foreach ($this->schema['properties'] as $prop_name => $prop_definition) {
|
||||
if (!empty($prop_definition['meta:enum'])) {
|
||||
$meta['properties'][$prop_name] = $this->getEnumOptions($prop_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
'path' => $this->path,
|
||||
'machineName' => $this->machineName,
|
||||
|
@ -204,7 +224,42 @@ class ComponentMetadata {
|
|||
'name' => $this->name,
|
||||
'group' => $this->group,
|
||||
'variants' => $this->variants,
|
||||
'meta' => $meta,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translated options labels from enumeration.
|
||||
*
|
||||
* @param string $propertyName
|
||||
* The enum property name.
|
||||
*
|
||||
* @return array<string, \Drupal\Core\StringTranslation\TranslatableMarkup>
|
||||
* An array with enum options as keys and the (non-rendered)
|
||||
* translated labels as values.
|
||||
*/
|
||||
public function getEnumOptions(string $propertyName): array {
|
||||
$options = [];
|
||||
if (isset($this->schema['properties'][$propertyName])) {
|
||||
$prop_definition = $this->schema['properties'][$propertyName];
|
||||
if (!empty($prop_definition['enum'])) {
|
||||
$translation_context = $prop_definition['x-translation-context'] ?? '';
|
||||
// We convert ['a', 'b'], into ['a' => t('a'), 'b' => t('b')].
|
||||
$options = array_combine(
|
||||
$prop_definition['enum'],
|
||||
// @phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
array_map(fn($value) => $this->t($value, [], ['context' => $translation_context]), $prop_definition['enum']),
|
||||
);
|
||||
if (!empty($prop_definition['meta:enum'])) {
|
||||
foreach ($prop_definition['meta:enum'] as $enum_value => $enum_label) {
|
||||
$options[$enum_value] =
|
||||
// @phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
$this->t($enum_label, [], ['context' => $translation_context]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ props:
|
|||
enum:
|
||||
- info
|
||||
- success
|
||||
meta:enum:
|
||||
info: Information
|
||||
success: Success
|
||||
slots:
|
||||
label:
|
||||
type: string
|
||||
|
|
|
@ -23,6 +23,9 @@ props:
|
|||
enum:
|
||||
- ellipsis
|
||||
- xs
|
||||
meta:enum:
|
||||
ellipsis: Ellipsis
|
||||
xs: 'Extra-small'
|
||||
extra_classes:
|
||||
type: array
|
||||
title: Extra classes.
|
||||
|
@ -43,6 +46,15 @@ props:
|
|||
- h5
|
||||
- h6
|
||||
- span
|
||||
meta:enum:
|
||||
h1: Heading 1
|
||||
h2: Heading 2
|
||||
h3: Heading 3
|
||||
h4: Heading 4
|
||||
h5: Heading 5
|
||||
h6: Heading 6
|
||||
span: Inline
|
||||
x-translation-context: HTML tag
|
||||
# Provide a default value
|
||||
default: h2
|
||||
icon:
|
||||
|
|
|
@ -37,6 +37,17 @@ props:
|
|||
- primary
|
||||
- small-offset
|
||||
- weight--400
|
||||
meta:enum:
|
||||
collapsible: Collapsible
|
||||
dark: Dark
|
||||
expand--down: Expands down
|
||||
expand--side: Expands to the side
|
||||
large: Large
|
||||
non-interactive: Non-Interactive
|
||||
primary: Primary
|
||||
small-offset: Small offset
|
||||
weight--400: Weight 400
|
||||
x-translation-context: Toolbar button modifiers
|
||||
extra_classes:
|
||||
type: array
|
||||
title: Extra classes.
|
||||
|
@ -53,6 +64,11 @@ props:
|
|||
- a
|
||||
- button
|
||||
- span
|
||||
meta:enum:
|
||||
a: Link
|
||||
button: Button
|
||||
span: Inline
|
||||
x-translation-context: HTML tag
|
||||
# Provide a default value
|
||||
default: button
|
||||
icon:
|
||||
|
|
|
@ -29,6 +29,10 @@ props:
|
|||
enum:
|
||||
- ''
|
||||
- _blank
|
||||
meta:enum:
|
||||
'': 'Open in same window'
|
||||
_blank: 'Open in a new window'
|
||||
x-translation-context: Banner link target
|
||||
image:
|
||||
title: Media Image
|
||||
description: Background image for the banner.
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<div {{ attributes }}>
|
||||
<div class="component--my-banner--header">
|
||||
<h3>{{ heading }}</h3>
|
||||
<p>CTA target selected value: {{ componentMetadata.meta.properties.ctaTarget[ctaTarget] }}</p>
|
||||
{% include 'sdc_test:my-cta' with { text: ctaText, href: ctaHref, target: ctaTarget } only %}
|
||||
</div>
|
||||
<div class="component--my-banner--body">
|
||||
|
|
|
@ -24,3 +24,7 @@ props:
|
|||
- power
|
||||
- like
|
||||
- external
|
||||
meta:enum:
|
||||
power: 'Power'
|
||||
like: 'Like'
|
||||
external: 'External'
|
||||
|
|
|
@ -17,12 +17,23 @@ props:
|
|||
type: string
|
||||
title: URL
|
||||
format: uri
|
||||
examples:
|
||||
- https://drupal.org
|
||||
target:
|
||||
type: string
|
||||
title: Target
|
||||
description: The target for opening the link.
|
||||
enum:
|
||||
- ''
|
||||
- _blank
|
||||
- '_blank'
|
||||
meta:enum:
|
||||
'': 'Open in same window'
|
||||
_blank: 'Open in a new window'
|
||||
x-translation-context: CTA link target
|
||||
default: ''
|
||||
examples:
|
||||
- ''
|
||||
- '_blank'
|
||||
attributes:
|
||||
type: Drupal\Core\Template\Attribute
|
||||
name: Attributes
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
{% endif %}
|
||||
|
||||
<a {{ attributes }} href="{{ href }}">
|
||||
{{ text }}
|
||||
{{ text }} ({{ componentMetadata.meta.properties.target[target] }})
|
||||
</a>
|
||||
|
|
|
@ -24,3 +24,7 @@ props:
|
|||
- power
|
||||
- like
|
||||
- external
|
||||
meta:enum:
|
||||
power: 'Power'
|
||||
like: 'Like'
|
||||
external: 'External'
|
||||
|
|
|
@ -27,6 +27,12 @@ props:
|
|||
- timer
|
||||
- serves
|
||||
- difficulty
|
||||
meta:enum:
|
||||
knife: Knife
|
||||
timer: Timer
|
||||
serves: Serves
|
||||
difficulty: Difficulty
|
||||
x-translation-context: Umami recipes icon names
|
||||
slots:
|
||||
label:
|
||||
type: string
|
||||
|
|
|
@ -24,6 +24,10 @@ props:
|
|||
enum:
|
||||
- article
|
||||
- div
|
||||
meta:enum:
|
||||
article: Article
|
||||
div: Container
|
||||
x-translation-context: HTML tag
|
||||
# Provide a default value
|
||||
default: article
|
||||
|
||||
|
|
|
@ -30,6 +30,15 @@ props:
|
|||
- h5
|
||||
- h6
|
||||
- span
|
||||
meta:enum:
|
||||
h1: Heading 1
|
||||
h2: Heading 2
|
||||
h3: Heading 3
|
||||
h4: Heading 4
|
||||
h5: Heading 5
|
||||
h6: Heading 6
|
||||
span: Inline
|
||||
x-translation-context: HTML tag
|
||||
# Provide a default value
|
||||
default: h2
|
||||
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// cspell:ignore Abre er bânnêh en una nueba bentana la mîmma
|
||||
|
||||
namespace Drupal\KernelTests\Components;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\locale\StringInterface;
|
||||
use Drupal\locale\StringStorageInterface;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\TerminableInterface;
|
||||
|
||||
/**
|
||||
* Tests the component can be translated.
|
||||
*
|
||||
* @group sdc
|
||||
*/
|
||||
class ComponentTranslationTest extends ComponentKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'sdc_test',
|
||||
'locale',
|
||||
'language',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $themes = ['sdc_theme_test'];
|
||||
|
||||
/**
|
||||
* The locale storage.
|
||||
*
|
||||
* @var \Drupal\locale\StringStorageInterface
|
||||
*/
|
||||
protected StringStorageInterface $storage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Add a default locale storage for all these tests.
|
||||
$this->storage = $this->container->get('locale.storage');
|
||||
ConfigurableLanguage::createFromLangcode('epa')->save();
|
||||
$this->container->get('string_translation')->setDefaultLangcode('epa');
|
||||
$this->installSchema('locale', [
|
||||
'locales_location',
|
||||
'locales_source',
|
||||
'locales_target',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that components render enum props correctly with their translations.
|
||||
*/
|
||||
public function testEnumPropsCanBeTranslated(): void {
|
||||
$bannerString = $this->buildSourceString(['source' => 'Open in a new window', 'context' => 'Banner link target']);
|
||||
$bannerString->save();
|
||||
$ctaString = $this->buildSourceString(['source' => 'Open in a new window', 'context' => 'CTA link target']);
|
||||
$ctaString->save();
|
||||
$ctaEmptyString = $this->buildSourceString(['source' => 'Open in same window', 'context' => 'CTA link target']);
|
||||
$ctaEmptyString->save();
|
||||
$this->createTranslation($bannerString, 'epa', ['translation' => 'Abre er bânnêh en una nueba bentana']);
|
||||
$this->createTranslation($ctaString, 'epa', ['translation' => 'Abre er CTA en una nueba bentana']);
|
||||
$this->createTranslation($ctaEmptyString, 'epa', ['translation' => 'Abre er CTA en la mîmma bentana']);
|
||||
|
||||
$build = [
|
||||
'banner' => [
|
||||
'#type' => 'component',
|
||||
'#component' => 'sdc_test:my-banner',
|
||||
'#props' => [
|
||||
'heading' => 'I am a banner',
|
||||
'ctaText' => 'Click me',
|
||||
'ctaHref' => 'https://www.example.org',
|
||||
'ctaTarget' => '_blank',
|
||||
],
|
||||
],
|
||||
'cta' => [
|
||||
'#type' => 'component',
|
||||
'#component' => 'sdc_test:my-cta',
|
||||
'#props' => [
|
||||
'text' => 'Click me',
|
||||
'href' => 'https://www.example.org',
|
||||
'target' => '_blank',
|
||||
],
|
||||
],
|
||||
'cta_with_empty_enum' => [
|
||||
'#type' => 'component',
|
||||
'#component' => 'sdc_test:my-cta',
|
||||
'#props' => [
|
||||
'text' => 'Click me',
|
||||
'href' => 'https://www.example.org',
|
||||
'target' => '',
|
||||
],
|
||||
],
|
||||
];
|
||||
\Drupal::state()->set('sdc_test_component', $build);
|
||||
$response = $this->request(Request::create('sdc-test-component'));
|
||||
$crawler = new Crawler($response->getContent());
|
||||
|
||||
// Assert that even if the source is the same, the translations depend on
|
||||
// the enum context.
|
||||
$this->assertStringContainsString('Abre er bânnêh en una nueba bentana', $crawler->filter('#sdc-wrapper [data-component-id="sdc_test:my-banner"]')->outerHtml());
|
||||
$this->assertStringContainsString('Abre er CTA en una nueba bentana', $crawler->filter('#sdc-wrapper a[data-component-id="sdc_test:my-cta"]:nth-of-type(1)')->outerHtml());
|
||||
$this->assertStringContainsString('Abre er CTA en la mîmma bentana', $crawler->filter('#sdc-wrapper a[data-component-id="sdc_test:my-cta"]:nth-of-type(2)')->outerHtml());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates random source string object.
|
||||
*
|
||||
* @param array $values
|
||||
* The values array.
|
||||
*
|
||||
* @return \Drupal\locale\StringInterface
|
||||
* A locale string.
|
||||
*/
|
||||
protected function buildSourceString(array $values = []): StringInterface {
|
||||
return $this->storage->createString($values += [
|
||||
'source' => $this->randomMachineName(100),
|
||||
'context' => $this->randomMachineName(20),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates single translation for source string.
|
||||
*
|
||||
* @param \Drupal\locale\StringInterface $source
|
||||
* The source string.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
* @param array $values
|
||||
* The values array.
|
||||
*
|
||||
* @return \Drupal\locale\StringInterface
|
||||
* The translated string object.
|
||||
*/
|
||||
protected function createTranslation(StringInterface $source, $langcode, array $values = []): StringInterface {
|
||||
return $this->storage->createTranslation($values + [
|
||||
'lid' => $source->lid,
|
||||
'language' => $langcode,
|
||||
'translation' => $this->randomMachineName(100),
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes a request to the HTTP kernel and returns a response.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* The response.
|
||||
*/
|
||||
protected function request(Request $request): Response {
|
||||
// @todo We should replace this when https://drupal.org/i/3390193 lands.
|
||||
// Reset the request stack.
|
||||
// \Drupal\KernelTests\KernelTestBase::bootKernel() pushes a bogus request
|
||||
// to boot the kernel, but it is also needed for any URL generation in tests
|
||||
// to work. We also need to reset the request stack every time we make a
|
||||
// request.
|
||||
$request_stack = $this->container->get('request_stack');
|
||||
while ($request_stack->getCurrentRequest() !== NULL) {
|
||||
$request_stack->pop();
|
||||
}
|
||||
|
||||
$http_kernel = $this->container->get('http_kernel');
|
||||
self::assertInstanceOf(HttpKernelInterface::class, $http_kernel);
|
||||
$response = $http_kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, FALSE);
|
||||
$content = $response->getContent();
|
||||
self::assertNotFalse($content);
|
||||
$this->setRawContent($content);
|
||||
|
||||
self::assertInstanceOf(TerminableInterface::class, $http_kernel);
|
||||
$http_kernel->terminate($request, $response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Drupal\Tests\Core\Theme\Component;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Theme\Component\ComponentMetadata;
|
||||
use Drupal\Core\Render\Component\Exception\InvalidComponentException;
|
||||
use Drupal\Tests\UnitTestCaseTest;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
/**
|
||||
* Unit tests for the component metadata class.
|
||||
|
@ -18,10 +20,13 @@ class ComponentMetadataTest extends UnitTestCaseTest {
|
|||
|
||||
/**
|
||||
* Tests that the correct data is returned for each property.
|
||||
*
|
||||
* @dataProvider dataProviderMetadata
|
||||
*/
|
||||
public function testMetadata(array $metadata_info, array $expectations): void {
|
||||
#[DataProvider('dataProviderMetadata')]
|
||||
public function testMetadata(array $metadata_info, array $expectations, bool $missing_schema, ?\Throwable $expectedException = NULL): void {
|
||||
if ($expectedException !== NULL) {
|
||||
$this->expectException($expectedException::class);
|
||||
$this->expectExceptionMessage($expectedException->getMessage());
|
||||
}
|
||||
$metadata = new ComponentMetadata($metadata_info, 'foo/', FALSE);
|
||||
$this->assertSame($expectations['path'], $metadata->path);
|
||||
$this->assertSame($expectations['status'], $metadata->status);
|
||||
|
@ -31,18 +36,23 @@ class ComponentMetadataTest extends UnitTestCaseTest {
|
|||
|
||||
/**
|
||||
* Tests the correct checks when enforcing schemas or not.
|
||||
*
|
||||
* @dataProvider dataProviderMetadata
|
||||
*/
|
||||
public function testMetadataEnforceSchema(array $metadata_info, array $expectations, bool $missing_schema): void {
|
||||
#[DataProvider('dataProviderMetadata')]
|
||||
public function testMetadataEnforceSchema(array $metadata_info, array $expectations, bool $missing_schema, ?\Throwable $expected_exception = NULL): void {
|
||||
if ($missing_schema) {
|
||||
$this->expectException(InvalidComponentException::class);
|
||||
$this->expectExceptionMessage('The component "' . $metadata_info['id'] . '" does not provide schema information. Schema definitions are mandatory for components declared in modules. For components declared in themes, schema definitions are only mandatory if the "enforce_prop_schemas" key is set to "true" in the theme info file.');
|
||||
new ComponentMetadata($metadata_info, 'foo/', TRUE);
|
||||
}
|
||||
else {
|
||||
if ($expected_exception !== NULL) {
|
||||
$this->expectException($expected_exception::class);
|
||||
$this->expectExceptionMessage($expected_exception->getMessage());
|
||||
}
|
||||
new ComponentMetadata($metadata_info, 'foo/', TRUE);
|
||||
$this->expectNotToPerformAssertions();
|
||||
if ($expected_exception === NULL) {
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +81,7 @@ class ComponentMetadataTest extends UnitTestCaseTest {
|
|||
],
|
||||
TRUE,
|
||||
],
|
||||
'complete example with schema' => [
|
||||
'complete example with schema, but no meta:enum' => [
|
||||
[
|
||||
'$schema' => 'https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json',
|
||||
'id' => 'core:my-button',
|
||||
|
@ -136,7 +146,321 @@ class ComponentMetadataTest extends UnitTestCaseTest {
|
|||
],
|
||||
FALSE,
|
||||
],
|
||||
'complete example with schema, but no matching meta:enum' => [
|
||||
[
|
||||
'$schema' => 'https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json',
|
||||
'id' => 'core:my-button',
|
||||
'machineName' => 'my-button',
|
||||
'path' => 'foo/my-other/path',
|
||||
'name' => 'Button',
|
||||
'description' => 'JavaScript enhanced button that tracks the number of times a user clicked it.',
|
||||
'libraryOverrides' => ['dependencies' => ['core/drupal']],
|
||||
'group' => 'my-group',
|
||||
'props' => [
|
||||
'type' => 'object',
|
||||
'required' => ['text'],
|
||||
'properties' => [
|
||||
'text' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Title',
|
||||
'description' => 'The title for the button',
|
||||
'minLength' => 2,
|
||||
'examples' => ['Press', 'Submit now'],
|
||||
],
|
||||
'iconType' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Icon Type',
|
||||
'enum' => [
|
||||
'power',
|
||||
'like',
|
||||
'external',
|
||||
],
|
||||
'meta:enum' => [
|
||||
'power' => 'Power',
|
||||
'fav' => 'Favorite',
|
||||
'external' => 'External',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'path' => 'my-other/path',
|
||||
'status' => 'stable',
|
||||
'thumbnail' => '',
|
||||
'group' => 'my-group',
|
||||
'additionalProperties' => FALSE,
|
||||
'props' => [
|
||||
'type' => 'object',
|
||||
'required' => ['text'],
|
||||
'additionalProperties' => FALSE,
|
||||
'properties' => [
|
||||
'text' => [
|
||||
'type' => ['string', 'object'],
|
||||
'title' => 'Title',
|
||||
'description' => 'The title for the button',
|
||||
'minLength' => 2,
|
||||
'examples' => ['Press', 'Submit now'],
|
||||
],
|
||||
'iconType' => [
|
||||
'type' => ['string', 'object'],
|
||||
'title' => 'Icon Type',
|
||||
'enum' => [
|
||||
'power',
|
||||
'like',
|
||||
'external',
|
||||
],
|
||||
'meta:enum' => [
|
||||
'power' => 'Power',
|
||||
'fav' => 'Favorite',
|
||||
'external' => 'External',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
FALSE,
|
||||
new InvalidComponentException('The values for the iconType prop enum in component core:my-button must be defined in meta:enum.'),
|
||||
],
|
||||
'complete example with schema (including meta:enum)' => [
|
||||
[
|
||||
'$schema' => 'https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json',
|
||||
'id' => 'core:my-button',
|
||||
'machineName' => 'my-button',
|
||||
'path' => 'foo/my-other/path',
|
||||
'name' => 'Button',
|
||||
'description' => 'JavaScript enhanced button that tracks the number of times a user clicked it.',
|
||||
'libraryOverrides' => ['dependencies' => ['core/drupal']],
|
||||
'group' => 'my-group',
|
||||
'props' => [
|
||||
'type' => 'object',
|
||||
'required' => ['text'],
|
||||
'properties' => [
|
||||
'text' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Title',
|
||||
'description' => 'The title for the button',
|
||||
'minLength' => 2,
|
||||
'examples' => ['Press', 'Submit now'],
|
||||
],
|
||||
'iconType' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Icon Type',
|
||||
'enum' => [
|
||||
'power',
|
||||
'like',
|
||||
'external',
|
||||
],
|
||||
'meta:enum' => [
|
||||
'power' => 'Power',
|
||||
'like' => 'Like',
|
||||
'external' => 'External',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'path' => 'my-other/path',
|
||||
'status' => 'stable',
|
||||
'thumbnail' => '',
|
||||
'group' => 'my-group',
|
||||
'additionalProperties' => FALSE,
|
||||
'props' => [
|
||||
'type' => 'object',
|
||||
'required' => ['text'],
|
||||
'additionalProperties' => FALSE,
|
||||
'properties' => [
|
||||
'text' => [
|
||||
'type' => ['string', 'object'],
|
||||
'title' => 'Title',
|
||||
'description' => 'The title for the button',
|
||||
'minLength' => 2,
|
||||
'examples' => ['Press', 'Submit now'],
|
||||
],
|
||||
'iconType' => [
|
||||
'type' => ['string', 'object'],
|
||||
'title' => 'Icon Type',
|
||||
'enum' => [
|
||||
'power',
|
||||
'like',
|
||||
'external',
|
||||
],
|
||||
'meta:enum' => [
|
||||
'power' => 'Power',
|
||||
'like' => 'Like',
|
||||
'external' => 'External',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
FALSE,
|
||||
],
|
||||
'complete example with schema (including meta:enum and x-translation-context)' => [
|
||||
[
|
||||
'$schema' => 'https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json',
|
||||
'id' => 'core:my-button',
|
||||
'machineName' => 'my-button',
|
||||
'path' => 'foo/my-other/path',
|
||||
'name' => 'Button',
|
||||
'description' => 'JavaScript enhanced button that tracks the number of times a user clicked it.',
|
||||
'libraryOverrides' => ['dependencies' => ['core/drupal']],
|
||||
'group' => 'my-group',
|
||||
'props' => [
|
||||
'type' => 'object',
|
||||
'required' => ['text'],
|
||||
'properties' => [
|
||||
'text' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Title',
|
||||
'description' => 'The title for the button',
|
||||
'minLength' => 2,
|
||||
'examples' => ['Press', 'Submit now'],
|
||||
],
|
||||
'iconType' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Icon Type',
|
||||
'enum' => [
|
||||
'power',
|
||||
'like',
|
||||
'external',
|
||||
],
|
||||
'meta:enum' => [
|
||||
'power' => 'Power',
|
||||
'like' => 'Like',
|
||||
'external' => 'External',
|
||||
],
|
||||
'x-translation-context' => 'Icon Type',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'path' => 'my-other/path',
|
||||
'status' => 'stable',
|
||||
'thumbnail' => '',
|
||||
'group' => 'my-group',
|
||||
'additionalProperties' => FALSE,
|
||||
'props' => [
|
||||
'type' => 'object',
|
||||
'required' => ['text'],
|
||||
'additionalProperties' => FALSE,
|
||||
'properties' => [
|
||||
'text' => [
|
||||
'type' => ['string', 'object'],
|
||||
'title' => 'Title',
|
||||
'description' => 'The title for the button',
|
||||
'minLength' => 2,
|
||||
'examples' => ['Press', 'Submit now'],
|
||||
],
|
||||
'iconType' => [
|
||||
'type' => ['string', 'object'],
|
||||
'title' => 'Icon Type',
|
||||
'enum' => [
|
||||
'power',
|
||||
'like',
|
||||
'external',
|
||||
],
|
||||
'meta:enum' => [
|
||||
'power' => 'Power',
|
||||
'like' => 'Like',
|
||||
'external' => 'External',
|
||||
],
|
||||
'x-translation-context' => 'Icon Type',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
FALSE,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function dataProviderEnumOptionsMetadata(): array {
|
||||
$common_schema = [
|
||||
'$schema' => 'https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json',
|
||||
'id' => 'core:my-button',
|
||||
'machineName' => 'my-button',
|
||||
'path' => 'foo/my-other/path',
|
||||
'name' => 'Button',
|
||||
];
|
||||
return [
|
||||
'no meta:enum' => [$common_schema +
|
||||
[
|
||||
'props' => [
|
||||
'type' => 'object',
|
||||
'required' => ['text'],
|
||||
'properties' => [
|
||||
'iconType' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Icon Type',
|
||||
'enum' => [
|
||||
'power',
|
||||
'like',
|
||||
'external',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'iconType',
|
||||
[
|
||||
'power' => 'power',
|
||||
'like' => 'like',
|
||||
'external' => 'external',
|
||||
],
|
||||
'',
|
||||
],
|
||||
'meta:enum, with x-translation-context' => [$common_schema +
|
||||
[
|
||||
'props' => [
|
||||
'type' => 'object',
|
||||
'required' => ['text'],
|
||||
'properties' => [
|
||||
'target' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Icon Type',
|
||||
'enum' => [
|
||||
'',
|
||||
'_blank',
|
||||
],
|
||||
'meta:enum' => [
|
||||
'' => 'Opens in same window',
|
||||
'_blank' => 'Opens in new window',
|
||||
],
|
||||
'x-translation-context' => 'Link target',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'target',
|
||||
[
|
||||
'' => 'Opens in same window',
|
||||
'_blank' => 'Opens in new window',
|
||||
],
|
||||
'Link target',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getEnumOptions
|
||||
*/
|
||||
#[DataProvider('dataProviderEnumOptionsMetadata')]
|
||||
public function testGetEnumOptions(array $metadata_info, string $prop_name, array $expected_values, string $expected_context): void {
|
||||
$translation = $this->getStringTranslationStub();
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('string_translation', $translation);
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
$component_metadata = new ComponentMetadata($metadata_info, 'foo/', TRUE);
|
||||
$options = $component_metadata->getEnumOptions($prop_name);
|
||||
$rendered_options = array_map(fn($value) => (string) $value, $options);
|
||||
$this->assertSame($expected_values, $rendered_options);
|
||||
foreach ($options as $translatable) {
|
||||
$this->assertSame($expected_context, $translatable->getOption('context'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue