Index: trunk/test/data/renderer/README
===================================================================
--- trunk/test/data/renderer/README	(revision 11433)
+++ trunk/test/data/renderer/README	(revision 11433)
@@ -0,0 +1,1 @@
+Test files for MapCSSRendererTest
Index: trunk/test/data/renderer/node-shapes/data.osm
===================================================================
--- trunk/test/data/renderer/node-shapes/data.osm	(revision 11433)
+++ trunk/test/data/renderer/node-shapes/data.osm	(revision 11433)
@@ -0,0 +1,82 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version='0.6' upload='true' generator='JOSM'>
+  <node id='-30470' action='modify' visible='true' lat='0.1' lon='0.1'>
+    <tag k='test' v='heptagon' />
+  </node>
+  <node id='-30472' action='modify' visible='true' lat='0.1' lon='0.3'>
+    <tag k='test' v='octagon' />
+  </node>
+  <node id='-30474' action='modify' visible='true' lat='0.1' lon='0.5'>
+    <tag k='test' v='nonagon' />
+  </node>
+  <node id='-30476' action='modify' visible='true' lat='0.1' lon='0.7'>
+    <tag k='test' v='decagon' />
+  </node>
+  <node id='-30478' action='modify' visible='true' lat='0.1' lon='0.9'>
+    <tag k='test' v='fill-color' />
+  </node>
+  <node id='-30480' action='modify' visible='true' lat='0.3' lon='0.1'>
+    <tag k='test' v='square' />
+  </node>
+  <node id='-30482' action='modify' visible='true' lat='0.3' lon='0.3'>
+    <tag k='test' v='circle' />
+  </node>
+  <node id='-30484' action='modify' visible='true' lat='0.3' lon='0.5'>
+    <tag k='test' v='triangle' />
+  </node>
+  <node id='-30486' action='modify' visible='true' lat='0.3' lon='0.7'>
+    <tag k='test' v='pentagon' />
+  </node>
+  <node id='-30488' action='modify' visible='true' lat='0.3' lon='0.9'>
+    <tag k='test' v='hexagon' />
+  </node>
+  <node id='-30490' action='modify' visible='true' lat='0.5' lon='0.1'>
+    <tag k='test' v='fill-color-none' />
+  </node>
+  <node id='-30492' action='modify' visible='true' lat='0.5' lon='0.3'>
+    <tag k='test' v='fill-opacity' />
+  </node>
+  <node id='-30494' action='modify' visible='true' lat='0.5' lon='0.5'>
+    <tag k='test' v='stroke-color' />
+  </node>
+  <node id='-30496' action='modify' visible='true' lat='0.5' lon='0.7'>
+    <tag k='test' v='stroke-color-none' />
+  </node>
+  <node id='-30498' action='modify' visible='true' lat='0.5' lon='0.9'>
+    <tag k='test' v='stroke-opacity' />
+  </node>
+  <node id='-30500' action='modify' visible='true' lat='0.7' lon='0.1'>
+    <tag k='test' v='small' />
+  </node>
+  <node id='-30502' action='modify' visible='true' lat='0.7' lon='0.3'>
+    <tag k='test' v='big' />
+  </node>
+  <node id='-30504' action='modify' visible='true' lat='0.7' lon='0.5'>
+    <tag k='test' v='thin' />
+  </node>
+  <node id='-30506' action='modify' visible='true' lat='0.7' lon='0.7'>
+    <tag k='test' v='bold' />
+  </node>
+  <node id='-30508' action='modify' visible='true' lat='0.7' lon='0.9'>
+    <tag k='defaults' v='fill' />
+  </node>
+  <node id='-30510' action='modify' visible='true' lat='0.9' lon='0.1'>
+    <tag k='test' v='small' />
+    <tag k='highlight' v='yes' />
+  </node>
+  <node id='-30512' action='modify' visible='true' lat='0.9' lon='0.3'>
+    <tag k='test' v='big' />
+    <tag k='highlight' v='yes' />
+  </node>
+  <node id='-30514' action='modify' visible='true' lat='0.9' lon='0.5'>
+    <tag k='test' v='thin' />
+    <tag k='highlight' v='yes' />
+  </node>
+  <node id='-30516' action='modify' visible='true' lat='0.9' lon='0.7'>
+    <tag k='test' v='bold' />
+    <tag k='highlight' v='yes' />
+  </node>
+  <node id='-30518' action='modify' visible='true' lat='0.9' lon='0.9'>
+    <tag k='defaults' v='stroke' />
+  </node>
+</osm>
Index: trunk/test/data/renderer/node-shapes/style.mapcss
===================================================================
--- trunk/test/data/renderer/node-shapes/style.mapcss	(revision 11433)
+++ trunk/test/data/renderer/node-shapes/style.mapcss	(revision 11433)
@@ -0,0 +1,99 @@
+canvas {
+    default-points: false;
+    default-lines: false;
+}
+
+node[test] {
+	symbol-stroke-color: red;
+	symbol-stroke-opacity: 1;
+	symbol-fill-color: orange;
+	symbol-fill-opacity: 1;
+	
+	symbol-shape: square;
+	symbol-size: 10;
+	symbol-stroke-width: 2;
+}
+
+node[test=circle] {
+	symbol-shape: circle;
+}
+
+node[test=triangle] {
+	symbol-shape: triangle;
+}
+
+node[test=pentagon] {
+	symbol-shape: pentagon;
+}
+
+node[test=hexagon] {
+	symbol-shape: circle;
+}
+
+node[test=heptagon] {
+	symbol-shape: triangle;
+}
+
+node[test=octagon] {
+	symbol-shape: pentagon;
+}
+
+node[test=nonagon] {
+	symbol-shape: circle;
+}
+
+node[test=decagon] {
+	symbol-shape: triangle;
+}
+
+node[test=fill-color] {
+	symbol-fill-color: green;
+}
+
+node[test=fill-color-none] {
+	symbol-fill-color: none;
+}
+
+node[test=fill-opacity] {
+	symbol-fill-opacity: .5;
+}
+
+node[test=stroke-color] {
+	symbol-stroke-color: green;
+}
+
+node[test=stroke-color-none] {
+	symbol-stroke-color: none;
+}
+
+node[test=stroke-opacity] {
+	symbol-stroke-opacity: .5;
+}
+
+node[test=small] {
+	symbol-size: 3;
+	symbol-stroke-width: 1;
+}
+
+node[test=big] {
+	symbol-size: 20;
+}
+
+node[test=thin] {
+	symbol-stroke-width: .5;
+}
+
+node[test=bold] {
+	symbol-stroke-width: 4;
+}
+
+node[defaults=fill] {
+	/* other values are defaults. */
+	symbol-shape: square;
+	symbol-fill-color: red;
+}
+
+node[defaults=stroke] {
+	symbol-shape: square;
+	symbol-stroke-color: red;
+}
Index: trunk/test/data/renderer/way-width/data.osm
===================================================================
--- trunk/test/data/renderer/way-width/data.osm	(revision 11433)
+++ trunk/test/data/renderer/way-width/data.osm	(revision 11433)
@@ -0,0 +1,88 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version='0.6' upload='true' generator='JOSM'>
+  <node id='-30552' action='modify' visible='true' lat='0.1' lon='0.1' />
+  <node id='-30554' action='modify' visible='true' lat='0.1' lon='0.3' />
+  <node id='-30556' action='modify' visible='true' lat='0.1' lon='0.5' />
+  <node id='-30558' action='modify' visible='true' lat='0.1' lon='0.7' />
+  <node id='-30560' action='modify' visible='true' lat='0.1' lon='0.9' />
+  <node id='-30562' action='modify' visible='true' lat='0.3' lon='0.1' />
+  <node id='-30564' action='modify' visible='true' lat='0.3' lon='0.3' />
+  <node id='-30566' action='modify' visible='true' lat='0.3' lon='0.5' />
+  <node id='-30568' action='modify' visible='true' lat='0.3' lon='0.7' />
+  <node id='-30570' action='modify' visible='true' lat='0.3' lon='0.9' />
+  <node id='-30572' action='modify' visible='true' lat='0.5' lon='0.1' />
+  <node id='-30574' action='modify' visible='true' lat='0.5' lon='0.3' />
+  <node id='-30576' action='modify' visible='true' lat='0.5' lon='0.5' />
+  <node id='-30578' action='modify' visible='true' lat='0.5' lon='0.7' />
+  <node id='-30580' action='modify' visible='true' lat='0.5' lon='0.9' />
+  <node id='-30582' action='modify' visible='true' lat='0.7' lon='0.1' />
+  <node id='-30584' action='modify' visible='true' lat='0.7' lon='0.3' />
+  <node id='-30586' action='modify' visible='true' lat='0.7' lon='0.5' />
+  <node id='-30588' action='modify' visible='true' lat='0.7' lon='0.7' />
+  <node id='-30590' action='modify' visible='true' lat='0.7' lon='0.9' />
+  <node id='-30592' action='modify' visible='true' lat='0.9' lon='0.1' />
+  <node id='-30594' action='modify' visible='true' lat='0.9' lon='0.3' />
+  <node id='-30596' action='modify' visible='true' lat='0.9' lon='0.5' />
+  <node id='-30598' action='modify' visible='true' lat='0.9' lon='0.7' />
+  <node id='-30600' action='modify' visible='true' lat='0.9' lon='0.9' />
+  <way id='-30626' action='modify' visible='true'>
+    <nd ref='-30600' />
+    <nd ref='-30598' />
+    <tag k='highlight' v='yes' />
+    <tag k='test' v='width5' />
+  </way>
+  <way id='-30629' action='modify' visible='true'>
+    <nd ref='-30590' />
+    <nd ref='-30588' />
+    <tag k='highlight' v='yes' />
+    <tag k='test' v='width4' />
+  </way>
+  <way id='-30630' action='modify' visible='true'>
+    <nd ref='-30580' />
+    <nd ref='-30578' />
+    <tag k='highlight' v='yes' />
+    <tag k='test' v='width3' />
+  </way>
+  <way id='-30631' action='modify' visible='true'>
+    <nd ref='-30570' />
+    <nd ref='-30568' />
+    <tag k='highlight' v='yes' />
+    <tag k='test' v='width2' />
+  </way>
+  <way id='-30632' action='modify' visible='true'>
+    <nd ref='-30560' />
+    <nd ref='-30558' />
+    <tag k='highlight' v='yes' />
+    <tag k='test' v='width1' />
+  </way>
+  <way id='-30638' action='modify' visible='true'>
+    <nd ref='-30596' />
+    <nd ref='-30594' />
+    <nd ref='-30592' />
+    <tag k='test' v='width5' />
+  </way>
+  <way id='-30640' action='modify' visible='true'>
+    <nd ref='-30586' />
+    <nd ref='-30584' />
+    <nd ref='-30582' />
+    <tag k='test' v='width4' />
+  </way>
+  <way id='-30642' action='modify' visible='true'>
+    <nd ref='-30576' />
+    <nd ref='-30574' />
+    <nd ref='-30572' />
+    <tag k='test' v='width3' />
+  </way>
+  <way id='-30644' action='modify' visible='true'>
+    <nd ref='-30566' />
+    <nd ref='-30564' />
+    <nd ref='-30562' />
+    <tag k='test' v='width2' />
+  </way>
+  <way id='-30646' action='modify' visible='true'>
+    <nd ref='-30556' />
+    <nd ref='-30554' />
+    <nd ref='-30552' />
+    <tag k='test' v='width1' />
+  </way>
+</osm>
Index: trunk/test/data/renderer/way-width/style.mapcss
===================================================================
--- trunk/test/data/renderer/way-width/style.mapcss	(revision 11433)
+++ trunk/test/data/renderer/way-width/style.mapcss	(revision 11433)
@@ -0,0 +1,28 @@
+canvas {
+    default-points: false;
+    default-lines: false;
+}
+
+way[test] {
+	color: green;
+}
+
+way[test=width5] {
+	width: thinnest;
+}
+
+way[test=width4] {
+	width: 10.5;
+}
+
+way[test=width3] {
+	width: 30;
+}
+
+way[test=width2] {
+	width: 3;
+}
+
+way[test=width1] {
+	width: .5;
+}
Index: trunk/test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java	(revision 11433)
+++ trunk/test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java	(revision 11433)
@@ -0,0 +1,197 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.imageio.ImageIO;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
+import org.openstreetmap.josm.gui.NavigatableComponent;
+import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
+import org.openstreetmap.josm.gui.preferences.SourceEntry;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.io.OsmReader;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Test cases for {@link StyledMapRenderer} and the MapCSS classes.
+ * <p>
+ * This test uses the data and reference files stored in the test data directory {@value #TEST_DATA_BASE}
+ * @author Michael Zangl
+ */
+@RunWith(Parameterized.class)
+public class MapCSSRendererTest {
+    private static final String TEST_DATA_BASE = "/renderer/";
+    /**
+     * lat = 0..1, lon = 0..1
+     */
+    private static final Bounds AREA_DEFAULT = new Bounds(0, 0, 1, 1);
+    private static final int IMAGE_SIZE = 256;
+
+    /**
+     * Minimal test rules required
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().preferences().projection();
+
+    private TestConfig testConfig;
+
+    /**
+     * The different configurations of this test.
+     * @return The parameters.
+     */
+    @Parameters
+    public static Collection<Object[]> testRuns() {
+        return Stream.of(
+                /** Tests for StyledMapRenderer#drawNodeSymbol */
+                new TestConfig("node-shapes", AREA_DEFAULT),
+
+                /** Tests that StyledMapRenderer#drawWay respects width */
+                new TestConfig("way-width", AREA_DEFAULT)
+
+                ).map(e -> new Object[] {e})
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * @param testConfig The config to use for this test.
+     */
+    public MapCSSRendererTest(TestConfig testConfig) {
+        this.testConfig = testConfig;
+    }
+
+    /**
+     * Run the test using {@link #testConfig}
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void render() throws Exception {
+        // load the data
+        DataSet dataSet = testConfig.getOsmDataSet();
+
+        // load the style
+        MapCSSStyleSource.STYLE_SOURCE_LOCK.writeLock().lock();
+        try {
+            MapPaintStyles.getStyles().clear();
+
+            MapCSSStyleSource source = new MapCSSStyleSource(testConfig.getStyleSourceEntry());
+            source.loadStyleSource();
+            if (!source.getErrors().isEmpty()) {
+                fail("Failed to load style file. Errors: " + source.getErrors());
+            }
+            MapPaintStyles.getStyles().setStyleSources(Arrays.asList(source));
+
+        } finally {
+            MapCSSStyleSource.STYLE_SOURCE_LOCK.writeLock().unlock();
+        }
+
+        // create the renderer
+        BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
+        NavigatableComponent nc = new NavigatableComponent() {
+            {
+                setBounds(0, 0, IMAGE_SIZE, IMAGE_SIZE);
+                updateLocationState();
+            }
+
+            @Override
+            protected boolean isVisibleOnScreen() {
+                return true;
+            }
+
+            @Override
+            public Point getLocationOnScreen() {
+                return new Point(0, 0);
+            }
+        };
+        nc.zoomTo(testConfig.testArea);
+        dataSet.allPrimitives().stream().forEach(n -> n.setHighlighted(n.isKeyTrue("highlight")));
+        new StyledMapRenderer(image.createGraphics(), nc, false).render(dataSet, false, testConfig.testArea);
+
+        BufferedImage reference = testConfig.getReference();
+
+        // now compute differences:
+        assertEquals(IMAGE_SIZE, reference.getWidth());
+        assertEquals(IMAGE_SIZE, reference.getHeight());
+
+        StringBuilder differences = new StringBuilder();
+
+        for (int y = 0; y < reference.getHeight(); y++) {
+            for (int x = 0; x < reference.getWidth(); x++) {
+                int expected = reference.getRGB(x, y);
+                int result = image.getRGB(x, y);
+                if (expected != result && differences.length() < 500) {
+                    differences.append("\nDifference at ")
+                               .append(x)
+                               .append(",")
+                               .append(y)
+                               .append(": Expected ")
+                               .append(Integer.toHexString(expected))
+                               .append(" but got ")
+                               .append(Integer.toHexString(result));
+                }
+            }
+        }
+
+        if (differences.length() > 0) {
+            // You can use this to debug:
+            ImageIO.write(image, "png", new File(testConfig.getTestDirectory() + "/test-output.png"));
+            fail("Images for test " + testConfig.testDirectory + " differ: " + differences.toString());
+        }
+    }
+
+    private static class TestConfig {
+        private final String testDirectory;
+        private final Bounds testArea;
+
+        TestConfig(String testDirectory, Bounds testArea) {
+            this.testDirectory = testDirectory;
+            this.testArea = testArea;
+        }
+
+        public BufferedImage getReference() throws IOException {
+            return ImageIO.read(new File(getTestDirectory() + "/reference.png"));
+        }
+
+        private String getTestDirectory() {
+            return TestUtils.getTestDataRoot() + TEST_DATA_BASE + testDirectory;
+        }
+
+        public SourceEntry getStyleSourceEntry() {
+            return new SourceEntry(getTestDirectory() + "/style.mapcss",
+                    "test style", "a test style", true // active
+            );
+        }
+
+        public DataSet getOsmDataSet() throws FileNotFoundException, IllegalDataException {
+            return OsmReader.parseDataSet(new FileInputStream(getTestDirectory() + "/data.osm"), null);
+        }
+
+        @Override
+        public String toString() {
+            return "TestConfig [testDirectory=" + testDirectory + ", testArea=" + testArea + ']';
+        }
+    }
+}
