Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Tests for the core_rtlcss class.
  19   *
  20   * The core_rtlcss class extends \MoodleHQ\RTLCSS\RTLCSS library which depends on sabberworm/php-css-parser library.
  21   * This test verifies that css parsing works as expected should any of the above change.
  22   *
  23   * @package    core
  24   * @category   phpunit
  25   * @copyright  2019 Jake Dallimore <jrhdallimore@gmail.com>
  26   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  use Sabberworm\CSS\Parser;
  31  use Sabberworm\CSS\OutputFormat;
  32  
  33  /**
  34   * Class rtlcss_test.
  35   */
  36  class rtlcss_test extends basic_testcase {
  37      /**
  38       * Data provider.
  39       * @return array
  40       */
  41      public function background_image_provider() {
  42          return [
  43              /* Not supported by MoodleHQ/RTLCSS yet.
  44              [[
  45                  'should' => 'Should process string map in url (processUrls:true)',
  46                  'expected' => 'div { background-image: url(images/rtl.png), url(images/right.png);}',
  47                  'input'    => 'div { background-image: url(images/ltr.png), url(images/left.png);}',
  48                  'reversable' => true,
  49                  'options' => [ 'processUrls' => true ],
  50                  'skip' => true
  51              ]],
  52              [[
  53                  'should' => 'Should not negate color value for linear gradient',
  54                  'expected' => 'div { background-image: linear-gradient(rgba(255, 255, 255, 0.3) 0%, #ff8 100%);}',
  55                  'input'    => 'div { background-image: linear-gradient(rgba(255, 255, 255, 0.3) 0%, #ff8 100%);}',
  56                  'reversable' => true,
  57                  'skip' => true
  58              ]],
  59              [[
  60                  'should' => 'Should not negate color value for linear gradient with calc',
  61                  'expected' => 'div { background-image: linear-gradient(rgba(255, 255, calc((125 * 2) + 5), 0.3) 0%, #ff8 100%);}',
  62                  'input'    => 'div { background-image: linear-gradient(rgba(255, 255, calc((125 * 2) + 5), 0.3) 0%, #ff8 100%);}',
  63                  'reversable' => true,
  64                  'skip' => true
  65              ]],
  66              [[
  67                  'should' => 'Should negate angle value for linear gradient',
  68                  'expected' => 'div { background-image: linear-gradient(13.25deg, rgba(255, 255, 255, .15) 25%, transparent 25%);}',
  69                  'input'    => 'div { background-image: linear-gradient(-13.25deg, rgba(255, 255, 255, .15) 25%, transparent 25%);}',
  70                  'reversable' => true,
  71                  'skip' => true
  72              ]]
  73              */
  74          ];
  75      }
  76  
  77      /**
  78       * Data provider.
  79       * @return array
  80       */
  81      public function background_position_provider() {
  82          return [
  83              [[
  84                  'should' => 'Should complement percentage horizontal position ',
  85                  'expected' => 'div {background-position:100% 75%;}',
  86                  'input' => 'div {background-position:0 75%;}',
  87                  'reversable' => false
  88              ]],
  89              /* Not supported by MoodleHQ/RTLCSS yet.
  90              [[
  91                  'should' => 'Should complement percentage horizontal position with calc',
  92                  'expected' => 'div {background-position:calc(100% - (30% + 50px)) 75%;}',
  93                  'input' => 'div {background-position:calc(30% + 50px) 75%;}',
  94                  'reversable' => false,
  95                  'skip' => true
  96              ]],
  97              [[
  98                  'should' => 'Should complement percentage horizontal position ',
  99                  'expected' => 'div {background-position:81.25% 75%, 10.75% top;}',
 100                  'input' => 'div {background-position:18.75% 75%, 89.25% top;}',
 101                  'reversable' => true,
 102                  'skip' => true
 103              ]],
 104              [[
 105                  'should' => 'Should complement percentage horizontal position with calc',
 106                  'expected' => 'div {background-position:calc(100% - (30% + 50px)) calc(30% + 50px), 10.75% top;}',
 107                  'input' => 'div {background-position:calc(30% + 50px) calc(30% + 50px), 89.25% top;}',
 108                  'reversable' => false,
 109                  'skip' => true
 110              ]],
 111              [[
 112                  'should' => 'Should swap left with right',
 113                  'expected' => 'div {background-position:right 75%, left top;}',
 114                  'input' => 'div {background-position:left 75%, right top;}',
 115                  'reversable' => true,
 116                  'skip' => true
 117              ]],
 118              [[
 119                  'should' => 'Should swap left with right wit calc',
 120                  'expected' => 'div {background-position:right -ms-calc(30% + 50px), left top;}',
 121                  'input' => 'div {background-position:left -ms-calc(30% + 50px), right top;}',
 122                  'reversable' => true,
 123                  'skip' => true
 124              ]],
 125              */
 126              [[
 127                  'should' => 'Should complement percentage: position-x (treat 0 as 0%)',
 128                  'expected' => 'div {background-position-x:100%, 0%;}',
 129                  'input' => 'div {background-position-x:0, 100%;}',
 130                  'reversable' => false
 131              ]],
 132              [[
 133                  'should' => 'Should complement percentage: position-x',
 134                  'expected' => 'div {background-position-x:81.75%, 11%;}',
 135                  'input' => 'div {background-position-x:18.25%, 89%;}',
 136                  'reversable' => true
 137              ]],
 138              /* Not supported by MoodleHQ/RTLCSS yet.
 139              [[
 140                  'should' => 'Should complement percentage with calc: position-x',
 141                  'expected' => 'div {background-position-x:calc(100% - (30% + 50px)), -webkit-calc(100% - (30% + 50px));}',
 142                  'input' => 'div {background-position-x:calc(30% + 50px), -webkit-calc(30% + 50px);}',
 143                  'reversable' => false,
 144                  'skip' => true
 145              ]],
 146              */
 147              [[
 148                  'should' => 'Should swap left with right: position-x',
 149                  'expected' => 'div {background-position-x:right, left;}',
 150                  'input' => 'div {background-position-x:left, right;}',
 151                  'reversable' => true
 152              ]],
 153              [[
 154                  'should' => 'Should keep as is: position-x',
 155                  'expected' => 'div {background-position-x:100px, 0px;}',
 156                  'input' => 'div {background-position-x:100px, 0px;}',
 157                  'reversable' => true
 158              ]],
 159  
 160              [[
 161                  'should' => 'Should flip when using 3 positions',
 162                  'expected' => 'div {background-position:center right 1px;}',
 163                  'input' => 'div {background-position:center left 1px;}',
 164                  'reversable' => true
 165              ]],
 166              [[
 167                  'should' => 'Should flip when using 4 positions',
 168                  'expected' => 'div {background-position:center 2px right 1px;}',
 169                  'input' => 'div {background-position:center 2px left 1px;}',
 170                  'reversable' => true
 171              ]],
 172              [[
 173                  'should' => 'Should flip when using 4 positions mixed',
 174                  'expected' => 'div {background-position:right 2px bottom 1px;}',
 175                  'input' => 'div {background-position:left 2px bottom 1px;}',
 176                  'reversable' => true
 177              ]]
 178          ];
 179      }
 180  
 181      /**
 182       * Data provider.
 183       * @return array
 184       */
 185      public function background_provider() {
 186          return [
 187              [[
 188                  'should' => 'Should treat 0 as 0%',
 189                  'expected' => '.banner { background: 100% top url("topbanner.png") #00d repeat-y fixed; }',
 190                  'input' => '.banner { background: 0 top url("topbanner.png") #00d repeat-y fixed; }',
 191                  'reversable' => false
 192              ]],
 193              [[
 194                  'should' => 'Should complement percentage horizontal position',
 195                  'expected' => '.banner { background: 81% top url("topbanner.png") #00d repeat-y fixed; }',
 196                  'input' => '.banner { background: 19% top url("topbanner.png") #00d repeat-y fixed; }',
 197                  'reversable' => true
 198              ]],
 199              /* Not supported by MoodleHQ/RTLCSS yet.
 200              [[
 201                  'should' => 'Should complement calc horizontal position',
 202                  'expected' => '.banner { background: calc(100% - (19% + 2px)) top url(topbanner.png) #00d repeat-y fixed; }',
 203                  'input' => '.banner { background: calc(19% + 2px) top url(topbanner.png) #00d repeat-y fixed; }',
 204                  'reversable' => false,
 205                  'skip' => true
 206              ]],
 207              */
 208              [[
 209                  'should' => 'Should mirror keyword horizontal position',
 210                  'expected' => '.banner { background: right top url("topbanner.png") #00d repeat-y fixed; }',
 211                  'input' => '.banner { background: left top url("topbanner.png") #00d repeat-y fixed; }',
 212                  'reversable' => true
 213              ]],
 214              [[
 215                  'should' => 'Should not process string map in url (default)',
 216                  'expected' => '.banner { background: 10px top url("ltr-top-right-banner.png") #00d repeat-y fixed; }',
 217                  'input' => '.banner { background: 10px top url("ltr-top-right-banner.png") #00d repeat-y fixed; }',
 218                  'reversable' => true
 219              ]],
 220              /* Not supported by MoodleHQ/RTLCSS yet.
 221              [[
 222                  'should' => 'Should process string map in url (processUrls:true)',
 223                  'expected' => '.banner { background: 10px top url(rtl-top-left-banner.png) #00d repeat-y fixed; }',
 224                  'input' => '.banner { background: 10px top url(ltr-top-right-banner.png) #00d repeat-y fixed; }',
 225                  'reversable' => true,
 226                  'options' => [ 'processUrls' => true ],
 227                  'skip' => true
 228              ]],
 229              [[
 230                  'should' => 'Should process string map in url (processUrls:{decl:true})',
 231                  'expected' => '.banner { background: 10px top url(rtl-top-left-banner.png) #00d repeat-y fixed; }',
 232                  'input' => '.banner { background: 10px top url(ltr-top-right-banner.png) #00d repeat-y fixed; }',
 233                  'reversable' => true,
 234                  'options' => [ 'processUrls' => [ 'decl' => true ] ],
 235                  'skip' => true
 236              ]],
 237              */
 238              [[
 239                  'should' => 'Should not process string map in url (processUrls:{atrule:true})',
 240                  'expected' => '.banner { background: 10px top url("ltr-top-right-banner.png") #00d repeat-y fixed; }',
 241                  'input' => '.banner { background: 10px top url("ltr-top-right-banner.png") #00d repeat-y fixed; }',
 242                  'reversable' => true,
 243                  'options' => [ 'processUrls' => [ 'atrule' => true ] ]
 244              ]],
 245              [[
 246                  'should' => 'Should not swap bright:bleft, ultra:urtla',
 247                  'expected' => '.banner { background: 10px top url("ultra/bright.png") #00d repeat-y fixed; }',
 248                  'input' => '.banner { background: 10px top url("ultra/bright.png") #00d repeat-y fixed; }',
 249                  'reversable' => true
 250              ]],
 251              /* Not supported by MoodleHQ/RTLCSS yet.
 252              [[
 253                  'should' => 'Should swap bright:bleft, ultra:urtla (processUrls: true, greedy)',
 254                  'expected' => '.banner { background: 10px top url("urtla/bleft.png") #00d repeat-y fixed; }',
 255                  'input' => '.banner { background: 10px top url("ultra/bright.png") #00d repeat-y fixed; }',
 256                  'reversable' => true,
 257                  'options' => [ 'processUrls' => true, 'greedy' => true ],
 258                  'skip' => true
 259              ]],
 260              */
 261              [[
 262                  'should' => 'Should not flip hex colors ',
 263                  'expected' => '.banner { background: #ff0; }',
 264                  'input' => '.banner { background: #ff0; }',
 265                  'reversable' => true
 266              ]]
 267          ];
 268      }
 269  
 270      /**
 271       * Data provider.
 272       * @return array
 273       */
 274      public function directives_provider() {
 275          return [
 276              [[
 277                  'should' => 'Should ignore flipping - rule level (default)',
 278                  'expected' => 'div {left:10px;text-align:right;}',
 279                  'input' => '/*rtl:ignore*/div { left:10px; text-align:right;}',
 280                  'reversable' => false
 281              ]],
 282              [[
 283                  'should' => 'Should ignore flipping - rule level (!important comment)',
 284                  'expected' => 'div {left:10px;text-align:right;}',
 285                  'input' => '/*!rtl:ignore*/div { left:10px; text-align:right;}',
 286                  'reversable' => false,
 287              ]],
 288              // Not supported by MoodleHQ/RTLCSS yet.
 289              //[[
 290              //    'should' => 'Should ignore flipping - decl. level (default)',
 291              //    'expected' => 'div {left:10px;text-align:left;}',
 292              //    'input' => 'div {left:10px/*rtl:ignore*/;text-align:right;}',
 293              //    'reversable' => false,
 294              //    'skip' => true
 295              //]],
 296              [[
 297                  'should' => 'Should add raw css rules',
 298                  'expected' => 'div {left:10px;text-align:right;} a {display:block;}',
 299                  'input' => '/*rtl:raw: div { left:10px;text-align:right;}*/ a {display:block;}',
 300                  'reversable' => false
 301              ]],
 302              [[
 303                  'should' => 'Should add raw css declarations',
 304                  'expected' => 'div {font-family:"Droid Kufi Arabic";right:10px;text-align:left;}',
 305                  'input' => 'div { /*rtl:raw: font-family: "Droid Kufi Arabic";*/ left:10px;text-align:right;}',
 306                  'reversable' => false
 307              ]],
 308              [[
 309                  'should' => 'Should support block-style',
 310                  'expected' => 'div {left:10px;text-align:right;}',
 311                  'input' => ' div {/*rtl:begin:ignore*/left:10px;/*rtl:end:ignore*/ text-align:left;}',
 312                  'reversable' => false
 313              ]],
 314              [[
 315                  'should' => 'Should support none block-style',
 316                  'expected' => 'div {left:10px;text-align:left;}',
 317                  'input' => ' /*rtl:ignore*/div {left:10px; text-align:left;}',
 318                  'reversable' => false
 319              ]],
 320              [[
 321                  'should' => 'Should remove rules (block-style)',
 322                  'expected' => 'b {float:right;}',
 323                  'input' => ' /*rtl:begin:remove*/div {left:10px; text-align:left;} a { display:block;} /*rtl:end:remove*/ b{float:left;}',
 324                  'reversable' => false
 325              ]],
 326              [[
 327                  'should' => 'Should remove rules',
 328                  'expected' => 'a {display:block;} b {float:right;}',
 329                  'input' => ' /*rtl:remove*/div {left:10px; text-align:left;} a { display:block;} b{float:left;}',
 330                  'reversable' => false
 331              ]],
 332              [[
 333                  'should' => 'Should remove declarations',
 334                  'expected' => 'div {text-align:right;}',
 335                  'input' => 'div {/*rtl:remove*/left:10px; text-align:left;}',
 336                  'reversable' => false
 337              ]],
 338              [[
 339                  'should' => 'Should remove declarations (block-style)',
 340                  'expected' => 'div {display:inline;}',
 341                  'input' => 'div {/*rtl:begin:remove*/left:10px; text-align:left;/*rtl:end:remove*/ display:inline;}',
 342                  'reversable' => false
 343              ]],
 344              // Not supported by MoodleHQ/RTLCSS yet.
 345              //[[
 346              //    'should' => 'Final/trailing comment ignored bug (block style): note a tag rules are NOT flipped as they should be',
 347              //    'expected' => 'div {left:10px;text-align:left;} a {right:10px;}',
 348              //    'input' => 'div {/*rtl:begin:ignore*/left:10px; text-align:left;/*rtl:end:ignore*/} a {left:10px;}',
 349              //    'reversable' => false,
 350              //    'skip' => true
 351              //]]
 352          ];
 353      }
 354  
 355      /**
 356       * Data provider.
 357       * @return array
 358       */
 359      public function properties_provider() {
 360          return [
 361              [[
 362                  'should' => 'Should mirror property name: border-top-right-radius',
 363                  'expected' => 'div { border-top-left-radius:15px; }',
 364                  'input' => 'div { border-top-right-radius:15px; }',
 365                  'reversable' => true
 366              ]],
 367              [[
 368                  'should' => 'Should mirror property name: border-bottom-right-radius',
 369                  'expected' => 'div { border-bottom-left-radius:15px; }',
 370                  'input' => 'div { border-bottom-right-radius:15px; }',
 371                  'reversable' => true
 372              ]],
 373              [[
 374                  'should' => 'Should mirror property name: border-left',
 375                  'expected' => 'div { border-right:1px solid black; }',
 376                  'input' => 'div { border-left:1px solid black; }',
 377                  'reversable' => true
 378              ]],
 379              [[
 380                  'should' => 'Should mirror property name: border-left-color',
 381                  'expected' => 'div { border-right-color:black; }',
 382                  'input' => 'div { border-left-color:black; }',
 383                  'reversable' => true
 384              ]],
 385              [[
 386                  'should' => 'Should mirror property name: border-left-style',
 387                  'expected' => 'div { border-right-style:solid; }',
 388                  'input' => 'div { border-left-style:solid; }',
 389                  'reversable' => true
 390              ]],
 391              [[
 392                  'should' => 'Should mirror property name: border-left-width',
 393                  'expected' => 'div { border-right-width:1em; }',
 394                  'input' => 'div { border-left-width:1em; }',
 395                  'reversable' => true
 396              ]],
 397              [[
 398                  'should' => 'Should mirror property name: left',
 399                  'expected' => 'div { right:auto; }',
 400                  'input' => 'div { left:auto; }',
 401                  'reversable' => true
 402              ]],
 403              [[
 404                  'should' => 'Should mirror property name: margin-left',
 405                  'expected' => 'div { margin-right:2em; }',
 406                  'input' => 'div { margin-left:2em; }',
 407                  'reversable' => true
 408              ]],
 409              [[
 410                  'should' => 'Should mirror property name: padding-left',
 411                  'expected' => 'div { padding-right:2em; }',
 412                  'input' => 'div { padding-left:2em; }',
 413                  'reversable' => true
 414              ]]
 415          ];
 416      }
 417  
 418      /**
 419       * Data provider.
 420       * @return array
 421       */
 422      public function special_provider() {
 423          return [
 424              /* Not supported by MoodleHQ/RTLCSS yet.
 425              [[
 426                  'should' => 'Should not negate tokens',
 427                  'expected' => 'div { box-shadow: rgba(0, 128, 128, .98) inset -5em 1em 0;}',
 428                  'input' => 'div { box-shadow: rgba(0, 128, 128, .98) inset 5em 1em 0;}',
 429                  'reversable' => true,
 430                  'skip' => true,
 431              ]]
 432              */
 433          ];
 434      }
 435  
 436      /**
 437       * Data provider.
 438       * @return array
 439       */
 440      public function transform_origin_provider() {
 441          return [
 442              [[
 443                  'should' => 'Should mirror (x-offset: 0 means 0%)',
 444                  'expected' => 'div { transform-origin:100%; }',
 445                  'input' => 'div { transform-origin:0; }',
 446                  'reversable' => false
 447              ]],
 448              [[
 449                  'should' => 'Should mirror (x-offset)',
 450                  'expected' => 'div { transform-origin:90.25%; }',
 451                  'input' => 'div { transform-origin:9.75%; }',
 452                  'reversable' => true
 453              ]],
 454              /* Not supported by MoodleHQ/RTLCSS yet.
 455              [[
 456                  'should' => 'Should mirror calc (x-offset)',
 457                  'expected' => 'div { transform-origin: -moz-calc(100% - (((25%/2) * 10px))); }',
 458                  'input' => 'div { transform-origin: -moz-calc(((25%/2) * 10px)); }',
 459                  'reversable' => false,
 460                  'skip' => true
 461              ]],
 462              */
 463              [[
 464                  'should' => 'Should not mirror (x-offset: not percent, not calc)',
 465                  'expected' => 'div { transform-origin:10.75px; }',
 466                  'input' => 'div { transform-origin:10.75px; }',
 467                  'reversable' => false
 468              ]],
 469              [[
 470                  'should' => 'Should mirror (offset-keyword)',
 471                  'expected' => 'div { transform-origin:right; }',
 472                  'input' => 'div { transform-origin:left; }',
 473                  'reversable' => true
 474              ]],
 475              [[
 476                  'should' => 'Should mirror (x-offset y-offset: 0 means 0%)',
 477                  'expected' => 'div { transform-origin:100% 0; }',
 478                  'input' => 'div { transform-origin:0 0; }',
 479                  'reversable' => false
 480              ]],
 481              /* Not supported by MoodleHQ/RTLCSS yet.
 482              [[
 483                  'should' => 'Should mirror with y being calc (x-offset y-offset: 0 means 0%)',
 484                  'expected' => 'div { transform-origin:100% -webkit-calc(15% * (3/2)); }',
 485                  'input' => 'div { transform-origin:0 -webkit-calc(15% * (3/2)); }',
 486                  'reversable' => false,
 487                  'skip' => true
 488              ]],
 489              */
 490              [[
 491                  'should' => 'Should mirror percent (x-offset y-offset)',
 492                  'expected' => 'div { transform-origin:30.25% 10%; }',
 493                  'input' => 'div { transform-origin:69.75% 10%; }',
 494                  'reversable' => true
 495              ]],
 496              /* Not supported by MoodleHQ/RTLCSS yet.
 497              [[
 498                  'should' => 'Should mirror with x being calc (x-offset y-offset)',
 499                  'expected' => 'div { transform-origin: -webkit-calc(100% - (15% * (3/2))) 30.25%; }',
 500                  'input' => 'div { transform-origin: -webkit-calc(15% * (3/2)) 30.25%; }',
 501                  'reversable' => false,
 502                  'skip' => true
 503              ]],
 504              [[
 505                  'should' => 'Should mirror with y being calc (x-offset y-offset)',
 506                  'expected' => 'div { transform-origin:30.25% calc(15% * (3/2)); }',
 507                  'input' => 'div { transform-origin:69.75% calc(15% * (3/2)); }',
 508                  'reversable' => true,
 509                  'skip' => true
 510              ]],
 511              */
 512              [[
 513                  'should' => 'Should mirror (y-offset x-offset-keyword)',
 514                  'expected' => 'div { transform-origin:70% right; }',
 515                  'input' => 'div { transform-origin:70% left; }',
 516                  'reversable' => true
 517              ]],
 518              /* Not supported by MoodleHQ/RTLCSS yet.
 519              [[
 520                  'should' => 'Should mirror with calc (y-offset x-offset-keyword)',
 521                  'expected' => 'div { transform-origin:-ms-calc(140%/2) right; }',
 522                  'input' => 'div { transform-origin:-ms-calc(140%/2) left; }',
 523                  'reversable' => true,
 524                  'skip' => true
 525              ]],
 526              */
 527              [[
 528                  'should' => 'Should mirror (x-offset-keyword y-offset)',
 529                  'expected' => 'div { transform-origin:right 70%; }',
 530                  'input' => 'div { transform-origin:left 70%; }',
 531                  'reversable' => true
 532              ]],
 533              /* Not supported by MoodleHQ/RTLCSS yet.
 534              [[
 535                  'should' => 'Should mirror with calc (x-offset-keyword y-offset)',
 536                  'expected' => 'div { transform-origin:right -moz-calc(((140%/2))); }',
 537                  'input' => 'div { transform-origin:left -moz-calc(((140%/2))); }',
 538                  'reversable' => true,
 539                  'skip' => true
 540              ]],
 541              */
 542              [[
 543                  'should' => 'Should mirror (y-offset-keyword x-offset)',
 544                  'expected' => 'div { transform-origin:top 30.25%; }',
 545                  'input' => 'div { transform-origin:top 69.75%; }',
 546                  'reversable' => true
 547              ]],
 548              /* Not supported by MoodleHQ/RTLCSS yet.
 549              [[
 550                  'should' => 'Should not mirror with x being calc (y-offset-keyword x-offset)',
 551                  'expected' => 'div { transform-origin:top calc(100% - (((140%/2)))); }',
 552                  'input' => 'div { transform-origin:top calc(((140%/2))); }',
 553                  'reversable' => false,
 554                  'skip' => true
 555              ]],
 556              */
 557              [[
 558                  'should' => 'Should mirror (x-offset-keyword y-offset-keyword)',
 559                  'expected' => 'div { transform-origin:right top; }',
 560                  'input' => 'div { transform-origin:left top; }',
 561                  'reversable' => true
 562              ]],
 563              [[
 564                  'should' => 'Should mirror (y-offset-keyword x-offset-keyword)',
 565                  'expected' => 'div { transform-origin:top right; }',
 566                  'input' => 'div { transform-origin:top left; }',
 567                  'reversable' => true
 568              ]],
 569              [[
 570                  'should' => 'Should mirror (x-offset y-offset z-offset)',
 571                  'expected' => 'div { transform-origin:80.25% 30% 10%; }',
 572                  'input' => 'div { transform-origin:19.75% 30% 10%; }',
 573                  'reversable' => true
 574              ]],
 575              /* Not supported by MoodleHQ/RTLCSS yet.
 576              [[
 577                  'should' => 'Should mirror with x being calc (x-offset y-offset z-offset)',
 578                  'expected' => 'div { transform-origin: calc(100% - (25% * 3 + 20px)) 30% 10%; }',
 579                  'input' => 'div { transform-origin: calc(25% * 3 + 20px) 30% 10%; }',
 580                  'reversable' => false,
 581                  'skip' => true
 582              ]],
 583              */
 584              [[
 585                  'should' => 'Should mirror (y-offset x-offset-keyword z-offset)',
 586                  'expected' => 'div { transform-origin:20% right 10%; }',
 587                  'input' => 'div { transform-origin:20% left 10%; }',
 588                  'reversable' => true
 589              ]],
 590              [[
 591                  'should' => 'Should mirror (x-offset-keyword y-offset z-offset)',
 592                  'expected' => 'div { transform-origin:left 20% 10%; }',
 593                  'input' => 'div { transform-origin:right 20% 10%; }',
 594                  'reversable' => true
 595              ]],
 596              [[
 597                  'should' => 'Should mirror (x-offset-keyword y-offset-keyword z-offset)',
 598                  'expected' => 'div { transform-origin:left bottom 10%; }',
 599                  'input' => 'div { transform-origin:right bottom 10%; }',
 600                  'reversable' => true
 601              ]],
 602              [[
 603                  'should' => 'Should mirror (y-offset-keyword x-offset-keyword z-offset)',
 604                  'expected' => 'div { transform-origin:bottom left 10%; }',
 605                  'input' => 'div { transform-origin:bottom right 10%; }',
 606                  'reversable' => true
 607              ]]
 608          ];
 609      }
 610  
 611      /**
 612       * Data provider.
 613       * @return array
 614       */
 615      public function transforms_provider() {
 616          return [
 617              /* Not supported by MoodleHQ/RTLCSS yet.
 618              [[
 619                  'should' => 'Should mirror transform : matrix',
 620                  'expected' => 'div { transform: matrix(2, 0.1, 20.75, 2, 2, 2); }',
 621                  'input' => 'div { transform: matrix(2, -0.1, -20.75, 2, -2, 2); }',
 622                  'reversable' => true,
 623                  'skip' => true
 624              ]],
 625              [[
 626                  'should' => 'Should mirror transform (with no digits before dot): matrix',
 627                  'expected' => 'div { transform: matrix(2, 0.1, 0.75, 2, 2, 2); }',
 628                  'input' => 'div { transform: matrix(2, -0.1, -.75, 2, -2, 2); }',
 629                  'reversable' => false,
 630                  'skip' => true
 631              ]],
 632              [[
 633                  'should' => 'Should mirror transform with calc: matrix',
 634                  'expected' => 'div { transform: matrix( -moz-calc(((25%/2) * 10px)), calc(-1*(((25%/2) * 10px))), 20.75, 2, 2, 2 ); }',
 635                  'input' => 'div { transform: matrix( -moz-calc(((25%/2) * 10px)), calc(((25%/2) * 10px)), -20.75, 2, -2, 2 ); }',
 636                  'reversable' => false,
 637                  'skip' => true
 638              ]],
 639              [[
 640                  'should' => 'Should mirror transform : matrix3d',
 641                  'expected' => 'div { transform:matrix3d(0.227114470162179, 0.127248412323519, 0, 0.000811630714323203, 0.113139853456515, 1.53997196559414, 0, 0.000596368270149729, 0, 0, 1, 0, -165, 67, 0, 1); }',
 642                  'input' => 'div { transform:matrix3d(0.227114470162179, -0.127248412323519, 0, -0.000811630714323203, -0.113139853456515, 1.53997196559414, 0, 0.000596368270149729, 0, 0, 1, 0, 165, 67, 0, 1); }',
 643                  'reversable' => true,
 644                  'skip' => true
 645              ]],
 646              [[
 647                  'should' => 'Should mirror transform (with no digits before dot): matrix3d',
 648                  'expected' => 'div { transform:matrix3d(0.227114470162179, 0.127248412323519, 0, 0.000811630714323203, 0.113139853456515, 1.53997196559414, 0, 0.000596368270149729, 0, 0, 1, 0, -165, 67, 0, 1); }',
 649                  'input' => 'div { transform:matrix3d(0.227114470162179, -.127248412323519, 0, -0.000811630714323203, -0.113139853456515, 1.53997196559414, 0, 0.000596368270149729, 0, 0, 1, 0, 165, 67, 0, 1); }',
 650                  'reversable' => false,
 651                  'skip' => true
 652              ]],
 653              [[
 654                  'should' => 'Should mirror transform with calc : matrix3d',
 655                  'expected' => 'div { transform:matrix3d(0.227114470162179, 0.127248412323519, 0, 0.000811630714323203, 0.113139853456515, 1.53997196559414, 0, 0.000596368270149729, 0, 0, 1, 0, calc(-1*(((25%/2) * 10px))), 67, 0, 1); }',
 656                  'input' => 'div { transform:matrix3d(0.227114470162179, -0.127248412323519, 0, -0.000811630714323203, -0.113139853456515, 1.53997196559414, 0, 0.000596368270149729, 0, 0, 1, 0, calc(((25%/2) * 10px)), 67, 0, 1); }',
 657                  'reversable' => false,
 658                  'skip' => true
 659              ]],
 660              [[
 661                  'should' => 'Should mirror transform : translate',
 662                  'expected' => 'div { transform: translate(-10.75px); }',
 663                  'input' => 'div { transform: translate(10.75px); }',
 664                  'reversable' => true,
 665                  'skip' => true
 666              ]],
 667              [[
 668                  'should' => 'Should mirror transform (with no digits before dot): translate',
 669                  'expected' => 'div { transform: translate(-0.75px); }',
 670                  'input' => 'div { transform: translate(.75px); }',
 671                  'reversable' => false,
 672                  'skip' => true
 673              ]],
 674              [[
 675                  'should' => 'Should mirror transform with calc: translate',
 676                  'expected' => 'div { transform: translate(-moz-calc(-1*(((25%/2) * 10px)))); }',
 677                  'input' => 'div { transform: translate(-moz-calc(((25%/2) * 10px))); }',
 678                  'reversable' => false,
 679                  'skip' => true
 680              ]],
 681              [[
 682                  'should' => 'Should mirror transform : translateX',
 683                  'expected' => 'div { transform: translateX(-50.25px); }',
 684                  'input' => 'div { transform: translateX(50.25px); }',
 685                  'reversable' => true,
 686                  'skip' => true
 687              ]],
 688              [[
 689                  'should' => 'Should mirror transform (with no digits before dot): translateX',
 690                  'expected' => 'div { transform: translateX(-0.25px); }',
 691                  'input' => 'div { transform: translateX(.25px); }',
 692                  'reversable' => false,
 693                  'skip' => true
 694              ]],
 695              [[
 696                  'should' => 'Should mirror transform with calc : translateX',
 697                  'expected' => 'div { transform: translateX(-ms-calc(-1*(((25%/2) * 10px))))); }',
 698                  'input' => 'div { transform: translateX(-ms-calc(((25%/2) * 10px)))); }',
 699                  'reversable' => false,
 700                  'skip' => true
 701              ]],
 702              [[
 703                  'should' => 'Should mirror transform : translate3d',
 704                  'expected' => 'div { transform: translate3d(-12.75px, 50%, 3em); }',
 705                  'input' => 'div { transform: translate3d(12.75px, 50%, 3em); }',
 706                  'reversable' => true,
 707                  'skip' => true
 708              ]],
 709              [[
 710                  'should' => 'Should mirror transform (with no digits before dot): translate3d',
 711                  'expected' => 'div { transform: translate3d(-0.75px, 50%, 3em); }',
 712                  'input' => 'div { transform: translate3d(.75px, 50%, 3em); }',
 713                  'reversable' => false,
 714                  'skip' => true
 715              ]],
 716              [[
 717                  'should' => 'Should mirror transform with calc: translate3d',
 718                  'expected' => 'div { transform: translate3d(-webkit-calc(-1*(((25%/2) * 10px))))), 50%, calc(((25%/2) * 10px))))); }',
 719                  'input' => 'div { transform: translate3d(-webkit-calc(((25%/2) * 10px)))), 50%, calc(((25%/2) * 10px))))); }',
 720                  'reversable' => false,
 721                  'skip' => true
 722              ]],
 723              [[
 724                  'should' => 'Should mirror transform : rotate',
 725                  'expected' => 'div { transform: rotate(-20.75deg); }',
 726                  'input' => 'div { transform: rotate(20.75deg); }',
 727                  'reversable' => true,
 728                  'skip' => true
 729              ]],
 730              [[
 731                  'should' => 'Should mirror transform (with no digits before dot): rotate',
 732                  'expected' => 'div { transform: rotate(-0.75deg); }',
 733                  'input' => 'div { transform: rotate(.75deg); }',
 734                  'reversable' => false,
 735                  'skip' => true
 736              ]],
 737              [[
 738                  'should' => 'Should mirror transform with calc: rotate',
 739                  'expected' => 'div { transform: rotate(calc(-1*(((25%/2) * 10deg)))); }',
 740                  'input' => 'div { transform: rotate(calc(((25%/2) * 10deg))); }',
 741                  'reversable' => false,
 742                  'skip' => true
 743              ]],
 744              [[
 745                  'should' => 'Should mirror transform : rotate3d',
 746                  'expected' => 'div { transform: rotate3d(10, -20.15, 10, -45.14deg); }',
 747                  'input' => 'div { transform: rotate3d(10, 20.15, 10, 45.14deg); }',
 748                  'reversable' => true,
 749                  'skip' => true
 750              ]],
 751              [[
 752                  'should' => 'Should mirror transform (with no digits before dot): rotate3d',
 753                  'expected' => 'div { transform: rotate3d(10, -20, 10, -0.14deg); }',
 754                  'input' => 'div { transform: rotate3d(10, 20, 10, .14deg); }',
 755                  'reversable' => false,
 756                  'skip' => true
 757              ]],
 758              [[
 759                  'should' => 'Should mirror transform with calc: rotate3d',
 760                  'expected' => 'div { transform: rotate3d(10, -20.15, 10, calc(-1*(((25%/2) * 10deg)))); }',
 761                  'input' => 'div { transform: rotate3d(10, 20.15, 10, calc(((25%/2) * 10deg))); }',
 762                  'reversable' => false,
 763                  'skip' => true
 764              ]],
 765              [[
 766                  'should' => 'Should not mirror transform : rotateX',
 767                  'expected' => 'div { transform: rotateX(45deg); }',
 768                  'input' => 'div { transform: rotateX(45deg); }',
 769                  'reversable' => false,
 770                  'skip' => true
 771              ]],
 772              [[
 773                  'should' => 'Should not mirror transform with calc: rotateX',
 774                  'expected' => 'div { transform: rotateX(calc(((25%/2) * 10deg))); }',
 775                  'input' => 'div { transform: rotateX(calc(((25%/2) * 10deg))); }',
 776                  'reversable' => false,
 777                  'skip' => true
 778              ]],
 779              [[
 780                  'should' => 'Should not mirror transform : rotateY',
 781                  'expected' => 'div { transform: rotateY(45deg); }',
 782                  'input' => 'div { transform: rotateY(45deg); }',
 783                  'reversable' => false,
 784                  'skip' => true
 785              ]],
 786              [[
 787                  'should' => 'Should not mirror transform with calc: rotateY',
 788                  'expected' => 'div { transform: rotateY(calc(((25%/2) * 10deg))); }',
 789                  'input' => 'div { transform: rotateY(calc(((25%/2) * 10deg))); }',
 790                  'reversable' => false,
 791                  'skip' => true
 792              ]],
 793              [[
 794                  'should' => 'Should mirror transform : rotateZ',
 795                  'expected' => 'div { transform: rotateZ(-45.75deg); }',
 796                  'input' => 'div { transform: rotateZ(45.75deg); }',
 797                  'reversable' => true,
 798                  'skip' => true
 799              ]],
 800              [[
 801                  'should' => 'Should mirror transform (with no digits before dot): rotateZ',
 802                  'expected' => 'div { transform: rotateZ(-0.75deg); }',
 803                  'input' => 'div { transform: rotateZ(.75deg); }',
 804                  'reversable' => false,
 805                  'skip' => true
 806              ]],
 807              [[
 808                  'should' => 'Should mirror transform with calc: rotateZ',
 809                  'expected' => 'div { transform: rotateZ(-ms-calc(-1*(((25%/2) * 10deg)))); }',
 810                  'input' => 'div { transform: rotateZ(-ms-calc(((25%/2) * 10deg))); }',
 811                  'reversable' => false,
 812                  'skip' => true
 813              ]],
 814              [[
 815                  'should' => 'Should mirror transform : skew',
 816                  'expected' => 'div { transform: skew(-20.25rad,-30deg); }',
 817                  'input' => 'div { transform: skew(20.25rad,30deg); }',
 818                  'reversable' => true,
 819                  'skip' => true
 820              ]],
 821              [[
 822                  'should' => 'Should mirror transform (with no digits before dot): skew',
 823                  'expected' => 'div { transform: skew(-0.25rad,-30deg); }',
 824                  'input' => 'div { transform: skew(.25rad,30deg); }',
 825                  'reversable' => false,
 826                  'skip' => true
 827              ]],
 828              [[
 829                  'should' => 'Should mirror transform with calc: skew',
 830                  'expected' => 'div { transform: skew(calc(-1*(((25%/2) * 10rad))),calc(-1*(((25%/2) * 10deg)))); }',
 831                  'input' => 'div { transform: skew(calc(((25%/2) * 10rad)),calc(((25%/2) * 10deg))); }',
 832                  'reversable' => false,
 833                  'skip' => true
 834              ]],
 835              [[
 836                  'should' => 'Should mirror transform : skewX',
 837                  'expected' => 'div { transform: skewX(-20.75rad); }',
 838                  'input' => 'div { transform: skewX(20.75rad); }',
 839                  'reversable' => true,
 840                  'skip' => true
 841              ]],
 842              [[
 843                  'should' => 'Should mirror transform (with no digits before dot): skewX',
 844                  'expected' => 'div { transform: skewX(-0.75rad); }',
 845                  'input' => 'div { transform: skewX(.75rad); }',
 846                  'reversable' => false,
 847                  'skip' => true
 848              ]],
 849              [[
 850                  'should' => 'Should mirror transform with calc: skewX',
 851                  'expected' => 'div { transform: skewX(-moz-calc(-1*(((25%/2) * 10rad)))); }',
 852                  'input' => 'div { transform: skewX(-moz-calc(((25%/2) * 10rad))); }',
 853                  'reversable' => false,
 854                  'skip' => true
 855              ]],
 856              [[
 857                  'should' => 'Should mirror transform : skewY',
 858                  'expected' => 'div { transform: skewY(-10.75grad); }',
 859                  'input' => 'div { transform: skewY(10.75grad); }',
 860                  'reversable' => true,
 861                  'skip' => true
 862              ]],
 863              [[
 864                  'should' => 'Should mirror transform (with no digits before dot): skewY',
 865                  'expected' => 'div { transform: skewY(-0.75grad); }',
 866                  'input' => 'div { transform: skewY(.75grad); }',
 867                  'reversable' => false,
 868                  'skip' => true
 869              ]],
 870              [[
 871                  'should' => 'Should mirror transform with calc: skewY',
 872                  'expected' => 'div { transform: skewY(calc(-1*(((25%/2) * 10grad)))); }',
 873                  'input' => 'div { transform: skewY(calc(((25%/2) * 10grad))); }',
 874                  'reversable' => false,
 875                  'skip' => true
 876              ]],
 877              [[
 878                  'should' => 'Should mirror multiple transforms : translateX translateY Rotate',
 879                  'expected' => 'div { transform: translateX(-50.25px) translateY(50.25px) rotate(-20.75deg); }',
 880                  'input' => 'div { transform: translateX(50.25px) translateY(50.25px) rotate(20.75deg); }',
 881                  'reversable' => true,
 882                  'skip' => true
 883              ]],
 884              [[
 885                  'should' => 'Should mirror multiple transforms with calc : translateX translateY Rotate',
 886                  'expected' => 'div { transform: translateX(-ms-calc(-1*(((25%/2) * 10px)))) translateY(-moz-calc(((25%/2) * 10rad))) rotate(calc(-1*(((25%/2) * 10grad)))); }',
 887                  'input' => 'div { transform: translateX(-ms-calc(((25%/2) * 10px))) translateY(-moz-calc(((25%/2) * 10rad))) rotate(calc(((25%/2) * 10grad))); }',
 888                  'reversable' => false,
 889                  'skip' => true
 890              ]]
 891              */
 892          ];
 893      }
 894  
 895      /**
 896       * Data provider.
 897       * @return array
 898       */
 899      public function values_nsyntax_provider() {
 900          return [
 901              [[
 902                  'should' => 'Should mirror property value: border-radius (4 values)',
 903                  'expected' => 'div { border-radius: 40.25px 10.5px 10.75px 40.3px; }',
 904                  'input' => 'div { border-radius: 10.5px 40.25px 40.3px 10.75px; }',
 905                  'reversable' => true
 906              ]],
 907              [[
 908                  'should' => 'Should mirror property value: border-radius (3 values)',
 909                  'expected' => 'div { border-radius: 40.75px 10.75px 40.75px 40.3px; }',
 910                  'input' => 'div { border-radius: 10.75px 40.75px 40.3px; }',
 911                  'reversable' => false
 912              ]],
 913              [[
 914                  'should' => 'Should mirror property value: border-radius (2 values)',
 915                  'expected' => 'div { border-radius: 40.25px 10.75px; }',
 916                  'input' => 'div { border-radius: 10.75px 40.25px; }',
 917                  'reversable' => true
 918              ]],
 919              /* Not supported by MoodleHQ/RTLCSS yet.
 920              [[
 921                  'should' => 'Should mirror property value: border-radius (4 values - double)',
 922                  'expected' => 'div { border-radius: 40.25px 10.75px .5px 40.75px / .4em 1em 1em 4.5em; }',
 923                  'input' => 'div { border-radius: 10.75px 40.25px 40.75px .5px / 1em .4em 4.5em 1em; }',
 924                  'reversable' => true,
 925                  'skip' => true
 926              ]],
 927              [[
 928                  'should' => 'Should mirror property value: border-radius (3 values - double)',
 929                  'expected' => 'div { border-radius: .40px 10.5px .40px 40px / 4em 1em 4em 3em; }',
 930                  'input' => 'div { border-radius: 10.5px .40px 40px / 1em 4em 3em; }',
 931                  'reversable' => false,
 932                  'skip' => true
 933              ]],
 934              [[
 935                  'should' => 'Should mirror property value: border-radius (2 values- double)',
 936                  'expected' => 'div { border-radius: 40px 10px / 2.5em .75em; }',
 937                  'input' => 'div { border-radius: 10px 40px / .75em 2.5em; }',
 938                  'reversable' => true,
 939                  'skip' => true
 940              ]],
 941              */
 942              [[
 943                  'should' => 'Should mirror property value: border-width',
 944                  'expected' => 'div { border-width: 1px 4px .3em 2.5em; }',
 945                  'input' => 'div { border-width: 1px 2.5em .3em 4px; }',
 946                  'reversable' => true
 947              ]],
 948              [[
 949                  'should' => 'Should mirror property value: border-width (none length)',
 950                  'expected' => 'div { border-width: thin medium thick none; }',
 951                  'input' => 'div { border-width: thin none thick medium; }',
 952                  'reversable' => true
 953              ]],
 954              [[
 955                  'should' => 'Should mirror property value: border-style (4 values)',
 956                  'expected' => 'div { border-style: none dashed dotted solid; }',
 957                  'input' => 'div { border-style: none solid dotted dashed; }',
 958                  'reversable' => true
 959              ]],
 960              [[
 961                  'should' => 'Should mirror property value: border-color (4 values)',
 962                  'expected' => 'div { border-color: rgba(255, 255, 255, 1) rgb(0, 0, 0) rgb(0, 0, 0) hsla(0, 100%, 50%, 1); }',
 963                  'input' => 'div { border-color: rgba(255, 255, 255, 1) hsla(0, 100%, 50%, 1) rgb(0, 0, 0) rgb(0, 0, 0); }',
 964                  'reversable' => true
 965              ]],
 966              [[
 967                  'should' => 'Should not mirror property value: border-color (3 values)',
 968                  'expected' => 'div { border-color: rgb(0, 0, 0) rgb(0, 0, 0) hsla(0, 100%, 50%, 1); }',
 969                  'input' => 'div { border-color: #000 rgb(0, 0, 0) hsla(0, 100%, 50%, 1); }',
 970                  'reversable' => false
 971              ]],
 972              [[
 973                  'should' => 'Should not mirror property value: border-color (2 values)',
 974                  'expected' => 'div { border-color: rgb(0, 0, 0) hsla(0, 100%, 50%, 1); }',
 975                  'input' => 'div { border-color: rgb(0, 0, 0) hsla(0, 100%, 50%, 1); }',
 976                  'reversable' => false
 977              ]],
 978              [[
 979                  'should' => 'Should mirror property value: margin',
 980                  'expected' => 'div { margin: .1em auto 3.5rem 2px; }',
 981                  'input' => 'div { margin: .1em 2px 3.5rem auto; }',
 982                  'reversable' => true
 983              ]],
 984              [[
 985                  'should' => 'Should mirror property value: padding',
 986                  'expected' => 'div { padding: 1px 4px .3rem 2.5em; }',
 987                  'input' => 'div { padding: 1px 2.5em .3rem 4px; }',
 988                  'reversable' => true
 989              ]],
 990              /* Not supported by MoodleHQ/RTLCSS yet.
 991              [[
 992                  'should' => 'Should mirror property value: box-shadow',
 993                  'expected' => 'div { box-shadow: -60px -16px rgba(0, 128, 128, 0.98), -10.25px 5px 5px #ff0, inset -0.5em 1em 0 white; }',
 994                  'input' => 'div { box-shadow: 60px -16px rgba(0, 128, 128, 0.98), 10.25px 5px 5px #ff0, inset 0.5em 1em 0 white; }',
 995                  'reversable' => true,
 996                  'skip' => true
 997              ]],
 998              [[
 999                  'should' => 'Should mirror property value: text-shadow',
1000                  'expected' => 'div { text-shadow: -60px -16px rgba(0, 128, 128, 0.98), -10.25px 5px 5px #ff0, inset -0.5em 1em 0 white; }',
1001                  'input' => 'div { text-shadow: 60px -16px rgba(0, 128, 128, 0.98), 10.25px 5px 5px #ff0, inset 0.5em 1em 0 white; }',
1002                  'reversable' => true,
1003                  'skip' => true
1004              ]],
1005              [[
1006                  'should' => 'Should mirror property value (no digit before the dot): box-shadow, text-shadow',
1007                  'expected' => 'div { box-shadow: inset -0.5em 1em 0 white; text-shadow: inset -0.5em 1em 0 white; }',
1008                  'input' => 'div { box-shadow: inset .5em 1em 0 white; text-shadow: inset .5em 1em 0 white; }',
1009                  'reversable' => false,
1010                  'skip' => true
1011              ]]
1012              */
1013          ];
1014      }
1015  
1016      /**
1017       * Data provider.
1018       * @return array
1019       */
1020      public function values_provider() {
1021          return [
1022              [[
1023                  'should' => 'Should mirror property value: clear',
1024                  'expected' => 'div { clear:right; }',
1025                  'input' => 'div { clear:left; }',
1026                  'reversable' => true
1027              ]],
1028              [[
1029                  'should' => 'Should mirror property value: direction',
1030                  'expected' => 'div { direction:ltr; }',
1031                  'input' => 'div { direction:rtl; }',
1032                  'reversable' => true
1033              ]],
1034              [[
1035                  'should' => 'Should mirror property value: float',
1036                  'expected' => 'div { float:right; }',
1037                  'input' => 'div { float:left; }',
1038                  'reversable' => true
1039              ]],
1040              [[
1041                  'should' => 'Should mirror property value: text-align',
1042                  'expected' => 'div { text-align:right; }',
1043                  'input' => 'div { text-align:left; }',
1044                  'reversable' => true
1045              ]],
1046              [[
1047                  'should' => 'Should mirror property value: cursor nw',
1048                  'expected' => 'div { cursor:nw-resize; }',
1049                  'input' => 'div { cursor:ne-resize; }',
1050                  'reversable' => true
1051              ]],
1052              [[
1053                  'should' => 'Should mirror property value: cursor sw',
1054                  'expected' => 'div { cursor:sw-resize; }',
1055                  'input' => 'div { cursor:se-resize; }',
1056                  'reversable' => true
1057              ]],
1058              [[
1059                  'should' => 'Should mirror property value: cursor nesw',
1060                  'expected' => 'div { cursor:nesw-resize; }',
1061                  'input' => 'div { cursor:nwse-resize; }',
1062                  'reversable' => true
1063              ]],
1064              [[
1065                  'should' => 'Should keep property value as is: cursor ew',
1066                  'expected' => 'div { cursor:ew-resize; }',
1067                  'input' => 'div { cursor:ew-resize; }',
1068                  'reversable' => false
1069              ]],
1070              /* Not supported by MoodleHQ/RTLCSS yet.
1071              [[
1072                  'should' => 'Should process string map in url: cursor (processUrls: true)',
1073                  'expected' => '.foo { cursor: url(right.cur), url(rtl.cur), se-resize, auto }',
1074                  'input' => '.foo { cursor: url(left.cur), url(ltr.cur), sw-resize, auto }',
1075                  'reversable' => true,
1076                  'options' => [ 'processUrls' => true ],
1077                  'skip' => true
1078              ]],
1079              */
1080              [[
1081                  'should' => 'Should mirror property value: transition',
1082                  'expected' => '.foo { transition:right .3s ease .1s,left .3s ease .1s,margin-right .3s ease,margin-left .3s ease,padding-right .3s ease,padding-left .3s ease; }',
1083                  'input' => '.foo { transition:left .3s ease .1s,right .3s ease .1s,margin-left .3s ease,margin-right .3s ease,padding-left .3s ease,padding-right .3s ease; }',
1084                  'reversable' => true
1085              ]],
1086              [[
1087                  'should' => 'Should mirror property value: transition-property',
1088                  'expected' => '.foo { transition-property:right; }',
1089                  'input' => '.foo { transition-property:left; }',
1090                  'reversable' => true
1091              ]]
1092          ];
1093      }
1094  
1095      /**
1096       * Assert that the provided data flips.
1097       *
1098       * @param string $expected The expected output.
1099       * @param string $input The input.
1100       * @param string $description The description of the assertion.
1101       * @param OutputFormat $output The output format to use.
1102       */
1103      protected function assert_flips($expected, $input, $description, $output = null) {
1104          $parser = new Parser($input);
1105          $tree = $parser->parse();
1106          $rtlcss = new core_rtlcss($tree);
1107          $flipped = $rtlcss->flip();
1108          $this->assertEquals($expected, $flipped->render($output), $description);
1109      }
1110  
1111      /**
1112       * Assert data.
1113       *
1114       * @param array $data With the keys: 'input', 'expected', 'reversable', 'should', and 'skip'.
1115       * @param OutputFormat $output The output format to use.
1116       */
1117      protected function assert_sample($data, $output = null) {
1118          if (!empty($data['skip'])) {
1119              $this->markTestSkipped('Not yet supported!');
1120          }
1121          $this->assert_flips($data['expected'], $data['input'], $data['should'], $output);
1122          if (!empty($data['reversable'])) {
1123              $this->assert_flips($data['input'], $data['expected'], $data['should'] . ' (reversed)', $output);
1124          }
1125      }
1126  
1127      /**
1128       * Test background images.
1129       * @param array $data the provider data.
1130       * @dataProvider background_image_provider
1131       */
1132      /* Not supported by MoodleHQ/RTLCSS yet.
1133      public function test_background_image($data) {
1134          $output = new OutputFormat();
1135          $this->assert_sample($data, $output);
1136      }
1137      */
1138  
1139      /**
1140       * Test background position.
1141       * @param array $data the provider data.
1142       * @dataProvider background_position_provider
1143       */
1144      public function test_background_position($data) {
1145          $output = new OutputFormat();
1146          $output->set('SpaceAfterRuleName', '');
1147          $output->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
1148          $this->assert_sample($data, $output);
1149      }
1150  
1151      /**
1152       * Test background.
1153       * @param array $data the provider data.
1154       * @dataProvider background_provider
1155       */
1156      public function test_background($data) {
1157          $output = new OutputFormat();
1158          $output->set('SpaceAfterRuleName', ' ');
1159          $output->set('SpaceBeforeRules', ' ');
1160          $output->set('SpaceAfterRules', ' ');
1161          $output->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
1162          $this->assert_sample($data, $output);
1163      }
1164  
1165      /**
1166       * Test directives.
1167       * @param array $data the provider data.
1168       * @dataProvider directives_provider
1169       */
1170      public function test_directives($data) {
1171          $output = new OutputFormat();
1172          $output->set('SpaceAfterRuleName', '');
1173          $output->set('SpaceBeforeRules', '');
1174          $output->set('SpaceAfterRules', '');
1175          $output->set('SpaceBetweenRules', '');
1176          $output->set('SpaceBetweenBlocks', ' ');
1177          $output->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
1178          $this->assert_sample($data, $output);
1179      }
1180  
1181      /**
1182       * Test properties.
1183       * @param array $data the provider data.
1184       * @dataProvider properties_provider
1185       */
1186      public function test_properties($data) {
1187          $output = new OutputFormat();
1188          $output->set('SpaceAfterRuleName', '');
1189          $output->set('SpaceBeforeRules', ' ');
1190          $output->set('SpaceAfterRules', ' ');
1191          $output->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
1192          $this->assert_sample($data, $output);
1193      }
1194  
1195      /**
1196       * Test special.
1197       * @param array $data the provider data.
1198       * @dataProvider special_provider
1199       */
1200      /* Not supported by MoodleHQ/RTLCSS yet.
1201      public function test_special($data) {
1202          $output = new OutputFormat();
1203          $output->set('SpaceBeforeRules', ' ');
1204          $output->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
1205          $this->assert_sample($data, $output);
1206      }
1207      */
1208  
1209      /**
1210       * Test transform original.
1211       * @param array $data the provider data.
1212       * @dataProvider transform_origin_provider
1213       */
1214      public function test_transform_origin($data) {
1215          $output = new OutputFormat();
1216          $output->set('SpaceAfterRuleName', '');
1217          $output->set('SpaceBeforeRules', ' ');
1218          $output->set('SpaceAfterRules', ' ');
1219          $output->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
1220          $this->assert_sample($data, $output);
1221      }
1222  
1223  
1224      /**
1225       * Test transform.
1226       * @param array $data the provider data.
1227       * @dataProvider transforms_provider
1228       */
1229      /* Not supported by MoodleHQ/RTLCSS yet.
1230      public function test_transforms($data) {
1231          $output = new OutputFormat();
1232          $output->set('SpaceBeforeRules', ' ');
1233          $output->set('SpaceAfterRules', ' ');
1234          $output->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
1235          $this->assert_sample($data, $output);
1236      }
1237      */
1238  
1239      /**
1240       * Test values n-syntax.
1241       * @param array $data the provider data.
1242       * @dataProvider values_nsyntax_provider
1243       */
1244      public function test_values_nsyntax($data) {
1245          $output = new OutputFormat();
1246          $output->set('SpaceBeforeRules', ' ');
1247          $output->set('SpaceAfterRules', ' ');
1248          $output->set('RGBHashNotation', false);
1249          $output->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
1250          $this->assert_sample($data, $output);
1251      }
1252  
1253      /**
1254       * Test values.
1255       * @param array $data the provider data.
1256       * @dataProvider values_provider
1257       */
1258      public function test_values($data) {
1259          $output = new OutputFormat();
1260          $output->set('SpaceAfterRuleName', '');
1261          $output->set('SpaceBeforeRules', ' ');
1262          $output->set('SpaceAfterRules', ' ');
1263          $output->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
1264          $this->assert_sample($data, $output);
1265      }
1266  }