<?php
/**
 * Test suite for the HTML_CSS class
 *
 * PHP version 5
 *
 * @category HTML
 * @package  HTML_CSS
 * @author   Laurent Laville <pear@laurent-laville.org>
 * @license  http://www.opensource.org/licenses/bsd-license.php BSD
 * @version  CVS: $Id: HTML_CSS_TestSuite_Standard.php,v 1.14 2009/07/04 07:51:18 farell Exp $
 * @link     http://pear.php.net/package/HTML_CSS
 * @since    File available since Release 1.4.0
 */
if (!defined("PHPUnit_MAIN_METHOD")) {
    define("PHPUnit_MAIN_METHOD", "HTML_CSS_TestSuite_Standard::main");
}

require_once "PHPUnit/Framework/TestCase.php";
require_once "PHPUnit/Framework/TestSuite.php";

require_once 'HTML/CSS.php';
require_once 'PEAR.php';

/**
 * Test suite class to test standard HTML_CSS API.
 *
 * @category HTML
 * @package  HTML_CSS
 * @author   Laurent Laville <pear@laurent-laville.org>
 * @license  http://www.opensource.org/licenses/bsd-license.php BSD
 * @version  Release: 1.5.4
 * @link     http://pear.php.net/package/HTML_CSS
 * @since    File available since Release 1.4.0
 */
class HTML_CSS_TestSuite_Standard extends PHPUnit_Framework_TestCase
{
    /**
     * A CSS object
     * @var  object
     */
    protected $css;

    /**
     * Runs the test methods of this class.
     *
     * @static
     * @return void
     */
    public static function main()
    {
        include_once "PHPUnit/TextUI/TestRunner.php";

        $suite  = new PHPUnit_Framework_TestSuite('HTML_CSS Standard Tests');
        $result = PHPUnit_TextUI_TestRunner::run($suite);
    }

    /**
     * Sets up the fixture.
     * This method is called before a test is executed.
     *
     * @return void
     */
    protected function setUp()
    {
        $attrs = array();
        $prefs = array('push_callback'  => array($this, 'handleError'),
                       'error_callback' => array($this, 'handleErrorOutput'));

        $this->css = new HTML_CSS($attrs, $prefs);
    }

    /**
     * Tears down the fixture.
     * This method is called after a test is executed.
     *
     * @return void
     */
    protected function tearDown()
    {
        unset($this->css);
    }

    /**
     * Don't die if the error is an exception (as default callback)
     *
     * @param int    $code  a numeric error code.
     *                      Valid are HTML_CSS_ERROR_* constants
     * @param string $level error level ('exception', 'error', 'warning', ...)
     *
     * @return int PEAR_ERROR_CALLBACK
     */
    public function handleError($code, $level)
    {
        return PEAR_ERROR_CALLBACK;
    }

    /**
     * Do nothing (no display, no log) when an error is raised
     *
     * @param object $css_error instance of HTML_CSS_Error
     *
     * @return void
     */
    public function handleErrorOutput($css_error)
    {
    }

    /**
     * tests API throws error
     *
     * @param object $error PEAR_Error instance
     * @param int    $code  error code
     * @param string $level error level (exception or error)
     *
     * @return void
     */
    public function catchError($error, $code, $level)
    {
        $this->assertType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $error);
        if ($error instanceof PEAR_Error) {
            $this->assertEquals($error->getCode(), $code);
            $user_info = $error->getUserInfo();
            $this->assertEquals($user_info['level'], $level);
        }
    }

    /**
     * Tests setting options all at once.
     *
     * @return void
     * @group  standard
     */
    public function testSetOptions()
    {
        $tab = '  ';
        $eol = strtolower(substr(PHP_OS, 0, 3)) == 'win' ? "\r\n" : "\n";

        $options = array('xhtml' => true, 'tab' => $tab, 'cache' => true,
            'oneline' => false, 'charset' => 'iso-8859-1',
            'contentDisposition' => false, 'lineEnd' => $eol,
            'groupsfirst' => true, 'allowduplicates' => false);

        foreach ($options as $opt => $val) {
            $this->css->__set($opt, $val);
            $this->assertSame($this->css->__get($opt), $val,
                "option '$opt' was not set");
        }
    }

    /**
     * Tests setting the 'xhtml' option.
     *
     * @return void
     * @group  standard
     */
    public function testSetXhtmlCompliance()
    {
        $arg = true;
        $e   = $this->css->setXhtmlCompliance($arg);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertSame($this->css->__get('xhtml'), $arg, $msg);
    }

    /**
     * Tests setting the 'tab' option.
     *
     * @return void
     * @group  standard
     */
    public function testSetTab()
    {
        $arg = "\t";
        $e   = $this->css->setTab($arg);
        $this->assertSame($this->css->__get('tab'), $arg,
            "'tab' option does not match");
    }

    /**
     * Tests setting the 'cache' option.
     *
     * @return void
     * @group  standard
     */
    public function testSetCache()
    {
        $arg = false;
        $e   = $this->css->setCache($arg);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertSame($this->css->__get('cache'), $arg, $msg);
    }

    /**
     * Tests setting the 'oneline' option.
     *
     * @return void
     * @group  standard
     */
    public function testSetSingleLineOutput()
    {
        $arg = true;
        $e   = $this->css->setSingleLineOutput($arg);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertSame($this->css->__get('oneline'), $arg, $msg);
    }

    /**
     * Tests setting the 'charset' option.
     *
     * @return void
     * @group  standard
     */
    public function testSetCharset()
    {
        $arg = 'UTF-8';
        $e   = $this->css->setCharset($arg);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertSame($this->css->__get('charset'), $arg, $msg);
    }

    /**
     * Tests setting the 'contentDisposition' option.
     *
     * @return void
     * @group  standard
     */
    public function testSetContentDisposition()
    {
        $enable   = true;
        $filename = 'myFile.css';
        $e        = $this->css->setContentDisposition($enable, $filename);
        $msg      = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertSame($this->css->__get('contentDisposition'), $filename,
            $msg);
    }

    /**
     * Tests setting the 'lineEnd' option.
     *
     * @return void
     * @group  standard
     */
    public function testSetLineEnd()
    {
        $arg = "\n";
        $e   = $this->css->setLineEnd($arg);
        $this->assertSame($this->css->__get('lineEnd'), $arg,
            "'lineEnd' option does not match");
    }

    /**
     * Tests setting the 'groupsfirst' option.
     *
     * @return void
     * @group  standard
     */
    public function testSetOutputGroupsFirst()
    {
        $arg = false;
        $e   = $this->css->setOutputGroupsFirst($arg);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertSame($this->css->__get('groupsfirst'), $arg, $msg);
    }

    /**
     * Tests setting the 'allowduplicates' option.
     *
     * @return void
     * @group  standard
     */
    public function testSetAllowDuplicates()
    {
        $arg = true;
        $e   = $this->css->__set('allowduplicates', $arg);
        $this->assertSame($this->css->__get('allowduplicates'), $arg,
            "'groupsfirst' option does not match");
    }

    /**
     * Tests handling selector and property values
     *
     * @return void
     * @group  standard
     */
    public function testStyle()
    {
        $element  = 'h2';
        $property = 'color';
        $value    = '#FFFFFF';
        $e        = $this->css->setStyle($element, $property, $value);
        $msg      = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), 'wrong set arguments');

        $e   = $this->css->getStyle($element, $property);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), 'wrong get arguments');
        $this->assertSame($value, $e, 'property value does not match');

        $e = $this->css->setSameStyle('.myclass', 'h2');

        $gs  = array('h2, .myclass' => array('color' => $value));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'invalid same style group selector result');
    }

    /**
     * Tests building/removing CSS definition group
     *
     * @return void
     * @group  standard
     */
    public function testGroup()
    {
        $g   = 1;
        $e   = $this->css->createGroup('body, html');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertSame($g, $e, 'impossible to create CSS def group');

        $e   = $this->css->unsetGroup($g);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
    }

    /**
     * Tests setting/getting styles for a CSS definition group
     *
     * @return void
     * @group  standard
     */
    public function testGroupStyle()
    {
        $p   = '#ffffff';
        $g   = $this->css->createGroup('body, html');
        $e   = $this->css->setGroupStyle($g, 'color', $p);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->getGroupStyle($g, 'color');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertSame($p, $e,
            "color property of group #$g does not match");

        $gs  = array('body, html' => array('color' => $p));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'invalid group selector result');
    }

    /**
     * Tests setting styles duplicated for a CSS definition group
     *
     * @return void
     * @group  standard
     */
    public function testGroupStyleDuplicates()
    {
        $g = $this->css->createGroup('html, #header');
        $r = $this->css->setGroupStyle($g, 'voice-family', '"\"}\""', true);
        $r = $this->css->setGroupStyle($g, 'voice-family', 'inherit', true);

        $gs  = array('html, #header' => array(1 => array('voice-family' => '"\"}\""'),
                                              2 => array('voice-family' => 'inherit'))
                     );
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'invalid group selector result');
    }

    /**
     * Tests add/remove selector to a CSS definition group
     *
     * @return void
     * @group  standard
     */
    public function testGroupSelector()
    {
        $g = $this->css->createGroup('body, html');
        $this->css->setGroupStyle($g, 'margin', '2px');
        $this->css->setGroupStyle($g, 'padding', '0');
        $this->css->setGroupStyle($g, 'border', '0');

        $old_gs  = array('body, html' =>
                       array('margin' => '2px',
                             'padding' => '0',
                             'border' => '0'));
        $cur_def = $this->css->toArray();
        $this->assertSame($old_gs, $cur_def,
            'invalid source group selector result');

        $e   = $this->css->removeGroupSelector($g, 'body');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->addGroupSelector($g, '.large');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $new_gs  = array('html, .large' =>
                       array('margin' => '2px',
                             'padding' => '0',
                             'border' => '0'));
        $cur_def = $this->css->toArray();
        $this->assertSame($new_gs, $cur_def,
            'invalid target group selector result');
    }

    /**
     * Tests parsing a simple string that contains CSS information.
     *
     * @return void
     * @group  standard
     */
    public function testParseString()
    {
        $strcss = '
html, body {
 margin: 2px;
 padding: 0px;
 border: 0px;
}

p, body {
 margin: 4px;
}
';

        $e   = $this->css->parseString($strcss);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $gs  = array('html, body' =>
                  array('margin' => '2px',
                        'padding' => '0px',
                        'border' => '0px'),
                    'p, body' =>
                  array('margin' => '4px'));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'string parses does not match');
    }

    /**
     * Tests parsing a simple string that contains invalid CSS information.
     *
     * @return void
     * @group  standard
     */
    public function testParseStringWithInvalidContent()
    {
        $strcss = '
img.thumbs {
 width: {IMG_THUMBS_WIDTH} px;
 height: {IMG_THUMBS_HEIGHT} px;
}
';

        $e = $this->css->parseString($strcss);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertTrue(PEAR::isError($e), $msg);
    }

    /**
     * Tests parsing a simple string that contains a simple AT Rule
     *
     * @return void
     * @group  standard
     */
    public function testParseStringWithSimpleAtRule()
    {
        $this->css->setXhtmlCompliance(false);
        $simpleAtRule = '
html { height: 100%; }
@charset "UTF-8";
';
        $this->css->parseString($simpleAtRule);

        $r        = $this->css->toArray();
        $expected = array('@charset' => array('"UTF-8"' => ''),
                          'html' => array('height' => '100%'));
        $this->assertSame($expected, $r);
    }

    /**
     * Tests parsing a simple string that contains a complex AT Rule
     *
     * @return void
     * @group  standard
     */
    public function testParseStringWithComplexAtRule()
    {
        $complexAtRule = '@media screen { color: green; background-color: yellow; }';
        $this->css->parseString($complexAtRule);

        $r        = $this->css->toArray();
        $expected = array('@media' =>
                        array('screen' =>
                            array('' =>
                                array('color' => 'green',
                                      'background-color' => 'yellow'),
                        )));
        $this->assertSame($expected, $r);
    }

    /**
     * Tests create duplicates copies of At-Rules with different strings
     *
     * @return void
     * @group  standard
     */
    public function testCreateAtRuleWithManyDefinitions()
    {
        $this->css->createAtRule('@import', '"foo.css"', true);
        $this->css->createAtRule('@import', '"bar.css"', true);

        $exp = array(
            '@import' => array(
                '1' => array(
                    '"foo.css"' => '',
                ),
                '2' => array(
                    '"bar.css"' => '',
                ),
            ),
        );
        $this->assertSame($exp, $this->css->toArray());
    }

    /**
     * Tests parsing a file that contains CSS information.
     *
     * @return void
     * @group  standard
     */
    public function testParseFile()
    {
        $this->css->setXhtmlCompliance(false);
        // parsing a file contents
        $fn  = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'stylesheet.css';
        $e   = $this->css->parseFile($fn);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $gs  = array('body' =>
                   array('font' => 'normal 68% verdana,arial,helvetica',
                     'color' => '#000000'),
                   'table tr td, table tr th' =>
                   array('font-size' => '68%'),
                   'table.details tr th' =>
                   array('font-weight' => 'bold',
                     'text-align' => 'left',
                     'background' => '#a6caf0'),
                   'table.details tr' =>
                   array('background' => '#eeeee0'),
                   'p' =>
                   array('line-height' => '1.5em',
                     'margin-top' => '0.5em',
                     'margin-bottom' => '1.0em'),
                   'h1' =>
                   array('margin' => '0px 0px 5px',
                     'font' => '165% verdana,arial,helvetica'),
                   'h2' =>
                   array('margin-top' => '1em',
                     'margin-bottom' => '0.5em',
                     'font' => 'bold 125% verdana,arial,helvetica'),
                   'h3' =>
                   array('margin-bottom' => '0.5em',
                     'font' => 'bold 115% verdana,arial,helvetica'),
                   'h4' =>
                   array('margin-bottom' => '0.5em',
                     'font' => 'bold 100% verdana,arial,helvetica'),
                   'h5' =>
                   array('margin-bottom' => '0.5em',
                     'font' => 'bold 100% verdana,arial,helvetica'),
                   'h6' =>
                   array('margin-bottom' => '0.5em',
                     'font' => 'bold 100% verdana,arial,helvetica'),
                   '.Error' =>
                   array('font-weight' => 'bold',
                     'color' => 'red'),
                   '.Failure, .Unexpected' =>
                   array('background' => '#ff0000',
                     'font-weight' => 'bold',
                     'color' => 'black'),
                   '.Unknown' =>
                   array('background' => '#ffff00',
                     'font-weight' => 'bold',
                     'color' => 'black'),
                   '.Pass, .Expected' =>
                   array('background' => '#00ff00',
                     'font-weight' => 'bold',
                     'color' => 'black'),
                   '.Properties' =>
                   array('text-align' => 'right'),
                   'CODE.expected' =>
                   array('color' => 'green',
                     'background' => 'none',
                     'font-weight' => 'normal'),
                   'CODE.actual' =>
                   array('color' => 'red',
                     'background' => 'none',
                     'font-weight' => 'normal'),
                   '.typeinfo' =>
                   array('color' => 'gray'));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'css file parses does not match');
    }

    /**
     * Tests parsing multiple data sources (a simple string and a file),
     * that contains CSS information, at once.
     *
     * @return void
     * @group  standard
     */
    public function testParseData()
    {
        $this->css->setXhtmlCompliance(false);
        $strcss   = '
body, p { background-color: white; font: 1.2em Arial; }
p, div#black { color: black; }
div{ color: green; }
p { margin-left: 3em; }
';
        $fn       = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'stylesheet.css';
        $css_data = array($fn, $strcss);

        $e   = $this->css->parseData($css_data);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $gs  = array('body' =>
                   array('font' => 'normal 68% verdana,arial,helvetica',
                     'color' => '#000000'),
                   'table tr td, table tr th' =>
                   array('font-size' => '68%'),
                   'table.details tr th' =>
                   array('font-weight' => 'bold',
                     'text-align' => 'left',
                     'background' => '#a6caf0'),
                   'table.details tr' =>
                   array('background' => '#eeeee0'),
                   'p' =>
                   array('line-height' => '1.5em',
                     'margin-top' => '0.5em',
                     'margin-bottom' => '1.0em',
                     'margin-left' => '3em'),
                   'h1' =>
                   array('margin' => '0px 0px 5px',
                     'font' => '165% verdana,arial,helvetica'),
                   'h2' =>
                   array('margin-top' => '1em',
                     'margin-bottom' => '0.5em',
                     'font' => 'bold 125% verdana,arial,helvetica'),
                   'h3' =>
                   array('margin-bottom' => '0.5em',
                     'font' => 'bold 115% verdana,arial,helvetica'),
                   'h4' =>
                   array('margin-bottom' => '0.5em',
                     'font' => 'bold 100% verdana,arial,helvetica'),
                   'h5' =>
                   array('margin-bottom' => '0.5em',
                     'font' => 'bold 100% verdana,arial,helvetica'),
                   'h6' =>
                   array('margin-bottom' => '0.5em',
                     'font' => 'bold 100% verdana,arial,helvetica'),
                   '.Error' =>
                   array('font-weight' => 'bold',
                     'color' => 'red'),
                   '.Failure, .Unexpected' =>
                   array('background' => '#ff0000',
                     'font-weight' => 'bold',
                     'color' => 'black'),
                   '.Unknown' =>
                   array('background' => '#ffff00',
                     'font-weight' => 'bold',
                     'color' => 'black'),
                   '.Pass, .Expected' =>
                   array('background' => '#00ff00',
                     'font-weight' => 'bold',
                     'color' => 'black'),
                   '.Properties' =>
                   array('text-align' => 'right'),
                   'CODE.expected' =>
                   array('color' => 'green',
                     'background' => 'none',
                     'font-weight' => 'normal'),
                   'CODE.actual' =>
                   array('color' => 'red',
                     'background' => 'none',
                     'font-weight' => 'normal'),
                   '.typeinfo' =>
                   array('color' => 'gray'),
                   'body, p' =>
                   array('background-color' => 'white',
                     'font' => '1.2em Arial'),
                   'p, div#black' =>
                   array('color' => 'black'),
                   'div' =>
                   array('color' => 'green'));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'css data sources parses does not match');
    }

    /**
     * Tests data source checking validity with W3C CSS validator service
     *
     * @return void
     * @group  standard
     */
    public function testValidate()
    {
        $strcss   = '
body, p { background-color: white; font: 1.2em Arial; }
p, div#black { color: black; }
div{ color: green; }
p { margin-left: 3em; }
';
        $fn       = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'stylesheet.css';
        $css_data = array($fn, $strcss);
        $messages = array();

        $stub = $this->getMock('HTML_CSS', array('validate'));
        $stub->expects($this->any())
             ->method('validate')
             ->will($this->returnValue(true));

        $e   = $stub->validate($css_data, $messages);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertTrue($e, 'CSS data source is invalid');
    }

    /**
     * Tests parsing data source with allow duplicates option activated.
     *
     * Internet Explorer <= 6 does not handle box model in same way as others
     * browsers that are better W3C compliant. For this reason, we need to fix
     * boxes size with a hack such as this one you can find in example that follow.
     * You can notice the duplicate 'voice-family' and 'height' properties.
     *
     * @return void
     * @group  standard
     */
    public function testAllowDuplicates()
    {
        $strcss = '
#header {
  background-color: ivory;
  font-family: "Times New Roman", Times, serif;
  font-size: 5mm;
  text-align: center;
  /* IE 5.5 */
  height:81px;
  border-top:1px solid #000;
  border-right:1px solid #000;
  border-left:1px solid #000;
  voice-family: "\"}\"";
  voice-family: inherit;
  /* IE 6 */
  height: 99px;
}
';
        // set local 'allowduplicates' option
        $e   = $this->css->parseString($strcss, true);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $gs  = array('#header' =>
                   array(1 => array('background-color' => 'ivory'),
                     2 => array('font-family' => '"Times New Roman", Times, serif'),
                     3 => array('font-size' => '5mm'),
                     4 => array('text-align' => 'center'),
                     5 => array('height' => '81px'),
                     6 => array('border-top' => '1px solid #000'),
                     7 => array('border-right' => '1px solid #000'),
                     8 => array('border-left' => '1px solid #000'),
                     9 => array('voice-family' => '"\\"}\\""'),
                     10 => array('voice-family' => 'inherit'),
                     11 => array('height' => '99px')));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'css source parses does not match');
    }

    /**
     * Tests render to inline html code, array, string or file.
     *
     * @return void
     * @group  standard
     */
    public function testOutput()
    {
        /**
         * Depending of platform (windows, unix, ...) be sure to compare
         * same end of line marker.
         */
        $this->css->setlineEnd("\n");

        $strcss  = '{eol}';
        $strcss .= 'ul, body {{eol}';
        $strcss .= ' padding: 1em 2em;{eol}';
        $strcss .= ' color: red;{eol}';
        $strcss .= '}{eol}';
        $strcss  = str_replace('{eol}', $this->css->lineEnd, $strcss);
        $this->css->parseString($strcss);

        // to inline
        $expected = 'padding:1em 2em;color:red;';
        $e        = $this->css->toInline('body');
        $msg      = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);
        $this->assertSame($e, $expected, 'inline output does not match');

        // to array
        $gs  = array('ul, body' =>
                   array('padding' => '1em 2em',
                     'color' => 'red'));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'array output does not match');

        // to string, multi lines
        $this->css->oneline
                   = false;  // PHP5 signature, see __set() for PHP4
        $expNline  = '{eol}';
        $expNline .= 'ul, body {{eol}';
        $expNline .= '{tab}padding: 1em 2em;{eol}';
        $expNline .= '{tab}color: red;{eol}';
        $expNline .= '}{eol}';
        $expNline  = str_replace(array('{tab}','{eol}'),
                         array($this->css->tab, $this->css->lineEnd), $expNline);
        $str       = $this->css->toString();
        $this->assertSame($str, $expNline, 'normal string output does not match');

        // to string, one line
        $this->css->oneline
                  = true;   // PHP5 signature, see __set() for PHP4
        $exp1line = 'ul, body { padding: 1em 2em; color: red; }';
        $str      = $this->css->toString();
        $this->assertSame($str, $exp1line, 'online string output does not match');

        $tmpFile = tempnam(dirname(__FILE__), 'CSS');
        // to file, multi lines
        $this->css->oneline
             = false;   // PHP5 signature, see __set() for PHP4
        $e   = $this->css->toFile($tmpFile);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $str = file_get_contents($tmpFile);
        $this->assertSame($str, $expNline, 'normal file output does not match');

        // to file, one line
        $this->css->oneline
             = true;    // PHP5 signature, see __set() for PHP4
        $e   = $this->css->toFile($tmpFile);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $str = file_get_contents($tmpFile);
        $this->assertSame($str, $exp1line, 'oneline file output does not match');

        unlink($tmpFile);
    }

    /**
     * Tests searching for selectors and properties
     *
     * @return void
     * @group  standard
     */
    public function testGrepStyle()
    {
        $this->css->setXhtmlCompliance(false);
        $strcss = '
#PB1 .cellI, #PB1 .cellA {
  width: 10px;
  height: 20px;
  font-family: Courier, Verdana;
  font-size: 8px;
  float: left;
  background-color: transparent;
}

#PB1 .progressBorder {
  width: 122px;
  height: 24px;
  border-width: 1px;
  border-style: solid;
  border-color: navy;
  background-color: #FFFFFF;
}

#PB1 .progressPercentLabel {
  width: 60px;
  text-align: center;
  background-color: transparent;
  font-size: 14px;
  font-family: Verdana, Tahoma, Arial;
  font-weight: normal;
  color: #000000;
}

#PB1 .cellI {
  background-color: #EEEECC;
}

#PB1 .cellA {
  background-color: #3874B4;
}

body {
    background-color: #E0E0E0;
    color: navy;
    font-family: Verdana, Arial;
}
';

        $e   = $this->css->parseString($strcss);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $gs = array('#PB1 .cellI' =>
                  array('width' => '10px',
                     'height' => '20px',
                     'font-family' => 'Courier, Verdana',
                     'font-size' => '8px',
                     'float' => 'left',
                     'background-color' => '#EEEECC'),
                  '#PB1 .cellA' =>
                  array('width' => '10px',
                     'height' => '20px',
                     'font-family' => 'Courier, Verdana',
                     'font-size' => '8px',
                     'float' => 'left',
                     'background-color' => '#3874B4'),
                  '#PB1 .progressBorder' =>
                  array('width' => '122px',
                     'height' => '24px',
                     'border-width' => '1px',
                     'border-style' => 'solid',
                     'border-color' => 'navy',
                     'background-color' => '#FFFFFF'),
                  '#PB1 .progressPercentLabel' =>
                  array('width' => '60px',
                     'text-align' => 'center',
                     'background-color' => 'transparent',
                     'font-size' => '14px',
                     'font-family' => 'Verdana, Tahoma, Arial',
                     'font-weight' => 'normal',
                     'color' => '#000000'));
        // find all selectors beginning with #PB1
        $style1 = $this->css->grepStyle('/^#PB1/');
        $this->assertSame($gs, $style1, 'search for pattern 1 does not match');

        $gs = array('#PB1 .progressPercentLabel' =>
                  array('width' => '60px',
                      'text-align' => 'center',
                      'background-color' => 'transparent',
                      'font-size' => '14px',
                      'font-family' => 'Verdana, Tahoma, Arial',
                      'font-weight' => 'normal',
                      'color' => '#000000'),
                  'body' =>
                  array('background-color' => '#E0E0E0',
                      'color' => 'navy',
                      'font-family' => 'Verdana, Arial'));
        // find all selectors that set the color property
        $style2 = $this->css->grepStyle('/./', '/^color$/');
        $this->assertSame($gs, $style2, 'search for pattern 2 does not match');
    }

    /**
     * Tests building/removing CSS simple At-Rule
     * "@charset, @import and @namespace"
     *
     * @return void
     * @group  standard
     */
    public function testSimpleAtRule()
    {
        $this->css->setStyle('html', 'height', '100%');

        $e   = $this->css->createAtRule('@charset', '"UTF-8"');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->createAtRule('@import', 'url("foo.css") screen, print');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->createAtRule('@namespace',
                   'foo url("http://www.example.com/")');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $gs  = array('@charset' =>
                   array('"UTF-8"' => ''),
                    '@import' =>
                   array('url("foo.css") screen, print' => ''),
                    '@namespace' =>
                   array('foo url("http://www.example.com/")' => ''),
                    'html' =>
                   array('height' => '100%'));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'array output does not match');

        $e   = $this->css->unsetAtRule('@CharSet');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        unset($gs['@charset']);
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'array clean does not match');
    }

    /**
     * Tests conditional/informative At-Rules
     * "@media, @page, @font-face"
     *
     * @return void
     * @group  standard
     */
    public function testAtRuleStyle()
    {
        $this->css->setStyle('html', 'height', '100%');

        $e   = $this->css->setAtRuleStyle('@media',
                   'screen', '', 'color', 'green');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->setAtRuleStyle('@media',
                   'screen', '', 'background-color', 'yellow');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->setAtRuleStyle('@media',
                   'print', 'blockquote', 'font-size', '16pt');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->setAtRuleStyle('@page',
                   ':first', '', 'size', '3in 8in');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->setAtRuleStyle('@font-face',
                   '', '', 'font-family', 'dreamy');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->setAtRuleStyle('@font-face',
                   '', '', 'font-weight', 'bold');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $e   = $this->css->setAtRuleStyle('@font-face',
                   '', '', 'src', 'url(http://www.example.com/font.eot)');
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $gs  = array('@media' => array(
                   'screen' => array(
                     '' => array('color' => 'green',
                                 'background-color' => 'yellow'),
                     ),
                   'print' => array(
                     'blockquote' => array('font-size' => '16pt'),
                     )
                   ),
                   '@page' => array(
                   ':first' => array(
                     '' => array('size' => '3in 8in'),
                     )
                   ),
                   '@font-face' => array(
                   '' => array(
                     '' => array('font-family' => 'dreamy',
                                 'font-weight' => 'bold',
                                 'src' => 'url(http://www.example.com/font.eot)')
                     )
                   ),
                   'html' => array('height' => '100%'));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'array does not match');
    }

    /**
     * Tests parsing CSS string that contains At-Rules
     *
     * @return void
     * @group  standard
     */
    public function testParseAtRuleString()
    {
        $strcss = <<<EOD
@media screen { color: green; background-color: yellow; }
@media    print {
    blockquote { font-size: 16pt; font-weight: bold; }
}
html { height: 100%; }
@page thin:first  { size: 3in 8in }
@font-face {
    font-family: dreamy;
    font-weight: bold;
    src: url(http://www.example.com/font.eot);
}
EOD;

        $e   = $this->css->parseString($strcss);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $gs  = array('@media' => array(
                   'screen' => array(
                     '' => array('color' => 'green',
                                 'background-color' => 'yellow'),
                     ),
                   'print' => array(
                     'blockquote' => array('font-size' => '16pt',
                                           'font-weight' => 'bold'),
                     )
                   ),
                   '@page' => array(
                   'thin:first' => array(
                     '' => array('size' => '3in 8in'),
                     )
                   ),
                   '@font-face' => array(
                   '' => array(
                     '' => array('font-family' => 'dreamy',
                                 'font-weight' => 'bold',
                                 'src' => 'url(http://www.example.com/font.eot)')
                     )
                   ),
                   'html' => array('height' => '100%'));
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'array does not match');
    }

    /**
     * Tests parsing CSS string that contains At-Rules with rule sets
     *
     * @return void
     * @group  standard
     */
    public function testParseAtRuleStringWithManyRuleSets()
    {
        $strcss = <<<EOD
@media print {
	body {
		font-size: 10pt;
		font-family: times new roman, times, serif;
	}

	#navigation {
		display: none;
	}
}

p {
	font-weight: bold;
}
EOD;

        $e   = $this->css->parseString($strcss);
        $msg = PEAR::isError($e) ? $e->getMessage() : null;
        $this->assertFalse(PEAR::isError($e), $msg);

        $gs  = array('@media' => array(
                   'print' => array(
                     'body' => array('font-size' => '10pt',
                                     'font-family' => 'times new roman, times, serif'),
                     '#navigation' => array('display' => 'none'),
                     ),
                   ),
                   'p' => array('font-weight' => 'bold'),
        );
        $def = $this->css->toArray();
        $this->assertSame($gs, $def, 'array does not match');
    }

    /**
     * Tests list of supported At-Rules by API 1.5.0
     *
     * @return void
     * @group  standard
     */
    public function testGetAtRulesList()
    {
        $expected = array('@charset', '@font-face',
                          '@import', '@media', '@page', '@namespace');
        $atRules  = $this->css->getAtRulesList();
        sort($atRules);
        sort($expected);
        $this->assertSame($atRules, $expected, 'unexpected At-Rules list');
    }

    /**
     * Tests API version number
     *
     * @return void
     * @group  standard
     */
    public function testApiVersion()
    {
        $expected = '1.5.0';
        if ($expected == '@'.'api_version@') {
            $this->markTestSkipped('Could not be run from CVS repository');
        }
        $actual   = $this->css->apiVersion();
        $this->assertEquals($expected, $actual);
    }

    /**
     * Tests read one or all options
     *
     * @return void
     * @group  standard
     */
    public function testReadOptions()
    {
        $tab = '  ';
        $eol = strtolower(substr(PHP_OS, 0, 3)) == 'win' ? "\r\n" : "\n";

        $expected = array('xhtml' => true, 'tab' => $tab, 'cache' => true,
            'oneline' => false, 'charset' => 'iso-8859-1',
            'contentDisposition' => false, 'lineEnd' => $eol,
            'groupsfirst' => true, 'allowduplicates' => false);
        $actual   = $this->css->getOptions();
        // read all default options
        $this->assertEquals($expected, $actual);

        // read an invalid/unknown option
        $actual   = $this->css->__get('unknown');
        $this->assertEquals(null, $actual);
    }

    /**
     * Tests to parse a string containing selector(s)
     *
     * @return void
     * @group  standard
     */
    public function testParsingSelectors()
    {
        $r = $this->css->parseSelectors('#pb1 blockquote .large', 2);
        $expected = array(array('inheritance' => array(
                                array('element' => '',
                                      'id' => '#pb1', 'class' => '', 'pseudo' => ''),
                                array('element' => 'blockquote',
                                      'id' => '', 'class' => '', 'pseudo' => ''),
                                array('element' => '',
                                      'id' => '', 'class' => '.large', 'pseudo' => '')))
                          );
        $this->assertEquals($expected, $r);
    }

    /**
     * Tests to get value of a valid At rule
     *
     * @return void
     * @group  standard
     */
    public function testGettingValidAtRule()
    {
        $this->css->setAtRuleStyle('@media', 'screen', '', 'background-color', 'yellow');

        $r = $this->css->getAtRuleStyle('@media', 'screen', '', 'background-color');
        $expected = 'yellow';
        $this->assertEquals($expected, $r);
    }

    /**
     * Tests to retrieve undefined At rule
     *
     * @return void
     * @group  standard
     */
    public function testGettingUndefinedAtRule()
    {
        $r = $this->css->getAtRuleStyle('@page', ':first', '', 'size');
        $expected = null;
        $this->assertEquals($expected, $r);
    }

    /**
     * Tests reading the 'cache' option.
     *
     * @return void
     * @group  standard
     */
    public function testReadCacheOption()
    {
        $o = $this->css->getOptions();
        $r = $this->css->getCache();
        $this->assertSame($o['cache'], $r);
    }

    /**
     * Tests reading the 'contentDisposition' option.
     *
     * @return void
     * @group  standard
     */
    public function testReadContentDispositionOption()
    {
        $o = $this->css->getOptions();
        $r = $this->css->getContentDisposition();
        $this->assertSame($o['contentDisposition'], $r);
    }

    /**
     * Tests reading the 'charset' option.
     *
     * @return void
     * @group  standard
     */
    public function testReadCharsetOption()
    {
        $o = $this->css->getOptions();
        $r = $this->css->getCharset();
        $this->assertSame($o['charset'], $r);
    }

    /**
     * Tests to catch exception on invalid parameters, when calling functions
     *
     * @return void
     * @group  standard
     */
    public function testInvalidParametersApiCall()
    {
        // setSingleLineOutput
        $r = $this->css->setSingleLineOutput('1');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // setOutputGroupsFirst
        $r = $this->css->setOutputGroupsFirst('1');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // parseSelectors
        $r = $this->css->parseSelectors(true);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->parseSelectors('color: green; background-color: yellow;', '4');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->parseSelectors('color: green; background-color: yellow;', 4);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        // setXhtmlCompliance
        $r = $this->css->setXhtmlCompliance('1');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // createAtRule
        $r = $this->css->createAtRule(true);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->createAtRule('myatrule');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        $r = $this->css->createAtRule('@namespace', true);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->createAtRule('@charset');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        // unsetAtRule
        $r = $this->css->unsetAtRule(true);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->unsetAtRule('@myspace');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        $r = $this->css->unsetAtRule('@charset');
        $this->catchError($r, HTML_CSS_ERROR_NO_ATRULE, 'error');

        // setAtRuleStyle
        $r = $this->css->setAtRuleStyle(1, 'print', 'blockquote', 'font-size', '16pt');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setAtRuleStyle('@namespace', '', '', '', '');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        $r = $this->css->setAtRuleStyle('@media', '', '', '', '');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        $r = $this->css->setAtRuleStyle('@font-face', '', '', '', '');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        $r = $this->css->setAtRuleStyle('@media', 'print', true, 'font-size', '16pt');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setAtRuleStyle('@media', 'print', 'blockquote', 16, '16pt');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setAtRuleStyle('@media', 'print', 'blockquote', '', '16pt');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        $r = $this->css->setAtRuleStyle('@media', 'print', 'blockquote', 'font-size', 16);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setAtRuleStyle('@media', 'print', 'blockquote', 'font-size', '');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        // getAtRuleStyle
        $r = $this->css->getAtRuleStyle(1, '', '', '', '');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->getAtRuleStyle('@myspace', '', '', '', '');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        $r = $this->css->getAtRuleStyle('@media', true, 'blockquote', 'font-size');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->getAtRuleStyle('@media', 'print', true, 'font-size');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->getAtRuleStyle('@media', 'print', 'blockquote', true);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // createGroup
        $r = $this->css->createGroup(1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->createGroup('body, html');
        $r = $this->css->createGroup('#main p, #main ul', 1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_GROUP, 'error');

        // unsetGroup
        $r = $this->css->unsetGroup(true);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->unsetGroup(0);
        $this->catchError($r, HTML_CSS_ERROR_NO_GROUP, 'error');

        // setGroupStyle
        $r = $this->css->setGroupStyle(true, null, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setGroupStyle(1, null, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setGroupStyle(1, '', null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        $r = $this->css->setGroupStyle(1, 'font-face', null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setGroupStyle(1, 'color', '', '1');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setGroupStyle(0, 'background-color', 'yellow');
        $this->catchError($r, HTML_CSS_ERROR_NO_GROUP, 'error');

        // getGroupStyle
        $r = $this->css->getGroupStyle(true, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->getGroupStyle(1, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->getGroupStyle(0, '');
        $this->catchError($r, HTML_CSS_ERROR_NO_GROUP, 'error');

        // addGroupSelector
        $r = $this->css->addGroupSelector(true, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->addGroupSelector(0, null);
        $this->catchError($r, HTML_CSS_ERROR_NO_GROUP, 'error');

        $r = $this->css->addGroupSelector(1, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // removeGroupSelector
        $r = $this->css->removeGroupSelector(true, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->removeGroupSelector(0, null);
        $this->catchError($r, HTML_CSS_ERROR_NO_GROUP, 'error');

        $r = $this->css->removeGroupSelector(1, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // setStyle
        $r = $this->css->setStyle(true, null, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setStyle('body', null, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setStyle('html, body', 'background-color', '#0c0c0c');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'error');

        $r = $this->css->setStyle('body', 'background-color', null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setStyle('body', 'background-color', '#0c0c0c', 1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // getStyle
        $r = $this->css->getStyle(true, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->getStyle('body', null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->getStyle('input', 'background-color');
        $this->catchError($r, HTML_CSS_ERROR_NO_ELEMENT, 'error');

        $this->css->setStyle('body', 'color', '#fff');
        $r = $this->css->getStyle('body', 'background-color');
        $this->catchError($r, HTML_CSS_ERROR_NO_ELEMENT_PROPERTY, 'error');

        // grepStyle
        $r = $this->css->grepStyle(1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->grepStyle('/^#PB1/', 1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // setSameStyle
        $r = $this->css->setSameStyle(null, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setSameStyle('body', 1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setSameStyle('body', 'p');
        $this->catchError($r, HTML_CSS_ERROR_NO_ELEMENT, 'error');

        // setCache
        $r = $this->css->setCache(1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // setContentDisposition
        $r = $this->css->setContentDisposition(1, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->setContentDisposition(true, null);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // setCharset
        $r = $this->css->setCharset(8859);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // parseString
        $r = $this->css->parseString(intval('iso-8859-1'));
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->parseString("intval('iso-8859-1')", 1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // parseFile
        $r = $this->css->parseFile(1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->parseFile('none.css');
        $this->catchError($r, HTML_CSS_ERROR_NO_FILE, 'error');

        $r = $this->css->parseFile('stylesheet.css', 1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // parseData
        $r = $this->css->parseData('img.thb { width: 80px; }');
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->parseData(array('img.thb { width: 80px; }'), 1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $r = $this->css->parseData(array('img.thb { width: 80px; }', true));
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // validate
        $stub = $this->getMock('HTML_CSS', array('validate'));
        $stub->expects($this->any())
             ->method('validate')
             ->will($this->returnCallback(array(&$this, 'cbMockValidator')));

        $styles   = ' ';
        $messages = array();
        $r = $stub->validate($styles, $messages);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $styles   = array('p, div#black { color: black; }');
        $messages = '';
        $r = $stub->validate($styles, $messages);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $styles   = array('p, div#black { color: black; }');
        $r = $stub->validate($styles, $messages);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        $messages = array();
        $styles   = array('php < 5');
        $r = $stub->validate($styles, $messages);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_DEPS, 'exception');

        $styles = array('Services_W3C_CSSValidator does not exists');
        $r      = $stub->validate($styles, $messages);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_DEPS, 'exception');

        $styles = array('p, div#black { color: black; }', true);
        $r      = $stub->validate($styles, $messages);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // toInline
        $r = $this->css->toInline(1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');

        // toFile
        $r = $this->css->toFile(1);
        $this->catchError($r, HTML_CSS_ERROR_INVALID_INPUT, 'exception');
    }

    /**
     * Stub callback to replace call to the CSS W3C Validator Web Service
     *
     * Simulate PHP version < 5,
     * and class Services_W3C_CSSValidator not available
     *
     * @return mixed
     */
    public function cbMockValidator()
    {
        $args     = func_get_args();
        $styles   = $args[0];
        $messages = $args[1];

        if (!is_array($styles)) {
            return $this->css->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
                array('var' => '$styles',
                      'was' => gettype($styles),
                      'expected' => 'array',
                      'paramnum' => 1));

        } elseif (!is_array($messages)) {
            return $this->css->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
                array('var' => '$messages',
                      'was' => gettype($messages),
                      'expected' => 'array',
                      'paramnum' => 2));
        }

        $css1 = 'php < 5';
        $css2 = 'Services_W3C_CSSValidator does not exists';

        foreach ($styles as $i => $source) {
            if (!is_string($source)) {
                return $this->css->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
                    array('var' => '$styles[' . $i . ']',
                          'was' => gettype($styles[$i]),
                          'expected' => 'string',
                          'paramnum' => 1));
            }
            if ($source == $css1) {
                $php = '4.3.10';
                return $this->css->raiseError(HTML_CSS_ERROR_INVALID_DEPS, 'exception',
                    array('funcname' => __FUNCTION__,
                          'dependency' => 'PHP 5',
                          'currentdep' => "PHP $php"));

            } elseif ($source == $css2) {
                return $this->css->raiseError(HTML_CSS_ERROR_INVALID_DEPS, 'exception',
                    array('funcname' => __FUNCTION__,
                          'dependency' => 'PEAR::Services_W3C_CSSValidator',
                          'currentdep' => 'nothing'));
            }
        }
    }
}

// Call HTML_CSS_TestSuite_Standard::main() if file is executed directly.
if (PHPUnit_MAIN_METHOD == "HTML_CSS_TestSuite_Standard::main") {
    HTML_CSS_TestSuite_Standard::main();
}
?>