ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
AbstractFactoryTest.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
21 require_once("libs/composer/vendor/autoload.php");
22 
26 
36 abstract class AbstractFactoryTest extends TestCase
37 {
38  public const COMPONENT = 1;
39  public const FACTORY = 2;
40 
41  /* This allows to omit checking of certain factory methods, use prudently...
42  */
43  private array $omit_factory_methods = [
44  "helpTopics"
45  ];
46 
47  /* kitchensink info test configuration:
48  * true = should be there, check
49  * false = may be there, don't check
50  * Notice, some properties (MUST/MUST NOT) will always be checked.
51  */
53  'description' => true,
54  'background' => false,
55  'context' => true,
56  'featurewiki' => false,
57  'javascript' => false,
58  'rules' => true
59  ];
60 
61  /* You can overwrite these settings per factory method when using this test
62  * by writing $kitchensink_info_settings. See GlyphFactoryTest for an example.
63  */
64 
65 
66  // Definitions and Helpers:
67 
68  private array $description_categories = ['purpose', 'composition', 'effect', 'rival'];
69 
70  private array $rules_categories = [
71  'usage',
72  'interaction',
73  'wording',
74  'style',
75  'ordering',
76  'responsiveness',
77  'composition',
78  'accessibility'
79  ];
80 
83 
84  final protected function returnsFactory(array $docstring_data): bool
85  {
86  return $this->isFactoryName($docstring_data["namespace"]);
87  }
88 
89  final protected function returnsComponent(array $docstring_data): bool
90  {
91  $reflection = new ReflectionClass($docstring_data["namespace"]);
92  return in_array("ILIAS\\UI\\Component\\Component", $reflection->getInterfaceNames());
93  }
94 
95  final protected function isFactoryName(string $name): bool
96  {
97  return preg_match("#^(\\\\)?ILIAS\\\\UI\\\\Component\\\\([a-zA-Z]+\\\\)*Factory$#", $name) === 1;
98  }
99 
100  final public function buildFactoryReflection(): ReflectionClass
101  {
102  return new ReflectionClass($this->factory_title);
103  }
104 
105  final public function getMethodsProvider(): array
106  {
107  $reflection = $this->buildFactoryReflection();
108  return array_filter(
109  array_map(function ($element) {
110  if (!in_array($element->getName(), $this->omit_factory_methods)) {
111  return array($element, $element->getName());
112  }
113  return false;
114  }, $reflection->getMethods())
115  );
116  }
117 
118  // Setup
119 
120  public function setUp(): void
121  {
122  $this->yaml_parser = new Crawler\EntriesYamlParser();
123  $this->reflection = $this->buildFactoryReflection();
124  }
125 
126  public function testProperNamespace(): void
127  {
128  $message = "TODO: Put your factory into the proper namespace.";
129  $this->assertMatchesRegularExpression(
130  "#^ILIAS\\\\UI\\\\Component.#",
131  $this->reflection->getNamespaceName(),
132  $message
133  );
134  }
135 
136  public function testProperName(): void
137  {
138  $name = $this->reflection->getName();
139  $message = "TODO: Give your factory a proper name.";
140  $this->assertTrue($this->isFactoryName($name), $message);
141  }
142 
148  final public function testCheckYamlExtraction(ReflectionMethod $method_reflection, string $name): array
149  {
150  try {
151  //Todo (TA) this is not pretty. We should think about using only reflection in the parser as well.
152  $function_name_string = "\n public function " . $method_reflection->getName() . "()";
153  $docstring_data = $this->yaml_parser->parseArrayFromString(
154  $method_reflection->getDocComment() . $function_name_string
155  );
156  $this->assertTrue(true);
158  $message = "TODO ($name): fix parse error in kitchen sink yaml: " . $e->getMessage();
159  $this->assertTrue(false, $message);
160  }
161  $this->assertCount(1, $docstring_data);
162  return $docstring_data[0];
163  }
164 
170  final public function testReturnType(ReflectionMethod $method_reflection, string $name): void
171  {
172  $message = "TODO ($name): fix return type, it must be a factory or a component.";
173  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
174  if ($this->returnsFactory($docstring_data)) {
175  $this->assertTrue(true);
176  } elseif ($this->returnsComponent($docstring_data)) {
177  $this->assertTrue(true);
178  } else {
179  $this->assertTrue(false, $message);
180  }
181  }
182 
189  ReflectionMethod $method_reflection,
190  string $name
191  ): void {
192  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
193  $this->testReturnType($method_reflection, $name);
194 
195  $return_doc = $docstring_data["namespace"];
196  $name_uppercase = ucwords($name);
197  $regex_factory_namespace = $this->getRegexFactoryNamespace();
198  $regex_head = "#^(\\\\?)$regex_factory_namespace";
199 
200  $message = "TODO ($name): fix @return, it does not match the method name.";
201  if ($this->returnsFactory($docstring_data)) {
202  $this->assertMatchesRegularExpression(
203  "$regex_head\\\\$name_uppercase\\\\Factory$#",
204  $return_doc,
205  $message
206  );
207  } else { // returnsComponent
208  // Every component MUST be described by a single interface, where the name of
209  // the interface corresponds to the name of the component.
210  $standard_pattern = "$regex_head\\\\$name_uppercase#";
211  $standard_case = preg_match($standard_pattern, $return_doc);
212 
213  // unless they only differ in a type and share a common prefix to their pathes.
214  $namespace_parts = explode("\\", $this->reflection->getNamespaceName());
215  $typediff_only_pattern = "$regex_head\\\\" . array_pop($namespace_parts) . "#";
216  $typediff_only_case = preg_match($typediff_only_pattern, $return_doc);
217 
218  $this->assertTrue($standard_case || $typediff_only_case, $message);
219  }
220  }
221 
222  protected function getRegexFactoryNamespace(): string
223  {
224  return str_replace("\\", "\\\\", $this->reflection->getNamespaceName());
225  }
226 
232  final public function testMethodParams(ReflectionMethod $method_reflection, string $name): void
233  {
234  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
235  if ($this->returnsFactory($docstring_data)) {
236  $message = "TODO ($name): remove params from method that returns Factory.";
237  $this->assertEquals(0, $method_reflection->getNumberOfParameters(), $message);
238  }
239  }
240 
241  // Common rules for all factory methods, regardless whether they return other
242  // factories or components.
243 
247  final public function testKitchensinkInfoDescription(ReflectionMethod $method_reflection, string $name): void
248  {
249  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
250  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
251 
252  if ($kitchensink_info_settings['description']) {
253  $message = "TODO ($name): add a description.";
254  $this->assertArrayHasKey('description', $docstring_data, $message);
255 
256  $desc_fields = implode(", ", $this->description_categories);
257  $message = "TODO ($name): the description field should at least contain one of these: $desc_fields.";
258  $existing_keys = array_keys($docstring_data["description"]);
259  $existing_expected_keys = array_intersect($this->description_categories, $existing_keys);
260  $this->assertGreaterThanOrEqual(
261  1,
262  $existing_expected_keys,
263  $message
264  );
265  }
266  }
267 
271  final public function testKitchensinkInfoRivals(ReflectionMethod $method_reflection, string $name): void
272  {
273  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
274  if (isset($docstring_data["description"]) && isset($docstring_data["description"]["rivals"])) {
275  $rules = $docstring_data["description"]["rivals"];
276  $message = "TODO ($name): The Rivals field has a non-string index. Format like 'rival_name': 'description'";
277  $this->assertTrue(array_unique(array_map("is_string", array_keys($rules))) === array(true), $message);
278  }
279  $this->assertTrue(true);
280  }
281 
285  final public function testKitchensinkInfoBackground(ReflectionMethod $method_reflection, string $name): void
286  {
287  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
288  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
289 
290  if ($kitchensink_info_settings['background']) {
291  $message = "TODO ($name): add a background field.";
292  $this->assertArrayHasKey('background', $docstring_data, $message);
293  }
294  }
295 
299  final public function testKitchensinkInfoFeatureWiki(ReflectionMethod $method_reflection, string $name): void
300  {
301  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
302  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
303 
304  if ($kitchensink_info_settings['featurewiki']) {
305  $message = "TODO ($name): add a featurewiki field.";
306  $this->assertArrayHasKey('featurewiki', $docstring_data, $message);
307  }
308  }
309 
313  final public function testKitchensinkInfoJavaScript(ReflectionMethod $method_reflection, string $name): void
314  {
315  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
316  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
317 
318  if ($kitchensink_info_settings['javascript']) {
319  $message = "TODO ($name): add a javascript field.";
320  $this->assertArrayHasKey('javascript', $docstring_data, $message);
321  }
322  }
323 
327  final public function testKitchensinkInfoRules(ReflectionMethod $method_reflection, string $name): void
328  {
329  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
330  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
331 
332  if ($kitchensink_info_settings['rules']) {
333  $message = "TODO ($name): add a rules field.";
334  $this->assertArrayHasKey('rules', $docstring_data, $message);
335 
336  $rules_fields = implode(", ", $this->rules_categories);
337  $message = "TODO ($name): the rules field should at least contain one of these: $rules_fields.";
338  $existing_keys = array_keys($docstring_data["rules"]);
339  $existing_expected_keys = array_intersect($this->rules_categories, $existing_keys);
340  $this->assertGreaterThanOrEqual(
341  1,
342  $existing_expected_keys,
343  $message
344  );
345  }
346  }
347 
351  final public function testKitchensinkInfoContext(ReflectionMethod $method_reflection, string $name): void
352  {
353  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
354  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
355  if (!$this->returnsFactory($docstring_data) && $kitchensink_info_settings["context"]) {
356  $message = "TODO ($name): factory method returning component should have context field. Add it.";
357  $this->assertArrayHasKey("context", $docstring_data, $message);
358  }
359  }
360 
361  final public function kitchensinkInfoSettingsMergedWithDefaults(string $name): array
362  {
363  if (array_key_exists($name, $this->kitchensink_info_settings)) {
364  return array_merge(
365  $this->kitchensink_info_settings_default,
366  $this->kitchensink_info_settings[$name]
367  );
368  } else {
370  }
371  }
372 }
testReturnType(ReflectionMethod $method_reflection, string $name)
Tests whether the method either returns a factory or a component.
kitchensinkInfoSettingsMergedWithDefaults(string $name)
testKitchensinkInfoFeatureWiki(ReflectionMethod $method_reflection, string $name)
getMethodsProvider
testKitchensinkInfoDescription(ReflectionMethod $method_reflection, string $name)
getMethodsProvider
testKitchensinkInfoBackground(ReflectionMethod $method_reflection, string $name)
getMethodsProvider
testFactoryMethodNameCompatibleDocstring(ReflectionMethod $method_reflection, string $name)
Tests whether the method name matches the return doctring?
Crawler EntriesYamlParser $yaml_parser
returnsComponent(array $docstring_data)
testKitchensinkInfoRivals(ReflectionMethod $method_reflection, string $name)
getMethodsProvider
Defines tests every SHOULD pass UI-factory.
returnsFactory(array $docstring_data)
testKitchensinkInfoRules(ReflectionMethod $method_reflection, string $name)
getMethodsProvider
testKitchensinkInfoContext(ReflectionMethod $method_reflection, string $name)
getMethodsProvider
testKitchensinkInfoJavaScript(ReflectionMethod $method_reflection, string $name)
getMethodsProvider
array array $description_categories
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
testMethodParams(ReflectionMethod $method_reflection, string $name)
Tests whether methods returning factories have no parameters.
ReflectionClass $reflection
$message
Definition: xapiexit.php:32
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Crawler.php:21
testCheckYamlExtraction(ReflectionMethod $method_reflection, string $name)
Tests whether the YAML Kitchen Sink info can be parsed.