ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
AbstractFactoryTestCase.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
21 require_once("vendor/composer/vendor/autoload.php");
22 
26 
36 abstract class AbstractFactoryTestCase 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 static 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  */
52  private static array $kitchensink_info_settings_default = [
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 static array $description_categories = ['purpose', 'composition', 'effect', 'rival'];
69 
70  private static array $rules_categories = [
71  'usage',
72  'interaction',
73  'wording',
74  'style',
75  'ordering',
76  'responsiveness',
77  'composition',
78  'accessibility'
79  ];
80 
81  private Crawler\EntriesYamlParser $yaml_parser;
82  private static ReflectionClass $reflection;
83  public static string $factory_title = '';
84 
85  final protected function returnsFactory(array $docstring_data): bool
86  {
87  return $this->isFactoryName($docstring_data["namespace"]);
88  }
89 
90  final protected function returnsComponent(array $docstring_data): bool
91  {
92  $reflection = new ReflectionClass($docstring_data["namespace"]);
93  return in_array("ILIAS\\UI\\Component\\Component", $reflection->getInterfaceNames());
94  }
95 
96  final protected function isFactoryName(string $name): bool
97  {
98  return preg_match("#^(\\\\)?ILIAS\\\\UI\\\\Component\\\\([a-zA-Z]+\\\\)*Factory$#", $name) === 1;
99  }
100 
101  final public static function buildFactoryReflection(): ReflectionClass
102  {
103  return new ReflectionClass(static::$factory_title);
104  }
105 
106  final public static function getMethodsProvider(): array
107  {
108 
109  $reflection = self::buildFactoryReflection();
110  return array_filter(
111  array_map(function ($element) {
112  if (!in_array($element->getName(), self::$omit_factory_methods)) {
113  return array($element, $element->getName());
114  }
115  return false;
116  }, $reflection->getMethods())
117  );
118  }
119 
120  // Setup
121 
122  public function setUp(): void
123  {
124  $this->yaml_parser = new Crawler\EntriesYamlParser();
125  self::$reflection = $this->buildFactoryReflection();
126  }
127 
128  public function testProperNamespace(): void
129  {
130  $message = "TODO: Put your factory into the proper namespace.";
131  $this->assertMatchesRegularExpression(
132  "#^ILIAS\\\\UI\\\\Component.#",
133  self::$reflection->getNamespaceName(),
134  $message
135  );
136  }
137 
138  public function testProperName(): void
139  {
140  $name = self::$reflection->getName();
141  $message = "TODO: Give your factory a proper name.";
142  $this->assertTrue($this->isFactoryName($name), $message);
143  }
144 
148  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
149  final public function testCheckYamlExtraction(ReflectionMethod $method_reflection, string $name): array
150  {
151  try {
152  //Todo (TA) this is not pretty. We should think about using only reflection in the parser as well.
153  $function_name_string = "\n public function " . $method_reflection->getName() . "()";
154  $docstring_data = $this->yaml_parser->parseArrayFromString(
155  $method_reflection->getDocComment() . $function_name_string
156  );
157  $this->assertTrue(true);
159  $message = "TODO ($name): fix parse error in kitchen sink yaml: " . $e->getMessage();
160  $this->assertTrue(false, $message);
161  }
162  $this->assertCount(1, $docstring_data);
163  return $docstring_data[0];
164  }
165 
169  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
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 
186  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
188  ReflectionMethod $method_reflection,
189  string $name
190  ): void {
191  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
192  $this->testReturnType($method_reflection, $name);
193 
194  $return_doc = $docstring_data["namespace"];
195  $name_uppercase = ucwords($name);
196  $regex_factory_namespace = $this->getRegexFactoryNamespace();
197  $regex_head = "#^(\\\\?)$regex_factory_namespace";
198 
199  $message = "TODO ($name): fix @return, it does not match the method name.";
200  if ($this->returnsFactory($docstring_data)) {
201  $this->assertMatchesRegularExpression(
202  "$regex_head\\\\$name_uppercase\\\\Factory$#",
203  $return_doc,
204  $message
205  );
206  } else { // returnsComponent
207  // Every component MUST be described by a single interface, where the name of
208  // the interface corresponds to the name of the component.
209  $standard_pattern = "$regex_head\\\\$name_uppercase#";
210  $standard_case = preg_match($standard_pattern, $return_doc);
211 
212  // unless they only differ in a type and share a common prefix to their pathes.
213  $namespace_parts = explode("\\", self::$reflection->getNamespaceName());
214  $typediff_only_pattern = "$regex_head\\\\" . array_pop($namespace_parts) . "#";
215  $typediff_only_case = preg_match($typediff_only_pattern, $return_doc);
216 
217  $this->assertTrue($standard_case || $typediff_only_case, $message);
218  }
219  }
220 
221  protected function getRegexFactoryNamespace(): string
222  {
223  return str_replace("\\", "\\\\", self::$reflection->getNamespaceName());
224  }
225 
229  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
230  final public function testMethodParams(ReflectionMethod $method_reflection, string $name): void
231  {
232  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
233  if ($this->returnsFactory($docstring_data)) {
234  $message = "TODO ($name): remove params from method that returns Factory.";
235  $this->assertEquals(0, $method_reflection->getNumberOfParameters(), $message);
236  }
237  }
238 
239  // Common rules for all factory methods, regardless whether they return other
240  // factories or components.
241  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
242  final public function testKitchensinkInfoDescription(ReflectionMethod $method_reflection, string $name): void
243  {
244  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
245  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
246 
247  if ($kitchensink_info_settings['description']) {
248  $message = "TODO ($name): add a description.";
249  $this->assertArrayHasKey('description', $docstring_data, $message);
250 
251  $desc_fields = implode(", ", static::$description_categories);
252  $message = "TODO ($name): the description field should at least contain one of these: $desc_fields.";
253  $existing_keys = array_keys($docstring_data["description"]);
254  $existing_expected_keys = array_intersect(static::$description_categories, $existing_keys);
255  $this->assertGreaterThanOrEqual(
256  1,
257  $existing_expected_keys,
258  $message
259  );
260  }
261  }
262 
263  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
264  final public function testKitchensinkInfoRivals(ReflectionMethod $method_reflection, string $name): void
265  {
266  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
267  if (isset($docstring_data["description"]) && isset($docstring_data["description"]["rivals"])) {
268  $rules = $docstring_data["description"]["rivals"];
269  $message = "TODO ($name): The Rivals field has a non-string index. Format like 'rival_name': 'description'";
270  $this->assertTrue(array_unique(array_map("is_string", array_keys($rules))) === array(true), $message);
271  }
272  $this->assertTrue(true);
273  }
274 
275  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
276  final public function testKitchensinkInfoBackground(ReflectionMethod $method_reflection, string $name): void
277  {
278  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
279  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
280 
281  if ($kitchensink_info_settings['background']) {
282  $message = "TODO ($name): add a background field.";
283  $this->assertArrayHasKey('background', $docstring_data, $message);
284  }
285  }
286 
287  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
288  final public function testKitchensinkInfoFeatureWiki(ReflectionMethod $method_reflection, string $name): void
289  {
290  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
291  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
292 
293  if ($kitchensink_info_settings['featurewiki']) {
294  $message = "TODO ($name): add a featurewiki field.";
295  $this->assertArrayHasKey('featurewiki', $docstring_data, $message);
296  }
297  }
298 
299  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
300  final public function testKitchensinkInfoJavaScript(ReflectionMethod $method_reflection, string $name): void
301  {
302  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
303  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
304 
305  if ($kitchensink_info_settings['javascript']) {
306  $message = "TODO ($name): add a javascript field.";
307  $this->assertArrayHasKey('javascript', $docstring_data, $message);
308  }
309  }
310 
311  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
312  final public function testKitchensinkInfoRules(ReflectionMethod $method_reflection, string $name): void
313  {
314  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
315  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
316 
317  if ($kitchensink_info_settings['rules']) {
318  $message = "TODO ($name): add a rules field.";
319  $this->assertArrayHasKey('rules', $docstring_data, $message);
320 
321  $rules_fields = implode(", ", static::$rules_categories);
322  $message = "TODO ($name): the rules field should at least contain one of these: $rules_fields.";
323  $existing_keys = array_keys($docstring_data["rules"]);
324  $existing_expected_keys = array_intersect(static::$rules_categories, $existing_keys);
325  $this->assertGreaterThanOrEqual(
326  1,
327  $existing_expected_keys,
328  $message
329  );
330  }
331  }
332 
333  #[\PHPUnit\Framework\Attributes\DataProvider('getMethodsProvider')]
334  final public function testKitchensinkInfoContext(ReflectionMethod $method_reflection, string $name): void
335  {
336  $docstring_data = $this->testCheckYamlExtraction($method_reflection, $name);
337  $kitchensink_info_settings = $this->kitchensinkInfoSettingsMergedWithDefaults($name);
338  if (!$this->returnsFactory($docstring_data) && $kitchensink_info_settings["context"]) {
339  $message = "TODO ($name): factory method returning component should have context field. Add it.";
340  $this->assertArrayHasKey("context", $docstring_data, $message);
341  }
342  }
343 
344  final public function kitchensinkInfoSettingsMergedWithDefaults(string $name): array
345  {
346  if (array_key_exists($name, static::$kitchensink_info_settings)) {
347  return array_merge(
348  static::$kitchensink_info_settings_default,
349  static::$kitchensink_info_settings[$name]
350  );
351  } else {
352  return static::$kitchensink_info_settings_default;
353  }
354  }
355 }
returnsFactory(array $docstring_data)
testKitchensinkInfoDescription(ReflectionMethod $method_reflection, string $name)
testReturnType(ReflectionMethod $method_reflection, string $name)
Tests whether the method either returns a factory or a component.
testKitchensinkInfoFeatureWiki(ReflectionMethod $method_reflection, string $name)
testKitchensinkInfoBackground(ReflectionMethod $method_reflection, string $name)
testKitchensinkInfoContext(ReflectionMethod $method_reflection, string $name)
returnsComponent(array $docstring_data)
static array static array $description_categories
testKitchensinkInfoRules(ReflectionMethod $method_reflection, string $name)
testFactoryMethodNameCompatibleDocstring(ReflectionMethod $method_reflection, string $name)
Tests whether the method name matches the return doctring?
testMethodParams(ReflectionMethod $method_reflection, string $name)
Tests whether methods returning factories have no parameters.
static ReflectionClass $reflection
testKitchensinkInfoRivals(ReflectionMethod $method_reflection, string $name)
$message
Definition: xapiexit.php:31
static array $kitchensink_info_settings_default
Crawler EntriesYamlParser $yaml_parser
kitchensinkInfoSettingsMergedWithDefaults(string $name)
testKitchensinkInfoJavaScript(ReflectionMethod $method_reflection, string $name)
Defines tests every SHOULD pass UI-factory.
testCheckYamlExtraction(ReflectionMethod $method_reflection, string $name)
Tests whether the YAML Kitchen Sink info can be parsed.