From bff3e88343454d88e4719f6e1ee2cca09c481698 Mon Sep 17 00:00:00 2001 From: Kai Volland Date: Fri, 22 Mar 2019 11:15:55 +0100 Subject: [PATCH] Replaces clip.test.js with new tests Transforms the old rendering test for layer clipping to the new rendering test approach. --- rendering/cases/layer-clipping/expected.png | Bin 0 -> 6740 bytes rendering/cases/layer-clipping/main.js | 61 ++++++++++ test/rendering/ol/layer/clip.test.js | 110 ------------------ .../ol/layer/expected/multipolygon-clip.png | Bin 5651 -> 0 bytes 4 files changed, 61 insertions(+), 110 deletions(-) create mode 100644 rendering/cases/layer-clipping/expected.png create mode 100644 rendering/cases/layer-clipping/main.js delete mode 100644 test/rendering/ol/layer/clip.test.js delete mode 100644 test/rendering/ol/layer/expected/multipolygon-clip.png diff --git a/rendering/cases/layer-clipping/expected.png b/rendering/cases/layer-clipping/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..2ecebf636f9a2a580b159b5c9651bc086f3dec2f GIT binary patch literal 6740 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 literal 0 HcmV?d00001 diff --git a/rendering/cases/layer-clipping/main.js b/rendering/cases/layer-clipping/main.js new file mode 100644 index 0000000000..5b220936fe --- /dev/null +++ b/rendering/cases/layer-clipping/main.js @@ -0,0 +1,61 @@ +import Map from '../../../src/ol/Map.js'; +import View from '../../../src/ol/View.js'; +import XYZ from '../../../src/ol/source/XYZ.js'; +import TileLayer from '../../../src/ol/layer/Tile.js'; +import MultiPolygon from '../../../src/ol/geom/MultiPolygon.js'; +import Style from '../../../src/ol/style/Style.js'; +import Stroke from '../../../src/ol/style/Stroke.js'; +import {getVectorContext} from '../../../src/ol/render.js'; + +const source = new XYZ({ + url: '/data/tiles/osm/{z}/{x}/{y}.png', + transition: 0 +}); + +const layer = new TileLayer({ + source: source +}); + +const geometry = new MultiPolygon([ + [[[-80, -40], [-40, 0], [-80, 40], [-120, 0], [-80, -40]]], + [[[80, -40], [120, 0], [80, 40], [40, 0], [80, -40]]] +]).transform('EPSG:4326', 'EPSG:3857'); + +const style = new Style({ + stroke: new Stroke({ + width: 2, + color: 'blue' + }) +}); + +layer.on('prerender', function(event) { + const context = event.context; + context.save(); + + const vectorContext = getVectorContext(event); + vectorContext.setStyle(style); + vectorContext.drawGeometry(geometry); + + context.clip(); +}); + +layer.on('postrender', function(event) { + const context = event.context; + context.restore(); + + const vectorContext = getVectorContext(event); + vectorContext.setStyle(style); + vectorContext.drawGeometry(geometry); +}); + +new Map({ + pixelRatio: 1, + target: 'map', + layers: [layer], + view: new View({ + center: [0, 0], + zoom: 0 + }) +}); + +render(); diff --git a/test/rendering/ol/layer/clip.test.js b/test/rendering/ol/layer/clip.test.js deleted file mode 100644 index 567543a7e6..0000000000 --- a/test/rendering/ol/layer/clip.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import Map from '../../../../src/ol/Map.js'; -import View from '../../../../src/ol/View.js'; -import MultiPolygon from '../../../../src/ol/geom/MultiPolygon.js'; -import TileLayer from '../../../../src/ol/layer/Tile.js'; -import XYZ from '../../../../src/ol/source/XYZ.js'; -import Stroke from '../../../../src/ol/style/Stroke.js'; -import Style from '../../../../src/ol/style/Style.js'; - - -describe('layer clipping', function() { - - function onLoad(source, callback) { - let loading = 0; - let loaded = 0; - let called = false; - - function check() { - if (!called && loading > 0 && loaded === loading) { - callback(); - } - } - - source.on('tileloadstart', function() { - ++loading; - }); - source.on('tileloadend', function() { - ++loaded; - setTimeout(check, 10); - }); - source.on('tileloaderror', function() { - callback(new Error('Tile loading failed')); - called = true; - }); - } - - - describe('MultiPolygon clipping', function() { - - let map = null; - beforeEach(function() { - map = new Map({ - pixelRatio: 1, - target: createMapDiv(256, 256), - view: new View({ - center: [0, 0], - zoom: 0 - }) - }); - }); - - afterEach(function() { - disposeMap(map); - map = null; - }); - - it('clips to all parts of the MultiPolygon', function(done) { - - const source = new XYZ({ - url: 'rendering/ol/data/tiles/osm/{z}/{x}/{y}.png', - transition: 0 - }); - - const layer = new TileLayer({ - source: source - }); - - const geometry = new MultiPolygon([ - [[[-80, -40], [-40, 0], [-80, 40], [-120, 0], [-80, -40]]], - [[[80, -40], [120, 0], [80, 40], [40, 0], [80, -40]]] - ]).transform('EPSG:4326', 'EPSG:3857'); - - const style = new Style({ - stroke: new Stroke({ - width: 2, - color: 'blue' - }) - }); - - layer.on('precompose', function(event) { - const context = event.context; - context.save(); - - const vectorContext = event.vectorContext; - vectorContext.setStyle(style); - vectorContext.drawGeometry(geometry); - - context.clip(); - }); - - layer.on('postcompose', function(event) { - const context = event.context; - context.restore(); - - const vectorContext = event.vectorContext; - vectorContext.setStyle(style); - vectorContext.drawGeometry(geometry); - }); - - onLoad(source, function(err) { - if (err) { - return done(err); - } - expectResemble(map, 'rendering/ol/layer/expected/multipolygon-clip.png', IMAGE_TOLERANCE, done); - }); - - map.addLayer(layer); - - }); - }); -}); diff --git a/test/rendering/ol/layer/expected/multipolygon-clip.png b/test/rendering/ol/layer/expected/multipolygon-clip.png deleted file mode 100644 index 709fd55a09b35e3419c9aad7224eb3f87fff4eb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5651 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 H@QD8(3DMyB