diff --git a/.travis.yml b/.travis.yml
index 2d6908b488..cf56d6cc8d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,13 @@
+env:
+ - DISPLAY=:99.0
+
before_install:
- "sudo pip install -r requirements.txt"
- "npm install -g npm && npm install"
before_script:
- "rm src/ol/renderer/webgl/*shader.js"
+ - "sh -e /etc/init.d/xvfb start"
script: "./build.py ci"
diff --git a/build.py b/build.py
index 0ca69bd75d..058d61bc76 100755
--- a/build.py
+++ b/build.py
@@ -145,6 +145,10 @@ SPEC = [path
for path in ifind('test/spec')
if path.endswith('.js')]
+SPEC_RENDERING = [path
+ for path in ifind('test_rendering/spec')
+ if path.endswith('.js')]
+
TASKS = [path
for path in ifind('tasks')
if path.endswith('.js')]
@@ -172,7 +176,7 @@ def report_sizes(t):
virtual('default', 'build')
-virtual('ci', 'lint', 'build', 'test',
+virtual('ci', 'lint', 'build', 'test', 'test-rendering',
'build/examples/all.combined.js', 'check-examples', 'apidoc')
@@ -231,19 +235,28 @@ for glsl_src in GLSL_SRC:
shader_src_helper(glsl_src)
-@target('build/test/requireall.js', SPEC)
-def build_test_requireall_js(t):
+def build_requires(task):
requires = set()
- for dependency in t.dependencies:
+ for dependency in task.dependencies:
for line in open(dependency, 'rU'):
match = re.match(r'goog\.provide\(\'(.*)\'\);', line)
if match:
requires.add(match.group(1))
- with open(t.name, 'wb') as f:
+ with open(task.name, 'wb') as f:
for require in sorted(requires):
f.write('goog.require(\'%s\');\n' % (require,))
+@target('build/test_requires.js', SPEC)
+def build_test_requires(t):
+ build_requires(t)
+
+
+@target('build/test_rendering_requires.js', SPEC_RENDERING)
+def build_test_rendering_requires(t):
+ build_requires(t)
+
+
virtual('build-examples', 'examples', 'build/examples/all.combined.js',
EXAMPLES_COMBINED)
@@ -392,7 +405,8 @@ def examples_star_combined_js(name, match):
return Target(name, action=action, dependencies=dependencies)
-@target('serve', 'examples', NPM_INSTALL)
+@target('serve', 'examples', 'build/test_requires.js', 'build/test_rendering_requires.js',
+ NPM_INSTALL)
def serve(t):
t.run('node', 'tasks/serve.js')
@@ -401,7 +415,8 @@ virtual('lint', 'build/lint-timestamp', 'build/check-requires-timestamp',
'build/check-whitespace-timestamp', 'jshint')
-@target('build/lint-timestamp', SRC, EXAMPLES_SRC, SPEC, precious=True)
+@target('build/lint-timestamp', SRC, EXAMPLES_SRC, SPEC, SPEC_RENDERING,
+ precious=True)
def build_lint_src_timestamp(t):
t.run('%(GJSLINT)s',
'--jslint_error=all',
@@ -412,8 +427,8 @@ def build_lint_src_timestamp(t):
virtual('jshint', 'build/jshint-timestamp')
-@target('build/jshint-timestamp', SRC, EXAMPLES_SRC, SPEC, TASKS,
- NPM_INSTALL, precious=True)
+@target('build/jshint-timestamp', SRC, EXAMPLES_SRC, SPEC, SPEC_RENDERING,
+ TASKS, NPM_INSTALL, precious=True)
def build_jshint_timestamp(t):
t.run(variables.JSHINT, '--verbose', t.newer(t.dependencies))
t.touch()
@@ -442,7 +457,8 @@ def _strip_comments(lines):
yield lineno, line
-@target('build/check-requires-timestamp', SRC, EXAMPLES_SRC, SHADER_SRC, SPEC)
+@target('build/check-requires-timestamp', SRC, EXAMPLES_SRC, SHADER_SRC,
+ SPEC, SPEC_RENDERING)
def build_check_requires_timestamp(t):
unused_count = 0
all_provides = set()
@@ -583,7 +599,7 @@ def build_check_requires_timestamp(t):
@target('build/check-whitespace-timestamp', SRC, EXAMPLES_SRC,
- SPEC, JSDOC_SRC, precious=True)
+ SPEC, SPEC_RENDERING, JSDOC_SRC, precious=True)
def build_check_whitespace_timestamp(t):
CR_RE = re.compile(r'\r')
LEADING_WHITESPACE_RE = re.compile(r'\s+')
@@ -723,7 +739,7 @@ def check_examples(t):
sys.exit(1)
-@target('test', NPM_INSTALL, phony=True)
+@target('test', NPM_INSTALL, 'build/test_requires.js', phony=True)
def test(t):
t.run('node', 'tasks/test.js')
@@ -733,6 +749,16 @@ def test_coverage(t):
t.run('node', 'tasks/test-coverage.js')
+@target('test-rendering', 'build/test_rendering_requires.js',
+ NPM_INSTALL, phony=True)
+def test_rendering(t):
+ # create a temp. profile to run the tests with WebGL
+ tmp_profile_dir = 'build/slimerjs-profile'
+ t.rm_rf(tmp_profile_dir)
+ t.cp_r('test_rendering/slimerjs-profile', tmp_profile_dir)
+ t.run('node', 'tasks/test-rendering.js')
+
+
@target('fixme', phony=True)
def find_fixme(t):
regex = re.compile('FIXME|TODO')
@@ -797,6 +823,7 @@ The most common targets are:
CSS. This is also the default build target which runs when
no target is specified.
test - Runs the testsuite and displays the results.
+ test-rendering - Runs the rendering testsuite and displays the results.
check - Runs the lint-target, builds some OpenLayers files, and
then runs test. Many developers call this target often
while working on the code.
@@ -806,9 +833,9 @@ Other less frequently used targets are:
apidoc - Builds the API-Documentation using JSDoc3.
ci - Builds all examples in various modes and usually takes a
long time to finish. This target calls the following
- targets: lint, build, build-all, test, build-examples,
- check-examples and apidoc. This is the target run on
- Travis CI.
+ targets: lint, build, build-all, test, test-rendering,
+ build-examples, check-examples and apidoc. This is the
+ target run on Travis CI.
test-coverage - Generates a test coverage report in the coverage folder.
reallyclean - Remove untracked files from the repository.
checkdeps - Checks whether all required development software is
diff --git a/package.json b/package.json
index 3fdf2de60c..ff89ab49da 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,9 @@
"mocha-phantomjs": "3.5.1",
"phantomjs": "1.9.10",
"proj4": "2.3.3",
- "sinon": "1.10.3"
+ "sinon": "1.10.3",
+ "slimerjs-edge": "0.10.0-pre-2",
+ "resemblejs": "1.2.0"
},
"ext": [
"rbush"
diff --git a/tasks/serve.js b/tasks/serve.js
index a7f792d51b..dfed1e5e8d 100644
--- a/tasks/serve.js
+++ b/tasks/serve.js
@@ -23,7 +23,10 @@ var createServer = exports.createServer = function(callback) {
lib: [
'src/**/*.js',
'build/ol.ext/*.js',
- 'test/spec/**/*.test.js'
+ 'test/spec/**/*.test.js',
+ 'test_rendering/spec/**/*.test.js',
+ 'build/test_requires.js',
+ 'build/test_rendering_requires.js'
],
main: 'examples/*.js'
});
@@ -41,12 +44,21 @@ var createServer = exports.createServer = function(callback) {
getMain: function(req) {
var main;
var query = url.parse(req.url, true).query;
- if (query.id) {
- var referer = req.headers.referer;
- if (referer) {
- var from = path.join(process.cwd(),
- path.dirname(url.parse(referer).pathname));
- main = path.resolve(from, query.id + '.js');
+ var referer = req.headers.referer;
+ var pathName = url.parse(referer).pathname;
+ if (pathName.indexOf('/test/') === 0) {
+ main = path.resolve(
+ path.join(process.cwd(), 'build'), 'test_requires.js');
+ } else if (pathName.indexOf('/test_rendering/') === 0) {
+ main = path.resolve(
+ path.join(process.cwd(), 'build'), 'test_rendering_requires.js');
+ } else {
+ if (query.id) {
+ if (referer) {
+ var from = path.join(process.cwd(),
+ path.dirname(url.parse(referer).pathname));
+ main = path.resolve(from, query.id + '.js');
+ }
}
}
return main;
diff --git a/tasks/test-coverage.js b/tasks/test-coverage.js
index 5e1aab20be..8fc0061e43 100644
--- a/tasks/test-coverage.js
+++ b/tasks/test-coverage.js
@@ -13,7 +13,7 @@ var wrench = require('wrench');
var path = require('path');
var glob = require('glob');
-var runTestsuite = require('./test');
+var runTestsuite = require('./test').runTests;
// setup some pathes
var dir = path.join(__dirname, '../src');
diff --git a/tasks/test-rendering.js b/tasks/test-rendering.js
new file mode 100644
index 0000000000..d440a52e52
--- /dev/null
+++ b/tasks/test-rendering.js
@@ -0,0 +1,65 @@
+/**
+ * This task starts a dev server that provides a script loader for OpenLayers
+ * and Closure Library and runs rendering tests in SlimerJS.
+ */
+
+var fs = require('fs');
+var path = require('path');
+var spawn = require('child_process').spawn;
+
+var slimerjs = require('slimerjs-edge');
+
+var serve = require('./serve');
+var listen = require('./test').listen;
+
+
+/**
+ * Create the debug server and run tests.
+ */
+serve.createServer(function(err, server) {
+ if (err) {
+ process.stderr.write(err.message + '\n');
+ process.exit(1);
+ }
+
+ listen(3001, 3005, server, function(err) {
+ if (err) {
+ process.stderr.write('Server failed to start: ' + err.message + '\n');
+ process.exit(1);
+ }
+
+ var address = server.address();
+ var url = 'http://' + address.address + ':' + address.port;
+ var profile = path.join(__dirname, '../build/slimerjs-profile');
+ var args = [
+ '-profile',
+ profile,
+ path.join(__dirname,
+ '../test_rendering/test.js'),
+ url + '/test_rendering/index.html'
+ ];
+
+ var child = spawn(slimerjs.path, args, {stdio: 'inherit'});
+ child.on('exit', function(code) {
+ // FIXME SlimerJS has a problem with returning the correct return
+ // code when using a custom profile, see
+ // https://github.com/laurentj/slimerjs/issues/333
+ // as a work-around we are currently reading the return code from
+ // a file created in the profile directory.
+ // if this issue is fixed we should use the npm package 'slimerjs'
+ // instead of the nightly build 'slimerjs-edge'.
+ var exitstatus = path.join(profile, 'exitstatus');
+ fs.readFile(exitstatus, {encoding: 'utf-8'}, function(err, data) {
+ if (err) {
+ process.stderr.write(
+ 'Error getting the exit status of SlimerJS' + '\n');
+ process.stderr.write(err);
+ process.exit(1);
+ } else {
+ process.exit(data);
+ }
+ });
+ });
+ });
+
+});
diff --git a/tasks/test.js b/tasks/test.js
index c9c8a96988..e644dea235 100644
--- a/tasks/test.js
+++ b/tasks/test.js
@@ -81,6 +81,9 @@ if (require.main === module) {
});
}
-module.exports = runTests;
+module.exports = {
+ runTests: runTests,
+ listen: listen
+};
diff --git a/test/spec/ol/format/geojsonformat.test.js b/test/spec/ol/format/geojsonformat.test.js
index df3c46342a..6a4906c9b9 100644
--- a/test/spec/ol/format/geojsonformat.test.js
+++ b/test/spec/ol/format/geojsonformat.test.js
@@ -693,7 +693,8 @@ describe('ol.format.GeoJSON', function() {
var newPoint = format.readGeometry(geojson, {
featureProjection: 'EPSG:3857'
});
- expect(point.getCoordinates()[0]).to.eql(newPoint.getCoordinates()[0]);
+ expect(point.getCoordinates()[0]).to.roughlyEqual(
+ newPoint.getCoordinates()[0], 1e-8);
expect(
Math.abs(point.getCoordinates()[1] - newPoint.getCoordinates()[1]))
.to.be.lessThan(0.0000001);
diff --git a/test/test-extensions.js b/test/test-extensions.js
index e5fd2a2364..c9cdac18a5 100644
--- a/test/test-extensions.js
+++ b/test/test-extensions.js
@@ -1,8 +1,13 @@
// FIXME remove afterLoadXml as it uses the wrong XML parser on IE9
-// helper functions for async testing
+// helper functions for async testing and other utility functions.
(function(global) {
+ /**
+ * The default tolerance for image comparisons.
+ */
+ global.IMAGE_TOLERANCE = 1.5;
+
function afterLoad(type, path, next) {
var client = new XMLHttpRequest();
client.open('GET', path, true);
@@ -326,5 +331,107 @@
return this;
};
+ global.createMapDiv = function(width, height) {
+ var target = document.createElement('div');
+ var style = target.style;
+ style.position = 'absolute';
+ style.left = '-1000px';
+ style.top = '-1000px';
+ style.width = width + 'px';
+ style.height = height + 'px';
+ document.body.appendChild(target);
+
+ return target;
+ };
+
+ global.disposeMap = function(map) {
+ var target = map.getTarget();
+ map.setTarget(null);
+ goog.dispose(map);
+ document.body.removeChild(target);
+ };
+
+ global.assertWebGL = function(map) {
+ if(!ol.has.WEBGL) {
+ expect().fail('No WebGL support!');
+ }
+ };
+
+ function resembleCanvas(canvas, referenceImage, tolerance, done) {
+ if (window.showMap) {
+ document.getElementById('debug').appendChild(canvas);
+ }
+
+ resemble(referenceImage)
+ .compareTo(canvas.getContext('2d').getImageData(
+ 0, 0, canvas.width, canvas.height))
+ .onComplete(function(data) {
+ if(!data.isSameDimensions) {
+ expect().fail(
+ 'The dimensions of the reference image and ' +
+ 'the test canvas are not the same.');
+ }
+
+ if (data.misMatchPercentage > tolerance) {
+ if (window.showDiff) {
+ var diffImage = new Image();
+ diffImage.src = data.getImageDataUrl();
+ document.getElementById('debug').appendChild(diffImage);
+ }
+ expect(data.misMatchPercentage).to.be.below(tolerance);
+ }
+ done();
+ });
+ };
+
+ function expectResembleCanvas(map, referenceImage, tolerance, done) {
+ map.render();
+ map.on('postcompose', function(event) {
+ var canvas = event.context.canvas;
+ resembleCanvas(canvas, referenceImage, tolerance, done);
+ });
+ };
+
+ function expectResembleWebGL(map, referenceImage, tolerance, done) {
+ map.render();
+ map.on('postcompose', function(event) {
+ if (event.frameState.animate) {
+ // make sure the tile-queue is empty
+ return;
+ }
+
+ var webglCanvas = event.glContext.getCanvas();
+ expect(webglCanvas).to.be.a(HTMLCanvasElement);
+
+ // draw the WebGL canvas on a new canvas, because we can not create
+ // a 2d context for that canvas because there is already a webgl context.
+ var canvas = document.createElement('canvas');
+ canvas.width = webglCanvas.width;
+ canvas.height = webglCanvas.height;
+ canvas.getContext('2d').drawImage(webglCanvas, 0, 0,
+ webglCanvas.width, webglCanvas.height);
+
+ resembleCanvas(canvas, referenceImage, tolerance, done);
+ });
+ };
+
+ /**
+ * Assert that the given map resembles a reference image.
+ *
+ * @param {ol.Map} map A map using the canvas renderer.
+ * @param {string} referenceImage Path to the reference image.
+ * @param {number} tolerance The accepted mismatch tolerance.
+ * @param {function} done A callback to indicate that the test is done.
+ */
+ global.expectResemble = function(map, referenceImage, tolerance, done) {
+ if (map.getRenderer() instanceof ol.renderer.canvas.Map) {
+ expectResembleCanvas(map, referenceImage, tolerance, done);
+ } else if (map.getRenderer() instanceof ol.renderer.webgl.Map) {
+ expectResembleWebGL(map, referenceImage, tolerance, done);
+ } else {
+ expect().fail(
+ 'resemble only works with the canvas and WebGL renderer.');
+ }
+ };
})(this);
diff --git a/test_rendering/README.md b/test_rendering/README.md
new file mode 100644
index 0000000000..8f305068a4
--- /dev/null
+++ b/test_rendering/README.md
@@ -0,0 +1,25 @@
+# Rendering tests
+
+This directory contains rendering tests which compare a rendered map with a
+reference image using [resemble.js](http://huddle.github.io/Resemble.js/).
+
+Similar to the unit tests, there are two ways to run the tests: directly in the
+browser or using [SlimerJS](http://slimerjs.org/) from the command-line.
+
+To run the tests in the browser, make sure the development server is running
+(`./build.py serve`) and open the URL
+[http://localhost:3000/test_rendering/index.html](http://localhost:3000/test_rendering/index.html).
+
+From the command-line the tests can be run with the build target `./build.py test-rendering`.
+
+## Adding new tests
+When creating a new test case, a reference image has to be created. By appending `?generate`
+to the URL, a canvas with the rendered map will be shown on the page when calling
+`expectResemble`. Then the reference image can simply be created with a right-click
+and "Save image as".
+
+It is recommended to only run a single test case when generating the reference image.
+
+## Image difference
+When a test fails, an image showing the difference between the reference image and the
+rendered map can be displayed by appending `?showdiff` to the URL.
diff --git a/test_rendering/index.html b/test_rendering/index.html
new file mode 100644
index 0000000000..b0293d06d0
--- /dev/null
+++ b/test_rendering/index.html
@@ -0,0 +1,93 @@
+
+
+
+ OL Rendering Test Runner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test_rendering/slimerjs-profile/prefs.js b/test_rendering/slimerjs-profile/prefs.js
new file mode 100644
index 0000000000..c2deee1837
--- /dev/null
+++ b/test_rendering/slimerjs-profile/prefs.js
@@ -0,0 +1,3 @@
+user_pref("webgl.force-enabled", true);
+user_pref("webgl.disabled", false);
+user_pref("webgl.msaa-force", true);
diff --git a/test_rendering/slimerjs-profile/times.json b/test_rendering/slimerjs-profile/times.json
new file mode 100755
index 0000000000..84e52a5d3e
--- /dev/null
+++ b/test_rendering/slimerjs-profile/times.json
@@ -0,0 +1,3 @@
+{
+"created": 1427803527460
+}
diff --git a/test_rendering/spec/ol/data/icon.png b/test_rendering/spec/ol/data/icon.png
new file mode 100644
index 0000000000..ed886623d5
Binary files /dev/null and b/test_rendering/spec/ol/data/icon.png differ
diff --git a/test_rendering/spec/ol/data/tiles/osm/5/5/12.png b/test_rendering/spec/ol/data/tiles/osm/5/5/12.png
new file mode 100644
index 0000000000..ada08786c9
Binary files /dev/null and b/test_rendering/spec/ol/data/tiles/osm/5/5/12.png differ
diff --git a/test_rendering/spec/ol/data/tiles/stamen-labels/5/5/12.png b/test_rendering/spec/ol/data/tiles/stamen-labels/5/5/12.png
new file mode 100644
index 0000000000..b88d09eb76
Binary files /dev/null and b/test_rendering/spec/ol/data/tiles/stamen-labels/5/5/12.png differ
diff --git a/test_rendering/spec/ol/expected/pan-canvas.png b/test_rendering/spec/ol/expected/pan-canvas.png
new file mode 100644
index 0000000000..f9c84f316d
Binary files /dev/null and b/test_rendering/spec/ol/expected/pan-canvas.png differ
diff --git a/test_rendering/spec/ol/expected/pan-webgl.png b/test_rendering/spec/ol/expected/pan-webgl.png
new file mode 100644
index 0000000000..49fe4e01aa
Binary files /dev/null and b/test_rendering/spec/ol/expected/pan-webgl.png differ
diff --git a/test_rendering/spec/ol/expected/render-canvas.png b/test_rendering/spec/ol/expected/render-canvas.png
new file mode 100644
index 0000000000..ed50f1d7d2
Binary files /dev/null and b/test_rendering/spec/ol/expected/render-canvas.png differ
diff --git a/test_rendering/spec/ol/expected/render-webgl.png b/test_rendering/spec/ol/expected/render-webgl.png
new file mode 100644
index 0000000000..ab1663b974
Binary files /dev/null and b/test_rendering/spec/ol/expected/render-webgl.png differ
diff --git a/test_rendering/spec/ol/expected/rotate-canvas.png b/test_rendering/spec/ol/expected/rotate-canvas.png
new file mode 100644
index 0000000000..e614cf3c08
Binary files /dev/null and b/test_rendering/spec/ol/expected/rotate-canvas.png differ
diff --git a/test_rendering/spec/ol/expected/rotate-webgl.png b/test_rendering/spec/ol/expected/rotate-webgl.png
new file mode 100644
index 0000000000..014481d84b
Binary files /dev/null and b/test_rendering/spec/ol/expected/rotate-webgl.png differ
diff --git a/test_rendering/spec/ol/expected/zoom-canvas.png b/test_rendering/spec/ol/expected/zoom-canvas.png
new file mode 100644
index 0000000000..ab3b0abde3
Binary files /dev/null and b/test_rendering/spec/ol/expected/zoom-canvas.png differ
diff --git a/test_rendering/spec/ol/expected/zoom-webgl.png b/test_rendering/spec/ol/expected/zoom-webgl.png
new file mode 100644
index 0000000000..f89e01ad68
Binary files /dev/null and b/test_rendering/spec/ol/expected/zoom-webgl.png differ
diff --git a/test_rendering/spec/ol/layer/expected/2-layers-canvas.png b/test_rendering/spec/ol/layer/expected/2-layers-canvas.png
new file mode 100644
index 0000000000..ad069af45c
Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/2-layers-canvas.png differ
diff --git a/test_rendering/spec/ol/layer/expected/2-layers-webgl.png b/test_rendering/spec/ol/layer/expected/2-layers-webgl.png
new file mode 100644
index 0000000000..a982b089de
Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/2-layers-webgl.png differ
diff --git a/test_rendering/spec/ol/layer/expected/image-canvas.png b/test_rendering/spec/ol/layer/expected/image-canvas.png
new file mode 100644
index 0000000000..86f9bedd38
Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/image-canvas.png differ
diff --git a/test_rendering/spec/ol/layer/expected/image-webgl.png b/test_rendering/spec/ol/layer/expected/image-webgl.png
new file mode 100644
index 0000000000..e0e4a150d8
Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/image-webgl.png differ
diff --git a/test_rendering/spec/ol/layer/expected/opacity-canvas.png b/test_rendering/spec/ol/layer/expected/opacity-canvas.png
new file mode 100644
index 0000000000..a69bb84d85
Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/opacity-canvas.png differ
diff --git a/test_rendering/spec/ol/layer/expected/opacity-webgl.png b/test_rendering/spec/ol/layer/expected/opacity-webgl.png
new file mode 100644
index 0000000000..389d138042
Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/opacity-webgl.png differ
diff --git a/test_rendering/spec/ol/layer/expected/osm-canvas.png b/test_rendering/spec/ol/layer/expected/osm-canvas.png
new file mode 100644
index 0000000000..86f9bedd38
Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/osm-canvas.png differ
diff --git a/test_rendering/spec/ol/layer/expected/osm-webgl.png b/test_rendering/spec/ol/layer/expected/osm-webgl.png
new file mode 100644
index 0000000000..86f9bedd38
Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/osm-webgl.png differ
diff --git a/test_rendering/spec/ol/layer/image.test.js b/test_rendering/spec/ol/layer/image.test.js
new file mode 100644
index 0000000000..692090e015
--- /dev/null
+++ b/test_rendering/spec/ol/layer/image.test.js
@@ -0,0 +1,94 @@
+goog.provide('ol.test.rendering.layer.Image');
+
+describe('ol.rendering.layer.Image', function() {
+
+ var target, map;
+
+ function createMap(renderer) {
+ target = createMapDiv(50, 50);
+
+ map = new ol.Map({
+ target: target,
+ renderer: renderer,
+ view: new ol.View({
+ center: ol.proj.transform(
+ [-122.416667, 37.783333], 'EPSG:4326', 'EPSG:3857'),
+ zoom: 5
+ })
+ });
+ return map;
+ }
+
+ function waitForImages(sources, layerOptions, onImagesLoaded) {
+ var imagesLoading = 0;
+ var imagesLoaded = 0;
+
+ var update = function() {
+ if (imagesLoading === imagesLoaded) {
+ onImagesLoaded();
+ }
+ };
+
+ goog.array.forEach(sources, function(source) {
+ source.on('imageloadstart', function(event) {
+ imagesLoading++;
+ });
+ source.on('imageloadend', function(event) {
+ imagesLoaded++;
+ update();
+ });
+ source.on('imageloaderror', function(event) {
+ expect().fail('Image failed to load');
+ });
+
+ var options = {
+ source: source
+ };
+ goog.object.extend(options, layerOptions);
+ map.addLayer(new ol.layer.Image(options));
+ });
+ }
+
+ describe('single image layer', function() {
+ var source;
+
+ beforeEach(function() {
+ source = new ol.source.ImageStatic({
+ url: 'spec/ol/data/tiles/osm/5/5/12.png',
+ imageExtent: new ol.tilegrid.XYZ({}).getTileCoordExtent(
+ [5, 5, -12 - 1]),
+ projection: ol.proj.get('EPSG:3857')
+ });
+ });
+
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ waitForImages([source], {}, function() {
+ expectResemble(map, 'spec/ol/layer/expected/image-canvas.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ waitForImages([source], {}, function() {
+ expectResemble(map, 'spec/ol/layer/expected/image-webgl.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+ });
+});
+
+goog.require('goog.array');
+goog.require('goog.object');
+goog.require('ol.proj');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.Image');
+goog.require('ol.source.ImageStatic');
+goog.require('ol.tilegrid.XYZ');
diff --git a/test_rendering/spec/ol/layer/tile.test.js b/test_rendering/spec/ol/layer/tile.test.js
new file mode 100644
index 0000000000..6d2340d0e7
--- /dev/null
+++ b/test_rendering/spec/ol/layer/tile.test.js
@@ -0,0 +1,155 @@
+goog.provide('ol.test.rendering.layer.Tile');
+
+describe('ol.rendering.layer.Tile', function() {
+
+ var target, map;
+
+ function createMap(renderer) {
+ target = createMapDiv(50, 50);
+
+ map = new ol.Map({
+ target: target,
+ renderer: renderer,
+ view: new ol.View({
+ center: ol.proj.transform(
+ [-122.416667, 37.783333], 'EPSG:4326', 'EPSG:3857'),
+ zoom: 5
+ })
+ });
+ return map;
+ }
+
+ function waitForTiles(sources, layerOptions, onTileLoaded) {
+ var tilesLoading = 0;
+ var tileLoaded = 0;
+
+ var update = function() {
+ if (tilesLoading === tileLoaded) {
+ onTileLoaded();
+ }
+ };
+
+ goog.array.forEach(sources, function(source) {
+ source.on('tileloadstart', function(event) {
+ tilesLoading++;
+ });
+ source.on('tileloadend', function(event) {
+ tileLoaded++;
+ update();
+ });
+ source.on('tileloaderror', function(event) {
+ expect().fail('Tile failed to load');
+ });
+
+ var options = {
+ source: source
+ };
+ goog.object.extend(options, layerOptions);
+ map.addLayer(new ol.layer.Tile(options));
+ });
+ }
+
+ describe('single tile layer', function() {
+ var source;
+
+ beforeEach(function() {
+ source = new ol.source.XYZ({
+ url: 'spec/ol/data/tiles/osm/{z}/{x}/{y}.png'
+ });
+ });
+
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ waitForTiles([source], {}, function() {
+ expectResemble(map, 'spec/ol/layer/expected/osm-canvas.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ waitForTiles([source], {}, function() {
+ expectResemble(map, 'spec/ol/layer/expected/osm-webgl.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+ });
+
+ describe('two tile layers', function() {
+ var source1, source2;
+
+ beforeEach(function() {
+ source1 = new ol.source.XYZ({
+ url: 'spec/ol/data/tiles/osm/{z}/{x}/{y}.png'
+ });
+ source2 = new ol.source.XYZ({
+ url: 'spec/ol/data/tiles/stamen-labels/{z}/{x}/{y}.png'
+ });
+ });
+
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ waitForTiles([source1, source2], {}, function() {
+ expectResemble(map, 'spec/ol/layer/expected/2-layers-canvas.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ waitForTiles([source1, source2], {}, function() {
+ expectResemble(map, 'spec/ol/layer/expected/2-layers-webgl.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+ });
+
+ describe('tile layer with opacity', function() {
+ var source;
+
+ beforeEach(function() {
+ source = new ol.source.XYZ({
+ url: 'spec/ol/data/tiles/osm/{z}/{x}/{y}.png'
+ });
+ });
+
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ waitForTiles([source], {opacity: 0.2}, function() {
+ expectResemble(map, 'spec/ol/layer/expected/opacity-canvas.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ waitForTiles([source], {opacity: 0.2}, function() {
+ expectResemble(map, 'spec/ol/layer/expected/opacity-webgl.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+ });
+});
+
+goog.require('goog.array');
+goog.require('goog.object');
+goog.require('ol.proj');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.Tile');
+goog.require('ol.source.XYZ');
diff --git a/test_rendering/spec/ol/map.test.js b/test_rendering/spec/ol/map.test.js
new file mode 100644
index 0000000000..424de13691
--- /dev/null
+++ b/test_rendering/spec/ol/map.test.js
@@ -0,0 +1,124 @@
+goog.provide('ol.test.rendering.Map');
+
+describe('ol.rendering.Map', function() {
+
+ var target, map;
+
+ function createMap(renderer) {
+ target = createMapDiv(50, 50);
+
+ var vectorLayer = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ features: [new ol.Feature({
+ geometry: new ol.geom.Point([0, 0])
+ })]
+ })
+ });
+
+ map = new ol.Map({
+ target: target,
+ renderer: renderer,
+ layers: [vectorLayer],
+ view: new ol.View({
+ projection: 'EPSG:4326',
+ center: [0, 0],
+ resolution: 1
+ })
+ });
+ return map;
+ }
+
+ describe('#render()', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ expectResemble(
+ map, 'spec/ol/expected/render-canvas.png', IMAGE_TOLERANCE, done);
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ expectResemble(
+ map, 'spec/ol/expected/render-webgl.png', IMAGE_TOLERANCE, done);
+ });
+ });
+
+ describe('#pan()', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ map.getView().setCenter([10, 10]);
+ expectResemble(
+ map, 'spec/ol/expected/pan-canvas.png', IMAGE_TOLERANCE, done);
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ map.getView().setCenter([10, 10]);
+ expectResemble(
+ map, 'spec/ol/expected/pan-webgl.png', IMAGE_TOLERANCE, done);
+ });
+ });
+
+ describe('#rotate()', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ map.getView().setRotation(90);
+ map.getView().setCenter([10, 10]);
+ expectResemble(
+ map, 'spec/ol/expected/rotate-canvas.png', IMAGE_TOLERANCE, done);
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ map.getView().setRotation(90);
+ map.getView().setCenter([10, 10]);
+ expectResemble(
+ map, 'spec/ol/expected/rotate-webgl.png', IMAGE_TOLERANCE, done);
+ });
+ });
+
+ describe('#zoom()', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ map.getView().setCenter([10, 10]);
+ map.getView().setResolution(2);
+ expectResemble(
+ map, 'spec/ol/expected/zoom-canvas.png', IMAGE_TOLERANCE, done);
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ map.getView().setCenter([10, 10]);
+ map.getView().setResolution(2);
+ expectResemble(
+ map, 'spec/ol/expected/zoom-webgl.png', IMAGE_TOLERANCE, done);
+ });
+ });
+});
+
+goog.require('goog.dispose');
+goog.require('ol.Feature');
+goog.require('ol.geom.Point');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');
diff --git a/test_rendering/spec/ol/style/circle.test.js b/test_rendering/spec/ol/style/circle.test.js
new file mode 100644
index 0000000000..72a53c1eaf
--- /dev/null
+++ b/test_rendering/spec/ol/style/circle.test.js
@@ -0,0 +1,201 @@
+goog.provide('ol.test.rendering.style.Circle');
+
+describe('ol.rendering.style.Circle', function() {
+
+ var target, map, vectorSource;
+
+ function createMap(renderer) {
+ target = createMapDiv(50, 50);
+
+ vectorSource = new ol.source.Vector();
+ vectorLayer = new ol.layer.Vector({
+ source: vectorSource
+ });
+
+ map = new ol.Map({
+ target: target,
+ renderer: renderer,
+ layers: [vectorLayer],
+ view: new ol.View({
+ projection: 'EPSG:4326',
+ center: [0, 0],
+ resolution: 1
+ })
+ });
+ return map;
+ }
+
+ describe('#render', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ function createFeatures() {
+ var feature;
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([-20, 18])
+ });
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 2,
+ fill: new ol.style.Fill({
+ color: '#91E339'
+ })
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([-10, 18])
+ });
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 4,
+ fill: new ol.style.Fill({
+ color: '#5447E6'
+ })
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([4, 18])
+ });
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 6,
+ fill: new ol.style.Fill({
+ color: '#92A8A6'
+ })
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([-20, 3])
+ });
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 2,
+ fill: new ol.style.Fill({
+ color: '#91E339'
+ }),
+ stroke: new ol.style.Stroke({
+ color: '#000000',
+ width: 1
+ })
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([-10, 3])
+ });
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 4,
+ fill: new ol.style.Fill({
+ color: '#5447E6'
+ }),
+ stroke: new ol.style.Stroke({
+ color: '#000000',
+ width: 2
+ })
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([4, 3])
+ });
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 6,
+ fill: new ol.style.Fill({
+ color: '#92A8A6'
+ }),
+ stroke: new ol.style.Stroke({
+ color: '#000000',
+ width: 3
+ })
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([-20, -15])
+ });
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 2,
+ stroke: new ol.style.Stroke({
+ color: '#256308',
+ width: 1
+ })
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([-10, -15])
+ });
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 4,
+ fill: new ol.style.Fill({
+ color: 'rgba(0, 0, 255, 0.3)'
+ }),
+ stroke: new ol.style.Stroke({
+ color: '#256308',
+ width: 2
+ })
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([4, -15])
+ });
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 6,
+ fill: new ol.style.Fill({
+ color: 'rgba(235, 45, 70, 0.6)'
+ }),
+ stroke: new ol.style.Stroke({
+ color: '#256308',
+ width: 3
+ })
+ })
+ }));
+ vectorSource.addFeature(feature);
+ }
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ createFeatures();
+ expectResemble(map, 'spec/ol/style/expected/circle-canvas.png',
+ 6.0, done);
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ createFeatures();
+ expectResemble(map, 'spec/ol/style/expected/circle-webgl.png',
+ 7.0, done);
+ });
+ });
+});
+
+goog.require('goog.dispose');
+goog.require('ol.Feature');
+goog.require('ol.geom.Point');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Circle');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Style');
+goog.require('ol.style.Stroke');
diff --git a/test_rendering/spec/ol/style/expected/circle-canvas.png b/test_rendering/spec/ol/style/expected/circle-canvas.png
new file mode 100644
index 0000000000..466b5281d1
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/circle-canvas.png differ
diff --git a/test_rendering/spec/ol/style/expected/circle-webgl.png b/test_rendering/spec/ol/style/expected/circle-webgl.png
new file mode 100644
index 0000000000..89c37e992e
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/circle-webgl.png differ
diff --git a/test_rendering/spec/ol/style/expected/icon-canvas.png b/test_rendering/spec/ol/style/expected/icon-canvas.png
new file mode 100644
index 0000000000..cdc0dba6b1
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/icon-canvas.png differ
diff --git a/test_rendering/spec/ol/style/expected/icon-webgl.png b/test_rendering/spec/ol/style/expected/icon-webgl.png
new file mode 100644
index 0000000000..c08cbfdd54
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/icon-webgl.png differ
diff --git a/test_rendering/spec/ol/style/expected/linestring-strokes-canvas.png b/test_rendering/spec/ol/style/expected/linestring-strokes-canvas.png
new file mode 100644
index 0000000000..0e354d444a
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/linestring-strokes-canvas.png differ
diff --git a/test_rendering/spec/ol/style/expected/polygon-fill-and-strokes-canvas.png b/test_rendering/spec/ol/style/expected/polygon-fill-and-strokes-canvas.png
new file mode 100644
index 0000000000..b1b5d9d4d0
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/polygon-fill-and-strokes-canvas.png differ
diff --git a/test_rendering/spec/ol/style/expected/polygon-types-canvas.png b/test_rendering/spec/ol/style/expected/polygon-types-canvas.png
new file mode 100644
index 0000000000..4abfccb058
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/polygon-types-canvas.png differ
diff --git a/test_rendering/spec/ol/style/expected/polygon-zindex-canvas.png b/test_rendering/spec/ol/style/expected/polygon-zindex-canvas.png
new file mode 100644
index 0000000000..931df4a072
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/polygon-zindex-canvas.png differ
diff --git a/test_rendering/spec/ol/style/expected/regularshape-canvas.png b/test_rendering/spec/ol/style/expected/regularshape-canvas.png
new file mode 100644
index 0000000000..75cb343754
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/regularshape-canvas.png differ
diff --git a/test_rendering/spec/ol/style/expected/regularshape-webgl.png b/test_rendering/spec/ol/style/expected/regularshape-webgl.png
new file mode 100644
index 0000000000..ae51d61dc5
Binary files /dev/null and b/test_rendering/spec/ol/style/expected/regularshape-webgl.png differ
diff --git a/test_rendering/spec/ol/style/icon.test.js b/test_rendering/spec/ol/style/icon.test.js
new file mode 100644
index 0000000000..d89bbd8a5f
--- /dev/null
+++ b/test_rendering/spec/ol/style/icon.test.js
@@ -0,0 +1,85 @@
+goog.provide('ol.test.rendering.style.Icon');
+
+describe('ol.rendering.style.Icon', function() {
+
+ var target, map, vectorSource;
+
+ function createMap(renderer) {
+ target = createMapDiv(50, 50);
+
+ vectorSource = new ol.source.Vector();
+ vectorLayer = new ol.layer.Vector({
+ source: vectorSource
+ });
+
+ map = new ol.Map({
+ target: target,
+ renderer: renderer,
+ layers: [vectorLayer],
+ view: new ol.View({
+ projection: 'EPSG:4326',
+ center: [0, 0],
+ resolution: 1
+ })
+ });
+ return map;
+ }
+
+ describe('#render', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ function createFeatures(callback) {
+ var feature;
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([0, 0])
+ });
+
+ var img = new Image();
+ img.onload = function() {
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.Icon(/** @type {olx.style.IconOptions} */ ({
+ anchor: [0.5, 46],
+ anchorXUnits: 'fraction',
+ anchorYUnits: 'pixels',
+ opacity: 0.75,
+ scale: 0.5,
+ img: img,
+ imgSize: [32, 48]
+ }))
+ }));
+ vectorSource.addFeature(feature);
+ callback();
+ };
+ img.src = 'spec/ol/data/icon.png';
+ }
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ createFeatures(function() {
+ expectResemble(map, 'spec/ol/style/expected/icon-canvas.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ createFeatures(function() {
+ expectResemble(map, 'spec/ol/style/expected/icon-webgl.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+ });
+});
+
+goog.require('goog.dispose');
+goog.require('ol.Feature');
+goog.require('ol.geom.Point');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Icon');
+goog.require('ol.style.Style');
diff --git a/test_rendering/spec/ol/style/linestring.test.js b/test_rendering/spec/ol/style/linestring.test.js
new file mode 100644
index 0000000000..bb8921f4a8
--- /dev/null
+++ b/test_rendering/spec/ol/style/linestring.test.js
@@ -0,0 +1,102 @@
+goog.provide('ol.test.rendering.style.LineString');
+
+describe('ol.rendering.style.LineString', function() {
+
+ var target, map, vectorSource;
+
+ function createMap(renderer) {
+ target = createMapDiv(50, 50);
+
+ vectorSource = new ol.source.Vector();
+ vectorLayer = new ol.layer.Vector({
+ source: vectorSource
+ });
+
+ map = new ol.Map({
+ target: target,
+ renderer: renderer,
+ layers: [vectorLayer],
+ view: new ol.View({
+ projection: 'EPSG:4326',
+ center: [0, 0],
+ resolution: 1
+ })
+ });
+ return map;
+ }
+
+ describe('different strokes', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ function createFeatures() {
+ var feature;
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.LineString(
+ [[-20, 20], [15, 20]]
+ )
+ });
+ feature.setStyle(new ol.style.Style({
+ stroke: new ol.style.Stroke({color: '#DE213A', width: 3})
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.LineString(
+ [[-20, 15], [15, 15]]
+ )
+ });
+ feature.setStyle(new ol.style.Style({
+ stroke: new ol.style.Stroke({color: '#9696EB', width: 1})
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.LineString(
+ [[-20, 10], [15, 10]]
+ )
+ });
+ feature.setStyle([new ol.style.Style({
+ stroke: new ol.style.Stroke({color: '#F2F211', width: 5})
+ }), new ol.style.Style({
+ stroke: new ol.style.Stroke({color: '#292921', width: 1})
+ })]);
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.LineString(
+ [[-20, -20], [-2, 0], [15, -20]]
+ )
+ });
+ feature.setStyle(new ol.style.Style({
+ stroke: new ol.style.Stroke({
+ color: '#000000',
+ width: 2,
+ lineCap: 'square',
+ lineJoin: 'round'
+ })
+ }));
+ vectorSource.addFeature(feature);
+ }
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ createFeatures();
+ expectResemble(
+ map, 'spec/ol/style/expected/linestring-strokes-canvas.png',
+ 3.0, done);
+ });
+ });
+});
+
+goog.require('goog.dispose');
+goog.require('ol.Feature');
+goog.require('ol.geom.LineString');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Style');
+goog.require('ol.style.Stroke');
diff --git a/test_rendering/spec/ol/style/polygon.test.js b/test_rendering/spec/ol/style/polygon.test.js
new file mode 100644
index 0000000000..29f7a64d2c
--- /dev/null
+++ b/test_rendering/spec/ol/style/polygon.test.js
@@ -0,0 +1,200 @@
+goog.provide('ol.test.rendering.style.Polygon');
+
+describe('ol.rendering.style.Polygon', function() {
+
+ var target, map, vectorSource;
+
+ function createMap(renderer) {
+ target = createMapDiv(50, 50);
+
+ vectorSource = new ol.source.Vector();
+ vectorLayer = new ol.layer.Vector({
+ source: vectorSource
+ });
+
+ map = new ol.Map({
+ target: target,
+ renderer: renderer,
+ layers: [vectorLayer],
+ view: new ol.View({
+ projection: 'EPSG:4326',
+ center: [0, 0],
+ resolution: 1
+ })
+ });
+ return map;
+ }
+
+ describe('different types', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ function createFeatures() {
+ var fill = new ol.style.Fill({color: 'red'});
+
+ var feature;
+ // rectangle
+ feature = new ol.Feature({
+ geometry: new ol.geom.Polygon([
+ [[-20, 10], [-20, 20], [-5, 20], [-5, 10], [-20, 10]]
+ ])
+ });
+ feature.setStyle(new ol.style.Style({
+ fill: fill
+ }));
+ vectorSource.addFeature(feature);
+
+ // rectangle with 1 hole
+ feature = new ol.Feature({
+ geometry: new ol.geom.Polygon([
+ [[0, 10], [0, 20], [15, 20], [15, 10], [0, 10]],
+ [[5, 13], [10, 13], [10, 17], [5, 17], [5, 13]]
+
+ ])
+ });
+ feature.setStyle(new ol.style.Style({
+ fill: fill
+ }));
+ vectorSource.addFeature(feature);
+
+ // rectangle with 2 holes
+ feature = new ol.Feature({
+ geometry: new ol.geom.Polygon([
+ [[-20, -20], [-20, 5], [15, 5], [15, -20], [-20, -20]],
+ [[-18, -18], [-12, -18], [-12, -12], [-18, -12], [-18, -18]],
+ [[5, -18], [12, -18], [12, -12], [5, -12], [5, -18]]
+
+ ])
+ });
+ feature.setStyle(new ol.style.Style({
+ fill: fill
+ }));
+ vectorSource.addFeature(feature);
+ }
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ createFeatures();
+ expectResemble(map, 'spec/ol/style/expected/polygon-types-canvas.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+
+ describe('z-index', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ function createFeatures() {
+ var feature;
+ // rectangle with z-index 2
+ feature = new ol.Feature({
+ geometry: new ol.geom.Polygon([
+ [[-20, 10], [-20, 20], [-0, 20], [-0, 10], [-20, 10]]
+ ])
+ });
+ feature.setStyle(new ol.style.Style({
+ fill: new ol.style.Fill({color: '#E31E10'}),
+ zIndex: 2
+ }));
+ vectorSource.addFeature(feature);
+
+ // rectangle with z-index 3
+ feature = new ol.Feature({
+ geometry: new ol.geom.Polygon([
+ [[-15, 5], [-15, 15], [5, 15], [5, 5], [-15, 5]]
+ ])
+ });
+ feature.setStyle(new ol.style.Style({
+ fill: new ol.style.Fill({color: '#1A5E42'}),
+ zIndex: 3
+ }));
+ vectorSource.addFeature(feature);
+
+ // rectangle with z-index 1
+ feature = new ol.Feature({
+ geometry: new ol.geom.Polygon([
+ [[-10, 0], [-10, 10], [10, 10], [10, 0], [-10, 0]]
+ ])
+ });
+ feature.setStyle(new ol.style.Style({
+ fill: new ol.style.Fill({color: '#DEDE21'}),
+ zIndex: 1
+ }));
+ vectorSource.addFeature(feature);
+
+ }
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ createFeatures();
+ expectResemble(map, 'spec/ol/style/expected/polygon-zindex-canvas.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+
+ describe('different fills and strokes', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ function createFeatures() {
+ var feature;
+ // rectangle
+ feature = new ol.Feature({
+ geometry: new ol.geom.Polygon([
+ [[-20, 10], [-20, 20], [-5, 20], [-5, 10], [-20, 10]]
+ ])
+ });
+ feature.setStyle(new ol.style.Style({
+ fill: new ol.style.Fill({color: '#9696EB'}),
+ stroke: new ol.style.Stroke({color: '#9696EB', width: 1})
+ }));
+ vectorSource.addFeature(feature);
+
+ // rectangle with 1 hole
+ feature = new ol.Feature({
+ geometry: new ol.geom.Polygon([
+ [[0, 10], [0, 20], [15, 20], [15, 10], [0, 10]]
+ ])
+ });
+ feature.setStyle(new ol.style.Style({
+ fill: new ol.style.Fill({color: 'rgba(255, 0, 0, 0.1)'}),
+ stroke: new ol.style.Stroke({color: '#DE213A', width: 3})
+ }));
+ vectorSource.addFeature(feature);
+
+ // rectangle with 2 holes
+ feature = new ol.Feature({
+ geometry: new ol.geom.Polygon([
+ [[-20, -20], [-20, 5], [15, 5], [15, -20], [-20, -20]]
+ ])
+ });
+ feature.setStyle(new ol.style.Style({
+ fill: new ol.style.Fill({color: 'rgba(18, 204, 105, 0.3)'}),
+ stroke: new ol.style.Stroke({color: '#032E17', width: 2})
+ }));
+ vectorSource.addFeature(feature);
+ }
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ createFeatures();
+ expectResemble(
+ map, 'spec/ol/style/expected/polygon-fill-and-strokes-canvas.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+});
+
+goog.require('goog.dispose');
+goog.require('ol.Feature');
+goog.require('ol.geom.Polygon');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Style');
+goog.require('ol.style.Stroke');
diff --git a/test_rendering/spec/ol/style/regularshape.test.js b/test_rendering/spec/ol/style/regularshape.test.js
new file mode 100644
index 0000000000..2eaca4b33e
--- /dev/null
+++ b/test_rendering/spec/ol/style/regularshape.test.js
@@ -0,0 +1,129 @@
+goog.provide('ol.test.rendering.style.RegularShape');
+
+describe('ol.rendering.style.RegularShape', function() {
+
+ var target, map, vectorSource;
+
+ function createMap(renderer) {
+ target = createMapDiv(50, 50);
+
+ vectorSource = new ol.source.Vector();
+ vectorLayer = new ol.layer.Vector({
+ source: vectorSource
+ });
+
+ map = new ol.Map({
+ target: target,
+ renderer: renderer,
+ layers: [vectorLayer],
+ view: new ol.View({
+ projection: 'EPSG:4326',
+ center: [0, 0],
+ resolution: 1
+ })
+ });
+ return map;
+ }
+
+ describe('#render', function() {
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ function createFeatures() {
+ var stroke = new ol.style.Stroke({color: 'black', width: 2});
+ var fill = new ol.style.Fill({color: 'red'});
+
+ var feature;
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([-15, 15])
+ });
+ // square
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.RegularShape({
+ fill: fill,
+ stroke: stroke,
+ points: 4,
+ radius: 10,
+ angle: Math.PI / 4
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([8, 15])
+ });
+ // triangle
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.RegularShape({
+ fill: fill,
+ stroke: stroke,
+ points: 3,
+ radius: 10,
+ rotation: Math.PI / 4,
+ angle: 0
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([-10, -8])
+ });
+ // star
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.RegularShape({
+ fill: fill,
+ stroke: stroke,
+ points: 5,
+ radius: 10,
+ radius2: 4,
+ angle: 0
+ })
+ }));
+ vectorSource.addFeature(feature);
+
+ feature = new ol.Feature({
+ geometry: new ol.geom.Point([12, -8])
+ });
+ // cross
+ feature.setStyle(new ol.style.Style({
+ image: new ol.style.RegularShape({
+ fill: fill,
+ stroke: stroke,
+ points: 4,
+ radius: 10,
+ radius2: 0,
+ angle: 0
+ })
+ }));
+ vectorSource.addFeature(feature);
+ }
+
+ it('tests the canvas renderer', function(done) {
+ map = createMap('canvas');
+ createFeatures();
+ expectResemble(map, 'spec/ol/style/expected/regularshape-canvas.png',
+ 6.0, done);
+ });
+
+ it('tests the WebGL renderer', function(done) {
+ assertWebGL();
+ map = createMap('webgl');
+ createFeatures();
+ expectResemble(map, 'spec/ol/style/expected/regularshape-webgl.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+});
+
+goog.require('goog.dispose');
+goog.require('ol.Feature');
+goog.require('ol.geom.Point');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Fill');
+goog.require('ol.style.RegularShape');
+goog.require('ol.style.Style');
+goog.require('ol.style.Stroke');
diff --git a/test_rendering/test.js b/test_rendering/test.js
new file mode 100644
index 0000000000..8343ab26c9
--- /dev/null
+++ b/test_rendering/test.js
@@ -0,0 +1,30 @@
+var url = phantom.args[0];
+var page = require("webpage").create();
+
+var v = slimer.geckoVersion;
+console.log('Gecko: ' + v.major + '.' + v.minor + '.' + v.patch);
+
+page.open(url).
+ then(function(status){
+ if (status == "success") {
+ page.onCallback = function(failedTests) {
+ if (failedTests.length > 0) {
+ for (var i = 0; i < failedTests.length; i++) {
+ var test = failedTests[i];
+ console.log(test.title);
+ console.error(test.errorStack);
+ console.log('');
+ }
+ console.error(failedTests.length + ' test(s) failed.');
+ } else {
+ console.log('All tests passed.');
+ }
+ page.close();
+ phantom.exit(failedTests.length === 0 ? 0 : 1);
+ }
+ } else {
+ console.error("The tests could not be started. Is the server running?");
+ page.close();
+ phantom.exit(1);
+ }
+ });