ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
AbstractFactoryTestCase.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21require_once("vendor/composer/vendor/autoload.php");
22
25use PHPUnit\Framework\TestCase;
26
36abstract 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
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
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(),
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,
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,
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,
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}
Defines tests every SHOULD pass UI-factory.
testKitchensinkInfoBackground(ReflectionMethod $method_reflection, string $name)
returnsFactory(array $docstring_data)
returnsComponent(array $docstring_data)
testFactoryMethodNameCompatibleDocstring(ReflectionMethod $method_reflection, string $name)
Tests whether the method name matches the return doctring?
testKitchensinkInfoContext(ReflectionMethod $method_reflection, string $name)
testKitchensinkInfoJavaScript(ReflectionMethod $method_reflection, string $name)
static array $kitchensink_info_settings_default
testKitchensinkInfoFeatureWiki(ReflectionMethod $method_reflection, string $name)
Crawler EntriesYamlParser $yaml_parser
testKitchensinkInfoRivals(ReflectionMethod $method_reflection, string $name)
testMethodParams(ReflectionMethod $method_reflection, string $name)
Tests whether methods returning factories have no parameters.
static ReflectionClass $reflection
testKitchensinkInfoRules(ReflectionMethod $method_reflection, string $name)
testCheckYamlExtraction(ReflectionMethod $method_reflection, string $name)
Tests whether the YAML Kitchen Sink info can be parsed.
kitchensinkInfoSettingsMergedWithDefaults(string $name)
testReturnType(ReflectionMethod $method_reflection, string $name)
Tests whether the method either returns a factory or a component.
testKitchensinkInfoDescription(ReflectionMethod $method_reflection, string $name)
return true
$message
Definition: xapiexit.php:31