mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/TemplateData
synced 2024-12-18 19:01:02 +00:00
8497d85a2d
Some time ago there was a little bit of custom CSS applied to the HTML table rendering. This is broken since patch I74214ea from 2016. This patch renamed all CSS classes but forgot to update the PHP code accordingly. I decided to not change the HTML rendering because these class names might already be used in custom per-wiki or per-user CSS. Instead I partly revert I74214ea. Unfortunately, some of the styles are quite dramatic, don't look good or just don't work. I decided to remove some. The argument is that the HTML rendering looks the same for 6 years now. I don't see a good reason to change it now. In detail: * Suggested values are not aliases and should not be rendered in gray. * The message "no description" is rendered in gray and italics. But this was applied to the wrong DOM element and made everything else gray and italic as well. * The color #777 is not readable, violating WCAG rules. While it's ok to dim aliases and such, it must be at least #555 or darker. * The "nowrap" destroys the table the moment one of the parameters does have a longer name or alias. Let the browser handle this, as it did for 6 years now. * Same for rendering aliases as individual, indented blocks. This makes the table unnecessarily big when there are many aliases, and just doesn't look right. Again, let's stick to what we had for 6 years. Change-Id: Idfa76eed6e2d68474c79d4674efce091cb031b66
1553 lines
38 KiB
PHP
1553 lines
38 KiB
PHP
<?php
|
|
|
|
use MediaWiki\Extension\TemplateData\Api\ApiTemplateData;
|
|
use MediaWiki\Extension\TemplateData\TemplateDataBlob;
|
|
use MediaWiki\Extension\TemplateData\TemplateDataHtmlFormatter;
|
|
use MediaWiki\Extension\TemplateData\TemplateDataValidator;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @group TemplateData
|
|
* @group Database
|
|
* @covers \MediaWiki\Extension\TemplateData\TemplateDataBlob
|
|
* @covers \MediaWiki\Extension\TemplateData\TemplateDataCompressedBlob
|
|
* @covers \MediaWiki\Extension\TemplateData\TemplateDataValidator
|
|
*/
|
|
class TemplateDataBlobTest extends MediaWikiIntegrationTestCase {
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
$this->setMwGlobals( 'wgLanguageCode', 'qqx' );
|
|
}
|
|
|
|
/**
|
|
* Helper method to generate a string that gzip can't compress.
|
|
*
|
|
* Output is consistent when given the same seed.
|
|
* @param int $minLength
|
|
* @param string $seed
|
|
* @return string
|
|
*/
|
|
private function generatePseudorandomString( $minLength, $seed ): string {
|
|
srand( $seed );
|
|
$string = '';
|
|
while ( strlen( $string ) < $minLength ) {
|
|
$string .= str_shuffle( '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' );
|
|
}
|
|
return $string;
|
|
}
|
|
|
|
public function provideParse() {
|
|
$cases = [
|
|
[
|
|
'input' => '{',
|
|
'status' => '(templatedata-invalid-parse)'
|
|
],
|
|
[
|
|
'input' => '[]
|
|
',
|
|
'status' => '(templatedata-invalid-type: templatedata, object)'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'status' => true,
|
|
'msg' => 'Minimal valid blob'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {},
|
|
"foo": "bar"
|
|
}
|
|
',
|
|
'status' => '(templatedata-invalid-unknown: foo)',
|
|
'msg' => 'Unknown properties'
|
|
],
|
|
[
|
|
'input' => '{ "description": [], "params": {} }',
|
|
'status' => '(templatedata-invalid-type: description, string|object)',
|
|
],
|
|
[
|
|
'input' => '{}',
|
|
'status' => '(templatedata-invalid-missing: params, object)',
|
|
'msg' => 'Empty object'
|
|
],
|
|
[
|
|
'input' => '{ "params": [] }',
|
|
'status' => '(templatedata-invalid-type: params, object)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": [] } }',
|
|
'status' => '(templatedata-invalid-type: params.a, object)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "foo": "" } } }',
|
|
'status' => '(templatedata-invalid-unknown: params.a.foo)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "label": [] } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.label, string|object)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "required": "" } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.required, boolean)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "suggested": "" } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.suggested, boolean)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "description": [] } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.description, string|object)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "example": [] } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.example, string|object)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "deprecated": [] } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.deprecated, boolean|string)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "aliases": "" } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.aliases, array)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "aliases": [ "1", 2, {} ] } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.aliases[2], int|string)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "autovalue": [] } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.autovalue, string)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "default": [] } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.default, string|object)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "type": [] } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.type, string)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "type": "" } } }',
|
|
'status' => '(templatedata-invalid-value: params.a.type)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "suggestedvalues": "" } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.suggestedvalues, array)',
|
|
],
|
|
[
|
|
'input' => '{ "params": { "a": { "suggestedvalues": [ {} ] } } }',
|
|
'status' => '(templatedata-invalid-type: params.a.suggestedvalues[0], string)',
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {}
|
|
}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {
|
|
"foo": {
|
|
"label": null,
|
|
"description": null,
|
|
"default": null,
|
|
"example": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
}
|
|
},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps": {}
|
|
}
|
|
',
|
|
'msg' => 'Optional properties are added if missing'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"comment": {
|
|
"type": "string/line"
|
|
}
|
|
}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {
|
|
"comment": {
|
|
"label": null,
|
|
"description": null,
|
|
"default": null,
|
|
"example": null,
|
|
"autovalue": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"type": "line"
|
|
}
|
|
},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps": {}
|
|
}
|
|
',
|
|
'msg' => 'Old string/* types are mapped to the unprefixed versions'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"description": "User badge MediaWiki developers.",
|
|
"params": {
|
|
"nickname": {
|
|
"label": null,
|
|
"description": "User name of user who owns the badge",
|
|
"default": "Base page name of the host page",
|
|
"example": null,
|
|
"required": false,
|
|
"suggested": true,
|
|
"aliases": [ 1 ]
|
|
}
|
|
}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": {
|
|
"qqx": "User badge MediaWiki developers."
|
|
},
|
|
"params": {
|
|
"nickname": {
|
|
"label": null,
|
|
"description": {
|
|
"qqx": "User name of user who owns the badge"
|
|
},
|
|
"default": {
|
|
"qqx": "Base page name of the host page"
|
|
},
|
|
"example": null,
|
|
"required": false,
|
|
"suggested": true,
|
|
"suggestedvalues": [],
|
|
"deprecated": false,
|
|
"aliases": [
|
|
"1"
|
|
],
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
}
|
|
},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps": {}
|
|
}
|
|
',
|
|
'msg' => 'InterfaceText is expanded to langcode-keyed object, assuming content language'
|
|
],
|
|
[
|
|
'input' => '{ "params": { "b": { "inherits": "a" } } }',
|
|
'status' => '(templatedata-invalid-missing: params.a)',
|
|
],
|
|
[
|
|
'input' => '{
|
|
"description": "Document the documenter.",
|
|
"params": {
|
|
"1d": {
|
|
"description": "Description of the template parameter",
|
|
"required": true,
|
|
"default": "example"
|
|
},
|
|
"2d": {
|
|
"inherits": "1d",
|
|
"default": "overridden"
|
|
}
|
|
}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": {
|
|
"qqx": "Document the documenter."
|
|
},
|
|
"params": {
|
|
"1d": {
|
|
"label": null,
|
|
"description": {
|
|
"qqx": "Description of the template parameter"
|
|
},
|
|
"example": null,
|
|
"required": true,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"default": {
|
|
"qqx": "example"
|
|
},
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
},
|
|
"2d": {
|
|
"label": null,
|
|
"description": {
|
|
"qqx": "Description of the template parameter"
|
|
},
|
|
"example": null,
|
|
"required": true,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"default": {
|
|
"qqx": "overridden"
|
|
},
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
}
|
|
},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'msg' => 'The inherits property copies over properties from another parameter '
|
|
. '(preserving overides)'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "sets": {} }',
|
|
'status' => '(templatedata-invalid-type: sets, array)'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "sets": [ [] ] }',
|
|
'status' => '(templatedata-invalid-value: sets.0)'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {},
|
|
"sets": [
|
|
{
|
|
"label": "Example"
|
|
}
|
|
]
|
|
}',
|
|
'status' => '(templatedata-invalid-missing: sets.0.params, array)'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "sets": [ { "label": "", "params": {} } ] }',
|
|
'status' => '(templatedata-invalid-type: sets.0.params, array)'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "sets": [ { "label": "", "params": [] } ] }',
|
|
'status' => '(templatedata-invalid-empty-array: sets.0.params)'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {
|
|
}
|
|
},
|
|
"sets": [
|
|
{
|
|
"params": ["foo"]
|
|
}
|
|
]
|
|
}',
|
|
'status' => '(templatedata-invalid-missing: sets.0.label, string|object)'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "sets": [ { "label": [] } ] }',
|
|
'status' => '(templatedata-invalid-type: sets.0.label, string|object)'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {
|
|
},
|
|
"bar": {
|
|
}
|
|
},
|
|
"sets": [
|
|
{
|
|
"label": "Foo with Quux",
|
|
"params": ["foo", "quux"]
|
|
}
|
|
]
|
|
}',
|
|
'status' => '(templatedata-invalid-value: sets.0.params[1])'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {
|
|
},
|
|
"bar": {
|
|
},
|
|
"quux": {
|
|
}
|
|
},
|
|
"sets": [
|
|
{
|
|
"label": "Foo with Quux",
|
|
"params": ["foo", "quux"]
|
|
},
|
|
{
|
|
"label": "Bar with Quux",
|
|
"params": ["bar", "quux"]
|
|
}
|
|
]
|
|
}',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {
|
|
"foo": {
|
|
"label": null,
|
|
"required": false,
|
|
"example": null,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
},
|
|
"bar": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
},
|
|
"quux": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
}
|
|
},
|
|
"sets": [
|
|
{
|
|
"label": {
|
|
"qqx": "Foo with Quux"
|
|
},
|
|
"params": ["foo", "quux"]
|
|
},
|
|
{
|
|
"label": {
|
|
"qqx": "Bar with Quux"
|
|
},
|
|
"params": ["bar", "quux"]
|
|
}
|
|
],
|
|
"format": null,
|
|
"maps": {}
|
|
}',
|
|
'status' => true
|
|
],
|
|
[
|
|
'input' => '{
|
|
"description": "Testing some template description.",
|
|
"params": {
|
|
"bar": {
|
|
"label": "Bar label",
|
|
"description": "Bar description",
|
|
"default": "Baz",
|
|
"example": "Foo bar baz",
|
|
"autovalue": "{{SomeTemplate}}",
|
|
"required": true,
|
|
"suggested": false,
|
|
"suggestedvalues": [ "baz", "boo" ],
|
|
"deprecated": false,
|
|
"aliases": [ "foo", "baz" ],
|
|
"type": "line"
|
|
}
|
|
}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": {
|
|
"qqx": "Testing some template description."
|
|
},
|
|
"params": {
|
|
"bar": {
|
|
"label": {
|
|
"qqx": "Bar label"
|
|
},
|
|
"description": {
|
|
"qqx": "Bar description"
|
|
},
|
|
"default": {
|
|
"qqx": "Baz"
|
|
},
|
|
"example": {
|
|
"qqx": "Foo bar baz"
|
|
},
|
|
"autovalue": "{{SomeTemplate}}",
|
|
"required": true,
|
|
"suggested": false,
|
|
"suggestedvalues": [ "baz", "boo" ],
|
|
"deprecated": false,
|
|
"aliases": [ "foo", "baz" ],
|
|
"type": "line"
|
|
}
|
|
},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'msg' => 'Parameter attributes preserve information.'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "maps": [] }',
|
|
'status' => '(templatedata-invalid-type: maps, object)'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "maps": { "a": [] } }',
|
|
'status' => '(templatedata-invalid-type: maps.a, object)'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "maps": { "a": { "b": "c" } } }',
|
|
'status' => '(templatedata-invalid-param: c, maps.a.b)'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "maps": { "a": { "b": [ {} ] } } }',
|
|
'status' => '(templatedata-invalid-type: maps.a.b[0], string|array)'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "maps": { "a": { "b": [ "c" ] } } }',
|
|
'status' => '(templatedata-invalid-param: c, maps.a.b[0])'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {
|
|
},
|
|
"bar": {
|
|
}
|
|
},
|
|
"sets": [],
|
|
"maps": {
|
|
"application": {
|
|
"things": [
|
|
"foo",
|
|
["bar", "quux"]
|
|
]
|
|
}
|
|
}
|
|
}',
|
|
'status' => '(templatedata-invalid-param: quux, maps.application.things[1][1])'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {
|
|
},
|
|
"bar": {
|
|
}
|
|
},
|
|
"sets": [],
|
|
"maps": {
|
|
"application": {
|
|
"things": {
|
|
"appbar": "bar",
|
|
"appfoo": "foo"
|
|
}
|
|
}
|
|
}
|
|
}',
|
|
'status' => '(templatedata-invalid-type: maps.application.things, string|array)'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {
|
|
},
|
|
"bar": {
|
|
}
|
|
},
|
|
"sets": [],
|
|
"maps": {
|
|
"application": {
|
|
"things": [
|
|
[ true ]
|
|
]
|
|
}
|
|
}
|
|
}',
|
|
'status' => '(templatedata-invalid-type: maps.application.things[0][0], string)'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {}
|
|
},
|
|
"format": "meshuggah format"
|
|
}',
|
|
'status' => '(templatedata-invalid-format: format)'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {},
|
|
"format": "inline"
|
|
}',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {},
|
|
"sets": [],
|
|
"format": "inline",
|
|
"maps": {}
|
|
}
|
|
',
|
|
'msg' => '"inline" is a valid format string',
|
|
'status' => true
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {},
|
|
"format": "block"
|
|
}',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {},
|
|
"sets": [],
|
|
"format": "block",
|
|
"maps": {}
|
|
}
|
|
',
|
|
'msg' => '"block" is a valid format string',
|
|
'status' => true
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {},
|
|
"format": "{{_ |\n ___ = _}}"
|
|
}',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {},
|
|
"sets": [],
|
|
"format": "{{_ |\n ___ = _}}",
|
|
"maps": {}
|
|
}
|
|
',
|
|
'msg' => 'Custom parameter format string (1)',
|
|
'status' => true
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {},
|
|
"format": "{{_|_=_\n}}\n"
|
|
}',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {},
|
|
"sets": [],
|
|
"format": "{{_|_=_\n}}\n",
|
|
"maps": {}
|
|
}
|
|
',
|
|
'msg' => 'Custom parameter format string (2)',
|
|
'status' => true
|
|
],
|
|
];
|
|
|
|
$calls = [];
|
|
foreach ( $cases as $case ) {
|
|
$calls[] = [ $case ];
|
|
}
|
|
return $calls;
|
|
}
|
|
|
|
private function getStatusText( Status $status ): string {
|
|
$str = Parser::stripOuterParagraph( $status->getHtml() );
|
|
// Unescape char references for things like "[, "]" and "|" for
|
|
// cleaner test assertions and output
|
|
$str = Sanitizer::decodeCharReferences( $str );
|
|
return $str;
|
|
}
|
|
|
|
private function ksort( array &$input ) {
|
|
ksort( $input );
|
|
foreach ( $input as &$value ) {
|
|
if ( is_array( $value ) ) {
|
|
$this->ksort( $value );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PHPUnit'a assertEquals does weak comparison, use strict instead.
|
|
*
|
|
* There is a built-in assertSame, but that only strictly compares
|
|
* the top level structure, not the invidual array values.
|
|
*
|
|
* so "array( 'a' => '' )" still equals "array( 'a' => null )"
|
|
* because empty string equals null in PHP's weak comparison.
|
|
*
|
|
* @param mixed $expected
|
|
* @param mixed $actual
|
|
* @param string|null $message
|
|
*/
|
|
private function assertStrictJsonEquals( $expected, $actual, $message = null ) {
|
|
// Lazy recursive strict comparison: Serialise to JSON and compare that
|
|
// Sort first to ensure key-order
|
|
$expected = json_decode( $expected, /* assoc = */ true );
|
|
$actual = json_decode( $actual, /* assoc = */ true );
|
|
$this->ksort( $expected );
|
|
$this->ksort( $actual );
|
|
|
|
$this->assertSame(
|
|
FormatJson::encode( $expected, true ),
|
|
FormatJson::encode( $actual, true ),
|
|
$message
|
|
);
|
|
}
|
|
|
|
private function assertTemplateData( array $case ) {
|
|
// Expand defaults
|
|
if ( !isset( $case['status'] ) ) {
|
|
$case['status'] = true;
|
|
}
|
|
if ( !isset( $case['msg'] ) ) {
|
|
$case['msg'] = is_string( $case['status'] ) ? $case['status'] : 'TemplateData assertion';
|
|
}
|
|
|
|
$t = TemplateDataBlob::newFromJSON( $this->db, $case['input'] );
|
|
/** @var TemplateDataBlob $t */
|
|
$t = TestingAccessWrapper::newFromObject( $t );
|
|
$actual = $t->getJSON();
|
|
$status = $t->getStatus();
|
|
|
|
$this->assertSame(
|
|
$case['status'],
|
|
is_string( $case['status'] ) ? $this->getStatusText( $status ) : $status->isGood(),
|
|
$case['msg'] . ' (status "' . $this->getStatusText( $status ) . '")'
|
|
);
|
|
|
|
if ( !isset( $case['output'] ) ) {
|
|
$expected = is_string( $case['status'] )
|
|
? '{ "description": null, "params": {}, "sets": [], "maps": {}, "format": null }'
|
|
: $case['input'];
|
|
$this->assertStrictJsonEquals( $expected, $actual, $case['msg'] );
|
|
} else {
|
|
$this->assertStrictJsonEquals( $case['output'], $actual, $case['msg'] );
|
|
|
|
// Assert this case roundtrips properly by running through the output as input.
|
|
$t = TemplateDataBlob::newFromJSON( $this->db, $case['output'] );
|
|
/** @var TemplateDataBlob $t */
|
|
$t = TestingAccessWrapper::newFromObject( $t );
|
|
$status = $t->getStatus();
|
|
|
|
if ( !$status->isGood() ) {
|
|
$this->assertSame( $case['status'], $this->getStatusText( $status ),
|
|
'Roundtrip status: ' . $case['msg']
|
|
);
|
|
}
|
|
$this->assertStrictJsonEquals( $case['output'], $t->getJSON(),
|
|
'Roundtrip: ' . $case['msg']
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideParse
|
|
*/
|
|
public function testParse( array $case ) {
|
|
$this->assertTemplateData( $case );
|
|
}
|
|
|
|
/**
|
|
* MySQL breaks if the input is too large even after compression
|
|
*/
|
|
public function testParseLongString() {
|
|
if ( $this->db->getType() !== 'mysql' ) {
|
|
$this->markTestSkipped( 'long compressed strings break on MySQL only' );
|
|
}
|
|
|
|
// Should be long enough to trigger this condition after gzipping.
|
|
$json = '{
|
|
"description": "' . $this->generatePseudorandomString( 100000, 42 ) . '",
|
|
"params": {}
|
|
}';
|
|
$templateData = TemplateDataBlob::newFromJSON( $this->db, $json );
|
|
|
|
$this->assertStringStartsWith(
|
|
'(templatedata-invalid-length: ',
|
|
$this->getStatusText( $templateData->getStatus() )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideInterfaceTexts
|
|
*/
|
|
public function testIsValidInterfaceText( $text, bool $expected ) {
|
|
/** @var TemplateDataValidator $validator */
|
|
$validator = TestingAccessWrapper::newFromObject(
|
|
new TemplateDataValidator()
|
|
);
|
|
$this->assertSame( $expected, $validator->isValidInterfaceText( $text ) );
|
|
}
|
|
|
|
public function provideInterfaceTexts() {
|
|
return [
|
|
// Invalid stuff
|
|
[ null, false ],
|
|
[ [], false ],
|
|
[ [ 'en' => 'example' ], false ],
|
|
[ (object)[], false ],
|
|
[ (object)[ null ], false ],
|
|
[ (object)[ 'en' => null ], false ],
|
|
|
|
[ 'example', true ],
|
|
[ (object)[ 'de' => 'Beispiel', 'en' => 'example' ], true ],
|
|
|
|
// Empty strings are allowed
|
|
[ '', true ],
|
|
[ (object)[ 'en' => '' ], true ],
|
|
|
|
// Language code can not be empty
|
|
[ (object)[ '' => 'example' ], false ],
|
|
[ (object)[ ' ' => 'example' ], false ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Verify we can gzdecode() which came in PHP 5.4.0. Mediawiki needs a
|
|
* fallback function for it.
|
|
* If this test fail, we are most probably attempting to use gzdecode()
|
|
* with PHP before 5.4.
|
|
*
|
|
* @see bug T56058
|
|
*
|
|
* Some databases will not be able to store compressed data cleanly
|
|
* but the object will be initialized properly even if compressed
|
|
* data are provided
|
|
*
|
|
* @see bug T203850
|
|
*/
|
|
public function testGetJsonForDatabase() {
|
|
// Compress JSON to trigger the code pass in newFromDatabase that ends
|
|
// up calling gzdecode().
|
|
$gzJson = gzencode( '{}' );
|
|
$templateData = TemplateDataBlob::newFromDatabase( $this->db, $gzJson );
|
|
$this->assertInstanceOf( TemplateDataBlob::class, $templateData );
|
|
}
|
|
|
|
public function provideGetDataInLanguage() {
|
|
$cases = [
|
|
[
|
|
'input' => '{
|
|
"description": {
|
|
"de": "German",
|
|
"nl": "Dutch",
|
|
"en": "English",
|
|
"de-formal": "German (formal address)"
|
|
},
|
|
"params": {}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": "German",
|
|
"params": {},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'lang' => 'de',
|
|
'msg' => 'Simple description'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"description": "Hi",
|
|
"params": {}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": "Hi",
|
|
"params": {},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'lang' => 'fr',
|
|
'msg' => 'Non multi-language value returned as is (expands to { "en": value } for' .
|
|
' content-lang, "fr" falls back to "en")'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"description": {
|
|
"nl": "Dutch",
|
|
"de": "German"
|
|
},
|
|
"params": {}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": "Dutch",
|
|
"params": {},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'lang' => 'fr',
|
|
'msg' => 'Try content language before giving up on user language and fallbacks'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"description": {
|
|
"es": "Spanish",
|
|
"de": "German"
|
|
},
|
|
"params": {}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'lang' => 'fr',
|
|
'msg' => 'Description is optional, use null if no suitable fallback'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"description": {
|
|
"de": "German",
|
|
"nl": "Dutch",
|
|
"en": "English"
|
|
},
|
|
"params": {}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": "German",
|
|
"params": {},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'lang' => 'de-formal',
|
|
'msg' => '"de-formal" falls back to "de"'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {
|
|
"label": {
|
|
"fr": "French",
|
|
"en": "English"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {
|
|
"foo": {
|
|
"label": "French",
|
|
"required": false,
|
|
"example": null,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
}
|
|
},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'lang' => 'fr',
|
|
'msg' => 'Simple parameter label'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {
|
|
"default": {
|
|
"fr": "French",
|
|
"en": "English"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {
|
|
"foo": {
|
|
"default": "French",
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"label": null,
|
|
"type": "unknown",
|
|
"autovalue": null,
|
|
"example": null
|
|
}
|
|
},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'lang' => 'fr',
|
|
'msg' => 'Simple parameter default value'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {
|
|
"label": {
|
|
"es": "Spanish",
|
|
"de": "German"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {
|
|
"foo": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
}
|
|
},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'lang' => 'fr',
|
|
'msg' => 'Parameter label is optional, use null if no matching fallback'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {}
|
|
},
|
|
"sets": [
|
|
{
|
|
"label": {
|
|
"es": "Spanish",
|
|
"de": "German"
|
|
},
|
|
"params": ["foo"]
|
|
}
|
|
]
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {
|
|
"foo": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
}
|
|
},
|
|
"sets": [
|
|
{
|
|
"label": "Spanish",
|
|
"params": ["foo"]
|
|
}
|
|
],
|
|
"format": null,
|
|
"maps": {}
|
|
}
|
|
',
|
|
'lang' => 'fr',
|
|
'msg' => 'Set label is not optional, choose first available key as final fallback'
|
|
],
|
|
];
|
|
$calls = [];
|
|
foreach ( $cases as $case ) {
|
|
$calls[] = [ $case ];
|
|
}
|
|
return $calls;
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetDataInLanguage
|
|
*/
|
|
public function testGetDataInLanguage( array $case ) {
|
|
// Change content-language to be non-English so we can distinguish between the
|
|
// last 'en' fallback and the content language in our tests
|
|
$this->setContentLang( 'nl' );
|
|
|
|
if ( !isset( $case['msg'] ) ) {
|
|
$case['msg'] = is_string( $case['status'] ) ? $case['status'] : 'TemplateData assertion';
|
|
}
|
|
|
|
$t = TemplateDataBlob::newFromJSON( $this->db, $case['input'] );
|
|
$status = $t->getStatus();
|
|
|
|
$this->assertTrue(
|
|
$status->isGood() ?: $this->getStatusText( $status ),
|
|
'Status is good: ' . $case['msg']
|
|
);
|
|
|
|
$actual = $t->getDataInLanguage( $case['lang'] );
|
|
$this->assertJsonStringEqualsJsonString(
|
|
$case['output'],
|
|
json_encode( $actual ),
|
|
$case['msg']
|
|
);
|
|
}
|
|
|
|
public function provideParamOrder() {
|
|
$cases = [
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {},
|
|
"bar": {},
|
|
"baz": {}
|
|
}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {
|
|
"foo": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
},
|
|
"bar": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
},
|
|
"baz": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
}
|
|
},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'msg' => 'Normalisation adds paramOrder'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {},
|
|
"bar": {},
|
|
"baz": {}
|
|
},
|
|
"paramOrder": ["baz", "foo", "bar"]
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {
|
|
"foo": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
},
|
|
"bar": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
},
|
|
"baz": {
|
|
"label": null,
|
|
"required": false,
|
|
"suggested": false,
|
|
"suggestedvalues": [],
|
|
"description": null,
|
|
"example": null,
|
|
"deprecated": false,
|
|
"aliases": [],
|
|
"default": null,
|
|
"type": "unknown",
|
|
"autovalue": null
|
|
}
|
|
},
|
|
"paramOrder": ["baz", "foo", "bar"],
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'msg' => 'Custom paramOrder'
|
|
],
|
|
[
|
|
'input' => '{ "params": {}, "paramOrder": {} }',
|
|
'status' => '(templatedata-invalid-type: paramOrder, array)',
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {},
|
|
"bar": {},
|
|
"baz": {}
|
|
},
|
|
"paramOrder": ["foo", "bar"]
|
|
}
|
|
',
|
|
'status' => '(templatedata-invalid-missing: paramOrder[2])',
|
|
'msg' => 'Incomplete paramOrder'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {}
|
|
}
|
|
',
|
|
'output' => '{
|
|
"description": null,
|
|
"params": {},
|
|
"sets": [],
|
|
"format": null,
|
|
"maps" : {}
|
|
}
|
|
',
|
|
'msg' => 'Empty parameter object produces empty array paramOrder'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {},
|
|
"bar": {},
|
|
"baz": {}
|
|
},
|
|
"paramOrder": ["foo", "bar", "baz", "quux"]
|
|
}
|
|
',
|
|
'status' => '(templatedata-invalid-value: paramOrder[3])',
|
|
'msg' => 'Unknown params in paramOrder'
|
|
],
|
|
[
|
|
'input' => '{
|
|
"params": {
|
|
"foo": {},
|
|
"bar": {},
|
|
"baz": {}
|
|
},
|
|
"paramOrder": ["foo", "bar", "baz", "bar"]
|
|
}
|
|
',
|
|
'status' => '(templatedata-invalid-duplicate-value: paramOrder[3], paramOrder[1], bar)',
|
|
'msg' => 'Duplicate params in paramOrder'
|
|
],
|
|
];
|
|
$calls = [];
|
|
foreach ( $cases as $case ) {
|
|
$calls[] = [ $case ];
|
|
}
|
|
return $calls;
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideParamOrder
|
|
*/
|
|
public function testParamOrder( array $case ) {
|
|
$this->assertTemplateData( $case );
|
|
}
|
|
|
|
/**
|
|
* @covers \MediaWiki\Extension\TemplateData\Api\ApiTemplateData
|
|
* @dataProvider provideGetRawParams
|
|
*/
|
|
public function testGetRawParams( string $inputWikitext, array $expectedParams ) {
|
|
/** @var ApiTemplateData $api */
|
|
$api = TestingAccessWrapper::newFromObject( $this->createMock( ApiTemplateData::class ) );
|
|
$params = $api->getRawParams( $inputWikitext );
|
|
$this->assertArrayEquals( $expectedParams, $params, true, true );
|
|
}
|
|
|
|
public function provideGetRawParams() {
|
|
return [
|
|
'No params' => [
|
|
'Lorem ipsum {{tpl}}.',
|
|
[]
|
|
],
|
|
'Two plain params' => [
|
|
'Lorem {{{name}}} ipsum {{{surname}}}',
|
|
[ 'name' => [], 'surname' => [] ]
|
|
],
|
|
'Param with multiple casing and default value' => [
|
|
'Lorem {{{name|{{{Name|Default name}}}}}} ipsum',
|
|
[ 'name' => [] ]
|
|
],
|
|
'Param name contains comment' => [
|
|
'Lorem {{{name<!-- comment -->}}} ipsum',
|
|
[ 'name' => [] ]
|
|
],
|
|
'Letter-case and underscore-space normalization' => [
|
|
'Lorem {{{First name|{{{first_name}}}}}} ipsum {{{first-Name}}}',
|
|
[ 'First name' => [] ]
|
|
],
|
|
'Dynamic param name' => [
|
|
'{{{{{#if:{{{nominee|}}}|nominee|candidate}}|}}}',
|
|
[ 'nominee' => [] ]
|
|
],
|
|
'More complicated dynamic param name' => [
|
|
'{{{party{{#if:{{{party_election||}}}|_election||}}|}}}',
|
|
[ 'party_election' => [] ]
|
|
],
|
|
'Bang in a param name' => [
|
|
'{{{!}}} {{{foo!}}}',
|
|
[ '!' => [], 'foo!' => [] ]
|
|
],
|
|
'Bang as a magic word in a table construct' => [
|
|
'{{{!}} class=""',
|
|
[]
|
|
],
|
|
'Params within comments and nowiki tags' => [
|
|
'Lorem <!-- {{{name}}} --> ipsum <nowiki > {{{middlename}}}' .
|
|
'</nowiki> <pre>{{{pre}}}</pre> {{{surname}}}',
|
|
[ 'surname' => [] ]
|
|
],
|
|
'Param within comments and param name outside with comment' => [
|
|
'Lorem {{{name<!--comment-->}}} ipsum <!--{{{surname}}}-->',
|
|
[ 'name' => [] ]
|
|
],
|
|
'safesubst: hack with an unnamed parameter' => [
|
|
'{{ {{{|safesubst:}}}#invoke:…|{{{1}}}|{{{ 1 }}}}}',
|
|
[ '1' => [] ]
|
|
],
|
|
'Characters impossible in parameter names' => [
|
|
'{{test|a|b=c|d=e=f}} {{{a|b}}} {{{d=e}}}',
|
|
[ 'a' => [] ]
|
|
],
|
|
'Characters that are, while technically possible, almost certainly a mistake' => [
|
|
"{{{a{a}}} {{{b}b}}} {{{c\nc}}}",
|
|
[]
|
|
],
|
|
'Table syntax escaped with {{!}}' => [
|
|
'{{{!}}\n! This is table syntax\n{{!}}}',
|
|
[]
|
|
],
|
|
];
|
|
}
|
|
|
|
public function provideGetHtml() {
|
|
// phpcs:disable Generic.Files.LineLength.TooLong
|
|
yield 'No params' => [
|
|
[ 'params' => [ (object)[] ] ],
|
|
<<<HTML
|
|
<section class="mw-templatedata-doc-wrap">
|
|
<header><p class="mw-templatedata-doc-desc mw-templatedata-doc-muted">(templatedata-doc-desc-empty)</p></header>
|
|
<table class="wikitable mw-templatedata-doc-params">
|
|
<caption><p>(templatedata-doc-params)</p></caption>
|
|
<thead><tr><th colspan="2">(templatedata-doc-param-name)</th><th>(templatedata-doc-param-desc)</th><th>(templatedata-doc-param-type)</th><th>(templatedata-doc-param-status)</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="mw-templatedata-doc-muted" colspan="7">(templatedata-doc-no-params-set)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
HTML
|
|
];
|
|
yield 'Basic params' => [
|
|
[ 'params' => [ 'foo' => (object)[], 'bar' => [ 'required' => true ] ] ],
|
|
<<<HTML
|
|
<section class="mw-templatedata-doc-wrap">
|
|
<header><p class="mw-templatedata-doc-desc mw-templatedata-doc-muted">(templatedata-doc-desc-empty)</p></header>
|
|
<table class="wikitable mw-templatedata-doc-params sortable">
|
|
<caption><p>(templatedata-doc-params)</p></caption>
|
|
<thead><tr><th colspan="2">(templatedata-doc-param-name)</th><th>(templatedata-doc-param-desc)</th><th>(templatedata-doc-param-type)</th><th>(templatedata-doc-param-status)</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<th>foo</th>
|
|
<td class="mw-templatedata-doc-param-name"><code>foo</code></td>
|
|
<td><p class="mw-templatedata-doc-muted">(templatedata-doc-param-desc-empty)</p><dl></dl></td>
|
|
<td class="mw-templatedata-doc-param-type mw-templatedata-doc-muted">(templatedata-doc-param-type-unknown)</td>
|
|
<td class="mw-templatedata-doc-param-status-optional" data-sort-value="0">(templatedata-doc-param-status-optional)</td>
|
|
</tr>
|
|
<tr>
|
|
<th>bar</th>
|
|
<td class="mw-templatedata-doc-param-name"><code>bar</code></td>
|
|
<td><p class="mw-templatedata-doc-muted">(templatedata-doc-param-desc-empty)</p><dl></dl></td>
|
|
<td class="mw-templatedata-doc-param-type mw-templatedata-doc-muted">(templatedata-doc-param-type-unknown)</td>
|
|
<td class="mw-templatedata-doc-param-status-required" data-sort-value="2">(templatedata-doc-param-status-required)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
HTML
|
|
];
|
|
yield [
|
|
[ 'description' => 'Template docs', 'params' => [
|
|
'suggestedParam' => [
|
|
'label' => 'Label',
|
|
'description' => 'Param docs',
|
|
'aliases' => [ 'Alias1', 'Alias2' ],
|
|
'suggestedvalues' => [ 'Suggested1', 'Suggested2' ],
|
|
'suggested' => true,
|
|
'default' => 'Default docs',
|
|
'example' => 'Example docs',
|
|
'autovalue' => 'Auto value',
|
|
],
|
|
'deprecatedParam' => [ 'type' => 'date', 'deprecated' => true ],
|
|
] ],
|
|
<<<HTML
|
|
<section class="mw-templatedata-doc-wrap">
|
|
<header><p class="mw-templatedata-doc-desc">Template docs</p></header>
|
|
<table class="wikitable mw-templatedata-doc-params sortable">
|
|
<caption><p>(templatedata-doc-params)</p></caption>
|
|
<thead><tr><th colspan="2">(templatedata-doc-param-name)</th><th>(templatedata-doc-param-desc)</th><th>(templatedata-doc-param-type)</th><th>(templatedata-doc-param-status)</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<th>Label</th>
|
|
<td class="mw-templatedata-doc-param-name"><code>suggestedParam</code>(word-separator)<code class="mw-templatedata-doc-param-alias">Alias1</code>(word-separator)<code class="mw-templatedata-doc-param-alias">Alias2</code></td>
|
|
<td>
|
|
<p>Param docs</p>
|
|
<dl>
|
|
<dt>(templatedata-doc-param-suggestedvalues)</dt><dd><code>Suggested1</code>(word-separator)<code>Suggested2</code></dd>
|
|
<dt>(templatedata-doc-param-default)</dt><dd>Default docs</dd>
|
|
<dt>(templatedata-doc-param-example)</dt><dd>Example docs</dd>
|
|
<dt>(templatedata-doc-param-autovalue)</dt><dd><code>Auto value</code></dd>
|
|
</dl>
|
|
</td>
|
|
<td class="mw-templatedata-doc-param-type mw-templatedata-doc-muted">(templatedata-doc-param-type-unknown)</td>
|
|
<td class="mw-templatedata-doc-param-status-suggested" data-sort-value="1">(templatedata-doc-param-status-suggested)</td>
|
|
</tr>
|
|
<tr>
|
|
<th>deprecatedParam</th>
|
|
<td class="mw-templatedata-doc-param-name"><code>deprecatedParam</code></td>
|
|
<td><p class="mw-templatedata-doc-muted">(templatedata-doc-param-desc-empty)</p><dl></dl></td>
|
|
<td class="mw-templatedata-doc-param-type">(templatedata-doc-param-type-date)</td>
|
|
<td class="mw-templatedata-doc-param-status-deprecated" data-sort-value="-1">(templatedata-doc-param-status-deprecated)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
HTML
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @covers \MediaWiki\Extension\TemplateData\TemplateDataHtmlFormatter
|
|
* @dataProvider provideGetHtml
|
|
*/
|
|
public function testGetHtml( array $data, $expected ) {
|
|
$t = TemplateDataBlob::newFromJSON( $this->db, json_encode( $data ) );
|
|
$localizer = new class implements MessageLocalizer {
|
|
public function msg( $key, ...$params ) {
|
|
return new RawMessage( "($key)" );
|
|
}
|
|
};
|
|
$formatter = new TemplateDataHtmlFormatter( $localizer );
|
|
$actual = $formatter->getHtml( $t );
|
|
$linedActual = preg_replace( '/>\s*</', ">\n<", $actual );
|
|
|
|
$linedExpected = preg_replace( '/>\s*</', ">\n<", trim( $expected ) );
|
|
|
|
$this->assertSame( $linedExpected, $linedActual );
|
|
}
|
|
}
|