Add tests for annotation labels.
MODIFY
src/annotationLayer.js
src/annotationLayer.js
@@ -640,35 +640,35 @@ var annotationLayer = function (args) {
value = !!value && ['false', 'no', 'off'].indexOf(('' + value).toLowerCase()) < 0;
break;
case 'booleanOrNumber':
- if ((!value && value !== 0) || ['true', 'false', 'off', 'on', 'no', 'yes'].indexOf(('' + value).toLowerCase()) >= 0) {
+ if ((!value && value !== 0 && value !== '') || ['true', 'false', 'off', 'on', 'no', 'yes'].indexOf(('' + value).toLowerCase()) >= 0) {
value = !!value && ['false', 'no', 'off'].indexOf(('' + value).toLowerCase()) < 0;
} else {
- value = +value;
- if (!isFinite(value)) {
+ if (!util.isNonNullFinite(value)) {
return;
}
+ value = +value;
}
break;
case 'coordinate2':
if (value === '') {
break;
}
- if (value && isFinite(value.x) && isFinite(value.y)) {
+ if (value && util.isNonNullFinite(value.x) && util.isNonNullFinite(value.y)) {
value.x = +value.x;
value.y = +value.y;
break;
}
try { value = JSON.parse(value); } catch (err) { }
- if (value && isFinite(value.x) && isFinite(value.y)) {
+ if (value && util.isNonNullFinite(value.x) && util.isNonNullFinite(value.y)) {
value.x = +value.x;
value.y = +value.y;
break;
}
- if (Array.isArray(value) && isFinite(value[0]) && isFinite(value[1])) {
+ if (Array.isArray(value) && util.isNonNullFinite(value[0]) && util.isNonNullFinite(value[1])) {
value = {x: +value[0], y: +value[1]};
break;
}
- parts = /^\s*([-.0-9eE]+)\s*,?\s*([-.0-9eE]+)\s*$/.exec('' + value);
+ parts = /^\s*([-.0-9eE]+)(?:\s+|\s*,)\s*([-.0-9eE]+)\s*$/.exec('' + value);
if (!parts || !isFinite(parts[1]) || !isFinite(parts[2])) {
return;
}
@@ -681,19 +681,19 @@ var annotationLayer = function (args) {
}
break;
case 'number':
- value = +value;
if (!util.isNonNullFinite(value)) {
return;
}
+ value = +value;
break;
case 'numberOrBlank':
if (value === '') {
break;
}
- value = +value;
if (!util.isNonNullFinite(value)) {
return;
}
+ value = +value;
break;
case 'opacity':
if (value === undefined || value === null || value === '') {
@@ -835,12 +835,12 @@ var annotationLayer = function (args) {
*/
this._updateLabels = function (labels) {
if (!labels || !labels.length) {
- m_this.removeLabelFeature();
+ m_this._removeLabelFeature();
return m_this;
}
if (!m_labelFeature) {
var renderer = registry.rendererForFeatures(['text']);
- if (renderer !== m_this.renderer()) {
+ if (renderer !== m_this.renderer().api()) {
m_labelLayer = registry.createLayer('feature', m_this.map(), {renderer: renderer});
m_this.addChild(m_labelLayer);
m_labelLayer._update();
@@ -888,7 +888,7 @@ var annotationLayer = function (args) {
*
* @returns {this} The current layer.
*/
- this.removeLabelFeature = function () {
+ this._removeLabelFeature = function () {
if (m_labelLayer) {
m_labelLayer._exit();
m_this.removeChild(m_labelLayer);
@@ -945,7 +945,7 @@ var annotationLayer = function (args) {
* @returns {this} The current layer.
*/
this._exit = function () {
- m_this.removeLabelFeature();
+ m_this._removeLabelFeature();
// Call super class exit
s_exit.call(m_this);
m_annotations = [];

MODIFY
src/util/index.js
src/util/index.js
@@ -179,13 +179,13 @@ var util = module.exports = {
/**
* Check if a value coerces to a number that is finite, not a NaN, and not
- * `null` or `false`.
+ * `null`, `false`, or the empty string.
*
* @param {object} val The value to check.
* @returns {boolean} True if `val` is a non-null, non-false, finite number.
*/
isNonNullFinite: function (val) {
- return isFinite(val) && val !== null && val !== false;
+ return isFinite(val) && val !== null && val !== false && val !== '';
},
/**

MODIFY
tests/cases/annotation.js
tests/cases/annotation.js
@@ -35,6 +35,9 @@ describe('geo.annotation', function () {
expect(ann.state()).toBe(geo.annotation.state.done);
expect(ann.id()).toBeGreaterThan(0);
expect(ann.name()).toBe('Test ' + ann.id());
+ expect(ann.label()).toBe('Test ' + ann.id());
+ expect(ann.label(undefined, true)).toBe(null);
+ expect(ann.description()).toBe(undefined);
expect(ann.layer()).toBe(undefined);
expect(ann.features()).toEqual([]);
expect(ann.coordinates()).toEqual([]);
@@ -72,6 +75,26 @@ describe('geo.annotation', function () {
expect(ann.name('')).toBe(ann);
expect(ann.name()).toBe('New Name');
});
+ it('label', function () {
+ var ann = geo.annotation.annotation('test');
+ expect(ann.label()).toBe('Test ' + ann.id());
+ expect(ann.label(undefined, true)).toBe(null);
+ expect(ann.label('New Label')).toBe(ann);
+ expect(ann.label()).toBe('New Label');
+ expect(ann.label('')).toBe(ann);
+ expect(ann.label()).toBe('');
+ expect(ann.label(null)).toBe(ann);
+ expect(ann.label()).toBe('Test ' + ann.id());
+ expect(ann.label(undefined, true)).toBe(null);
+ });
+ it('description', function () {
+ var ann = geo.annotation.annotation('test');
+ expect(ann.description()).toBe(undefined);
+ expect(ann.description('New Description')).toBe(ann);
+ expect(ann.description()).toBe('New Description');
+ expect(ann.description('')).toBe(ann);
+ expect(ann.description()).toBe('');
+ });
it('layer', function () {
var ann = geo.annotation.annotation('test');
expect(ann.layer()).toBe(undefined);
@@ -103,10 +126,16 @@ describe('geo.annotation', function () {
expect(ann.options().testopt).toBe(40);
expect(ann.options({testopt: 30})).toBe(ann);
expect(ann.options().testopt).toBe(30);
- /* name and coordinates are handled specially */
+ /* name, label, description, and coordinates are handled specially */
ann.options('name', 'newname');
expect(ann.options().name).toBe(undefined);
expect(ann.name()).toBe('newname');
+ ann.options('label', 'newlabel');
+ expect(ann.options().label).toBe(undefined);
+ expect(ann.label()).toBe('newlabel');
+ ann.options('description', 'newdescription');
+ expect(ann.options().description).toBe(undefined);
+ expect(ann.description()).toBe('newdescription');
var coord = null, testval = [[1, 2], [3, 4]];
ann._coordinates = function (arg) {
if (arg !== undefined) {
@@ -168,6 +197,9 @@ describe('geo.annotation', function () {
var ann = geo.annotation.annotation('test', {
layer: layer,
style: {fillColor: 'red'},
+ labelStyle: {color: 'blue', textStrokeColor: 'rgba(0, 255, 0, 0.5)'},
+ label: 'testLabel',
+ description: 'testDescription',
name: 'testAnnotation'
});
expect(ann.geojson()).toBe(undefined);
@@ -188,6 +220,11 @@ describe('geo.annotation', function () {
expect(geojson.geometry.coordinates[1]).toBeCloseTo(42.849775);
expect(geojson.properties.name).toBe('testAnnotation');
expect(geojson.properties.fillColor).toBe('#ff0000');
+ expect(geojson.properties.description).toBe('testDescription');
+ expect(geojson.properties.label).toBe('testLabel');
+ expect(geojson.properties.labelColor).toBe('#0000ff');
+ expect(geojson.properties.labelTextStrokeColor).toBe('#00ff0080');
+ expect(geojson.properties.showLabel).toBe(undefined);
expect(geojson.crs).toBe(undefined);
geojson = ann.geojson('EPSG:3857');
expect(geojson.geometry.coordinates[1]).toBeCloseTo(5289134.103576);
@@ -196,6 +233,45 @@ describe('geo.annotation', function () {
expect(geojson.crs.properties.name).toBe('EPSG:3857');
geojson = ann.geojson(undefined, true);
expect(geojson.crs.properties.name).toBe('EPSG:4326');
+ ann.options('showLabel', false);
+ geojson = ann.geojson(undefined, true);
+ expect(geojson.properties.showLabel).toBe(false);
+ });
+ it('_labelPosition', function () {
+ var ann = geo.annotation.annotation('test', {
+ layer: layer,
+ name: 'testAnnotation'
+ });
+ ann._coordinates = function () {
+ };
+ expect(ann._labelPosition()).toBe(undefined);
+ ann._coordinates = function () {
+ return [{x: 1, y: 2}];
+ };
+ expect(ann._labelPosition()).toEqual({x: 1, y: 2});
+ ann._coordinates = function () {
+ return [{x: 1, y: 2}, {x: 3, y: 5}, {x: 8, y: 11}];
+ };
+ expect(ann._labelPosition()).toEqual({x: 4, y: 6});
+ });
+ it('labelRecord', function () {
+ var ann = geo.annotation.annotation('test', {
+ layer: layer,
+ name: 'testAnnotation'
+ });
+ ann._coordinates = function () {
+ };
+ expect(ann.labelRecord()).toBe(undefined);
+ ann._coordinates = function () {
+ return [{x: 1, y: 2}];
+ };
+ expect(ann.labelRecord().text).toBe('testAnnotation');
+ expect(ann.labelRecord().position).toEqual({x: 1, y: 2});
+ expect(ann.labelRecord().style).toBe(undefined);
+ ann.options('labelStyle', {opacity: 0.8});
+ expect(ann.labelRecord().style.opacity).toBe(0.8);
+ ann.options('showLabel', false);
+ expect(ann.labelRecord()).toBe(undefined);
});
});

MODIFY
tests/cases/annotationLayer.js
tests/cases/annotationLayer.js
@@ -238,33 +238,91 @@ describe('geo.annotationLayer', function () {
it('validateAttribute', function () {
expect(layer.validateAttribute(undefined, 'other')).toBe(undefined);
expect(layer.validateAttribute(null, 'other')).toBe(undefined);
-
expect(layer.validateAttribute('value', 'other')).toBe('value');
+
+ expect(layer.validateAttribute(0.5, 'angle')).toBe(0.5);
+ expect(layer.validateAttribute('0.5', 'angle')).toBe(0.5);
+ expect(layer.validateAttribute('', 'angle')).toBe('');
+ expect(layer.validateAttribute(-1, 'angle')).toBe(-1);
+ expect(layer.validateAttribute('value', 'angle')).toBe(undefined);
+ expect(layer.validateAttribute('90deg ', 'angle')).toBeCloseTo(Math.PI / 2);
+ expect(layer.validateAttribute(' 100grad', 'angle')).toBeCloseTo(Math.PI / 2);
+ expect(layer.validateAttribute('0.25 turn', 'angle')).toBeCloseTo(Math.PI / 2);
+ expect(layer.validateAttribute('1.57079rad', 'angle')).toBeCloseTo(Math.PI / 2);
+ expect(layer.validateAttribute('1.57079', 'angle')).toBeCloseTo(Math.PI / 2);
+
expect(layer.validateAttribute('value', 'boolean')).toBe(true);
expect(layer.validateAttribute(true, 'boolean')).toBe(true);
expect(layer.validateAttribute(0, 'boolean')).toBe(false);
expect(layer.validateAttribute('false', 'boolean')).toBe(false);
+ expect(layer.validateAttribute(true, 'booleanOrNumber')).toBe(true);
+ expect(layer.validateAttribute('false', 'booleanOrNumber')).toBe(false);
+ expect(layer.validateAttribute(0.5, 'booleanOrNumber')).toBe(0.5);
+ expect(layer.validateAttribute('0.5', 'booleanOrNumber')).toBe(0.5);
+ expect(layer.validateAttribute(0, 'booleanOrNumber')).toBe(0);
+ expect(layer.validateAttribute(-1, 'booleanOrNumber')).toBe(-1);
+ expect(layer.validateAttribute(1.2, 'booleanOrNumber')).toBe(1.2);
+ expect(layer.validateAttribute('', 'booleanOrNumber')).toBe(undefined);
+ expect(layer.validateAttribute('value', 'booleanOrNumber')).toBe(undefined);
+
expect(layer.validateAttribute('not a color', 'color')).toBe(undefined);
expect(layer.validateAttribute('yellow', 'color')).toEqual({r: 1, g: 1, b: 0});
expect(layer.validateAttribute('#ffff00', 'color')).toEqual({r: 1, g: 1, b: 0});
expect(layer.validateAttribute('#ff0', 'color')).toEqual({r: 1, g: 1, b: 0});
expect(layer.validateAttribute({r: 1, g: 1, b: 0}, 'color')).toEqual({r: 1, g: 1, b: 0});
+ expect(layer.validateAttribute('', 'coordinate2')).toBe('');
+ expect(layer.validateAttribute(0.5, 'coordinate2')).toBe(undefined);
+ expect(layer.validateAttribute({x: 1, y: 2}, 'coordinate2')).toEqual({x: 1, y: 2});
+ expect(layer.validateAttribute({x: 1, y: null}, 'coordinate2')).toEqual(undefined);
+ expect(layer.validateAttribute('{"x": 1, "y": 2}', 'coordinate2')).toEqual({x: 1, y: 2});
+ expect(layer.validateAttribute('{"x": 1, "y": "value"}', 'coordinate2')).toEqual(undefined);
+ expect(layer.validateAttribute([1, 2], 'coordinate2')).toEqual({x: 1, y: 2});
+ expect(layer.validateAttribute([1, 2, 3], 'coordinate2')).toEqual({x: 1, y: 2});
+ expect(layer.validateAttribute('1, 2', 'coordinate2')).toEqual({x: 1, y: 2});
+ expect(layer.validateAttribute('1 2', 'coordinate2')).toEqual({x: 1, y: 2});
+ expect(layer.validateAttribute(' 1 , 2 ', 'coordinate2')).toEqual({x: 1, y: 2});
+ expect(layer.validateAttribute('1 value', 'coordinate2')).toEqual(undefined);
+
expect(layer.validateAttribute(0.5, 'opacity')).toBe(0.5);
expect(layer.validateAttribute('0.5', 'opacity')).toBe(0.5);
expect(layer.validateAttribute(0, 'opacity')).toBe(0);
expect(layer.validateAttribute(-1, 'opacity')).toBe(undefined);
expect(layer.validateAttribute(1, 'opacity')).toBe(1);
expect(layer.validateAttribute(1.2, 'opacity')).toBe(undefined);
+ expect(layer.validateAttribute('', 'opacity')).toBe(undefined);
expect(layer.validateAttribute('value', 'opacity')).toBe(undefined);
+ expect(layer.validateAttribute(0.5, 'number')).toBe(0.5);
+ expect(layer.validateAttribute('0.5', 'number')).toBe(0.5);
+ expect(layer.validateAttribute(0, 'number')).toBe(0);
+ expect(layer.validateAttribute(-1, 'number')).toBe(-1);
+ expect(layer.validateAttribute(1.2, 'number')).toBe(1.2);
+ expect(layer.validateAttribute('1.2e2', 'number')).toBe(120);
+ expect(layer.validateAttribute('1.2e-2', 'number')).toBe(0.012);
+ expect(layer.validateAttribute('', 'number')).toBe(undefined);
+ expect(layer.validateAttribute('value', 'number')).toBe(undefined);
+
+ expect(layer.validateAttribute(0.5, 'numberOrBlank')).toBe(0.5);
+ expect(layer.validateAttribute('0.5', 'numberOrBlank')).toBe(0.5);
+ expect(layer.validateAttribute(0, 'numberOrBlank')).toBe(0);
+ expect(layer.validateAttribute(-1, 'numberOrBlank')).toBe(-1);
+ expect(layer.validateAttribute(1.2, 'numberOrBlank')).toBe(1.2);
+ expect(layer.validateAttribute('1.2e2', 'numberOrBlank')).toBe(120);
+ expect(layer.validateAttribute('1.2e-2', 'numberOrBlank')).toBe(0.012);
+ expect(layer.validateAttribute('', 'numberOrBlank')).toBe('');
+ expect(layer.validateAttribute('value', 'numberOrBlank')).toBe(undefined);
+
expect(layer.validateAttribute(0.5, 'positive')).toBe(0.5);
expect(layer.validateAttribute('0.5', 'positive')).toBe(0.5);
expect(layer.validateAttribute(0, 'positive')).toBe(undefined);
expect(layer.validateAttribute(-1, 'positive')).toBe(undefined);
expect(layer.validateAttribute(1.2, 'positive')).toBe(1.2);
expect(layer.validateAttribute('value', 'positive')).toBe(undefined);
+
+ expect(layer.validateAttribute(0.5, 'text')).toBe('0.5');
+ expect(layer.validateAttribute('value', 'text')).toBe('value');
});
});
describe('Private utility functions', function () {
@@ -278,7 +336,8 @@ describe('geo.annotationLayer', function () {
});
point = geo.annotation.pointAnnotation({
layer: layer,
- position: {x: 2, y: 3}});
+ position: {x: 2, y: 3},
+ labelStyle: {opacity: 0.8}});
rect = geo.annotation.rectangleAnnotation({
layer: layer,
corners: [{x: 0, y: 0}, {x: 1, y: 0}, {x: 1, y: 1}, {x: 0, y: 1}]});
@@ -290,6 +349,36 @@ describe('geo.annotationLayer', function () {
layer.addAnnotation(rect2);
expect(layer.features.length).toBe(1);
});
+ it('_updateLabels and _removeLabelFeature', function () {
+ var numChild, canvasLayer, canvasLine;
+ var labels = [point.labelRecord()];
+ /* calling _updateLabels with no argument or an empty list implicitly
+ * calls _removeLabelFeature. The call to _updateLabels with a list will
+ * add it back. */
+ layer._updateLabels([]);
+ numChild = layer.children().length;
+ layer._updateLabels(labels);
+ expect(layer.children().length).toBe(numChild + 1);
+ expect(layer.children()[layer.children().length - 1] instanceof geo.featureLayer);
+ layer._updateLabels(); // implicitly calls _removeLabelFeature
+ expect(layer.children().length).toBe(numChild);
+ // canvas layers shouldn't add a sublayer, but instead a feature
+ canvasLayer = map.createLayer('annotation', {
+ renderer: 'canvas'
+ });
+ canvasLine = geo.annotation.lineAnnotation({
+ layer: layer,
+ vertices: [{x: 0, y: 0}, {x: 1, y: 0}, {x: 1, y: 1}, {x: 0, y: 1}]});
+ canvasLayer.addAnnotation(canvasLine);
+ canvasLayer._updateLabels([]);
+ numChild = canvasLayer.children().length;
+ canvasLayer._updateLabels(labels);
+ expect(layer.children()[layer.children().length - 1] instanceof geo.textFeature);
+ expect(canvasLayer.children().length).toBe(numChild + 1);
+ canvasLayer._updateLabels();
+ expect(canvasLayer.children().length).toBe(numChild);
+ map.deleteLayer(canvasLayer);
+ });
it('_handleMouseClick', function () {
layer.removeAllAnnotations();
layer.mode('polygon');

Add tests for annotation labels.