mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 05:30:35 +02:00
add tests for mapcss checks
ref #remote-presets
This commit is contained in:
+9
-10
@@ -5,6 +5,7 @@ import _find from 'lodash-es/find';
|
||||
import _forOwn from 'lodash-es/forOwn';
|
||||
import _isObject from 'lodash-es/isObject';
|
||||
import _isString from 'lodash-es/isString';
|
||||
import _map from 'lodash-es/map';
|
||||
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
|
||||
@@ -15,8 +16,6 @@ import {
|
||||
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import mapcssParse from 'mapcss-parse/source/index';
|
||||
|
||||
import {
|
||||
t,
|
||||
currentLocale,
|
||||
@@ -456,14 +455,14 @@ export function coreContext() {
|
||||
locale = locale.split('-')[0];
|
||||
}
|
||||
|
||||
if (utilExternalValidationRules()) {
|
||||
var validationsUrl = utilStringQs(window.location.hash)['validations'];
|
||||
d3_text(validationsUrl, function (err, mapcss) {
|
||||
if (err) return;
|
||||
var validations = _map(mapcssParse(mapcss), function(mapcssConfig) { return utilMapCSSRule(mapcsS) });
|
||||
context.validationRules = function() { return validations; };
|
||||
});
|
||||
}
|
||||
// if (utilExternalValidationRules()) {
|
||||
// var validationsUrl = utilStringQs(window.location.hash).validations;
|
||||
// d3_text(validationsUrl, function (err, mapcss) {
|
||||
// if (err) return;
|
||||
// var validations = _map(mapcssParse(mapcss), function(mapcssConfig) { return utilMapCSSRule(mapcssConfig); });
|
||||
// context.validationRules = function() { return validations; };
|
||||
// });
|
||||
// }
|
||||
|
||||
history = coreHistory(context);
|
||||
context.graph = history.graph;
|
||||
|
||||
@@ -284,7 +284,7 @@ export function coreHistory(context) {
|
||||
return _flatten(
|
||||
_map(Validations, function(fn) {
|
||||
var warnings;
|
||||
if (fn === Validations.validationMapCSSChecks) {
|
||||
if (fn === Validations.validationMapCSSChecks && context.hasOwnProperty('validationRules')) {
|
||||
warnings = fn()(changes, _stack[_index].graph, context.validationRules());
|
||||
} else {
|
||||
warnings = fn()(changes, _stack[_index].graph);
|
||||
|
||||
@@ -183,7 +183,7 @@ export function presetIndex() {
|
||||
};
|
||||
|
||||
all.fromExternal = function() {
|
||||
var presetsUrl = utilStringQs(window.location.hash)['presets'];
|
||||
var presetsUrl = utilStringQs(window.location.hash).presets;
|
||||
d3_json(presetsUrl, function(err, presets) {
|
||||
if (err) all.init();
|
||||
all.overwrite(presets);
|
||||
@@ -213,7 +213,7 @@ export function presetIndex() {
|
||||
all.defaults = function(geometry, n) {
|
||||
var rec = _recent.matchGeometry(geometry).collection.slice(0, 4);
|
||||
var def = _uniq(rec.concat(_defaults[geometry].collection)).slice(0, n - 1);
|
||||
var fin = _uniq(rec.concat(def).concat(all.item(geometry))).filter(i => i !== undefined);
|
||||
var fin = _uniq(rec.concat(def).concat(all.item(geometry))).filter(function(d) { return d !== undefined; });
|
||||
return presetCollection(fin);
|
||||
};
|
||||
|
||||
|
||||
@@ -1 +1,85 @@
|
||||
export function mapcssRule() {}
|
||||
import _isMatch from 'lodash-es/isMatch';
|
||||
|
||||
export function utilMapCSSRule(selector) {
|
||||
var ruleChecks = {
|
||||
equals: function (tags) {
|
||||
return _isMatch(tags, selector.equals);
|
||||
},
|
||||
notEquals: function (tags) {
|
||||
return !_isMatch(tags, selector.notEquals);
|
||||
},
|
||||
absence: function(tags) {
|
||||
return Object.keys(tags).indexOf(selector.absence) === -1;
|
||||
},
|
||||
presence: function(tags) {
|
||||
return Object.keys(tags).indexOf(selector.presence) > -1;
|
||||
},
|
||||
greaterThan: function(tags) {
|
||||
var key = Object.keys(selector.greaterThan)[0];
|
||||
var value = selector.greaterThan[key];
|
||||
return tags[key] > value;
|
||||
},
|
||||
greaterThanEqual: function(tags) {
|
||||
var key = Object.keys(selector.greaterThanEqual)[0];
|
||||
var value = selector.greaterThanEqual[key];
|
||||
return tags[key] >= value;
|
||||
},
|
||||
lessThan: function(tags) {
|
||||
var key = Object.keys(selector.lessThan)[0];
|
||||
var value = selector.lessThan[key];
|
||||
return tags[key] < value;
|
||||
},
|
||||
lessThanEqual: function(tags) {
|
||||
var key = Object.keys(selector.lessThanEqual)[0];
|
||||
var value = selector.lessThanEqual[key];
|
||||
return tags[key] <= value;
|
||||
},
|
||||
positiveRegex: function(tags) {
|
||||
var tagKey = Object.keys(selector.positiveRegex)[0];
|
||||
var expression = selector.positiveRegex[tagKey].join('|');
|
||||
var regex = new RegExp(expression);
|
||||
return regex.test(tags[tagKey]);
|
||||
},
|
||||
negativeRegex: function(tags) {
|
||||
var tagKey = Object.keys(selector.negativeRegex)[0];
|
||||
var expression = selector.negativeRegex[tagKey].join('|');
|
||||
var regex = new RegExp(expression);
|
||||
return !regex.test(tags[tagKey]);
|
||||
}
|
||||
};
|
||||
|
||||
var rule = {
|
||||
ruleChecks: ruleChecks,
|
||||
type: Object.keys(selector).indexOf('error') > -1 ? 'error' : 'warning',
|
||||
buildChecks: function() {
|
||||
return Object.keys(selector)
|
||||
.filter(function(key) { return key !== 'geometry' && key !== 'error' && key !== 'warning'; })
|
||||
.map(function(key) { return ruleChecks[key]; });
|
||||
|
||||
},
|
||||
matches: function(entity) {
|
||||
return this.buildChecks().every(function(check) { return check(entity.tags); });
|
||||
|
||||
},
|
||||
geometryMatches: function(entity, graph) {
|
||||
if (entity.type === 'node' || entity.type === 'relation') {
|
||||
return selector.geometry === entity.type;
|
||||
} else if (entity.type === 'way') {
|
||||
return selector.geometry === entity.geometry(graph);
|
||||
}
|
||||
|
||||
},
|
||||
findWarnings: function (entity, graph, warnings) {
|
||||
if (this.geometryMatches(entity, graph) && this.matches(entity)) {
|
||||
warnings.push({
|
||||
id: 'mapcss_' + rule.type,
|
||||
message: selector[rule.type],
|
||||
entity: entity
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// import { t } from '../util/locale';
|
||||
|
||||
export function validationMapCSSChecks() {
|
||||
var validation = function(changes, graph, rules) {
|
||||
var warnings = [];
|
||||
@@ -10,7 +8,7 @@ export function validationMapCSSChecks() {
|
||||
var entity = entities[i];
|
||||
for (var k = 0; k < rules.length; k++) {
|
||||
var rule = rules[k];
|
||||
rules.findWarnings(entity, rules);
|
||||
rule.findWarnings(entity, graph, warnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
describe('iD.utilMapCSSRule', function() {
|
||||
var entities = [
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'marketplace' }}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'water_tap' }}),
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'marketplace', height: 0 }}),
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'school', height: 5, width: 3 }}),
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'healthcare' }}),
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'place_of_worship' }}),
|
||||
];
|
||||
var selectors = [
|
||||
{
|
||||
'geometry':'node',
|
||||
'equals':{'amenity':'marketplace'},
|
||||
'absence':'name',
|
||||
'warning':'throwWarning: "[amenity=marketplace]: MapRules preset \'Market\': must be coupled with name";'
|
||||
},
|
||||
{
|
||||
'geometry':'node',
|
||||
'equals':{'man_made':'water_tap'},
|
||||
'absence':'name',
|
||||
'warning':'throwWarning: "[amenity=drinking_water][man_made=water_tap]: MapRules preset \'Water Tap\': must be coupled with name";'
|
||||
},
|
||||
{
|
||||
'geometry':'node',
|
||||
'equals':{'amenity':'marketplace'},
|
||||
'presence':'height',
|
||||
'lessThanEqual': { 'height': 0 },
|
||||
'warning':'throwWarning: "[amenity=marketplace]: height must be greater than 0";'
|
||||
},
|
||||
{
|
||||
'geometry': 'node',
|
||||
'equals': {'amenity': 'school'},
|
||||
'greaterThan': { 'height': 0 },
|
||||
'greaterThanEqual': { 'width': 1 },
|
||||
'lessThanEqual': { 'width': 10 },
|
||||
'lessThan': { 'height': 10 },
|
||||
'warning': 'this is the warning!'
|
||||
},
|
||||
{
|
||||
'geometry': 'node',
|
||||
'presence': 'amenity',
|
||||
'positiveRegex': { amenity: ['^school$', '^healthcare$'] },
|
||||
'error': 'amenity cannot be healthcare or school!'
|
||||
},
|
||||
{
|
||||
'geometry': 'node',
|
||||
'presence': 'amenity',
|
||||
'negativeRegex': { amenity: ['^school$', '^healthcare$'] },
|
||||
'error': 'amenity must be healtcare or school!'
|
||||
}
|
||||
];
|
||||
var rules = selectors.map(function(s) { return iD.utilMapCSSRule(s); });
|
||||
it ('turns selector object in mapcssRule', function () {
|
||||
var ruleKeys = ['ruleChecks', 'type','buildChecks','matches','geometryMatches','findWarnings'];
|
||||
rules.forEach(function(rule) {
|
||||
expect(Object.keys(rule)).to.eql(ruleKeys);
|
||||
});
|
||||
});
|
||||
describe('#type', function() {
|
||||
it('is either error or warning', function() {
|
||||
selectors.forEach(function(s) {
|
||||
expect(['error', 'warning'].indexOf(iD.utilMapCSSRule(s).type)).to.be.greaterThan(-1);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#geometryMatches', function() {
|
||||
it('determines if entity and rule geometries match', function() {
|
||||
var node = iD.Entity({ type: 'node'});
|
||||
var way = iD.Entity({ type: 'way'});
|
||||
var graph = iD.Graph([node, way]);
|
||||
rules.forEach(function(rule) {
|
||||
expect(rule.geometryMatches(node, graph)).to.be.true;
|
||||
expect(rule.geometryMatches(way, graph)).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#buildsChecks', function() {
|
||||
it('builds array of MapCSS rule check functions to run entities against', function() {
|
||||
rules.forEach(function(rule) {
|
||||
expect(rule.buildChecks().every(function(fn) { return fn instanceof Function; })).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#matches', function() {
|
||||
it('determines if an entity matches the MapCSS rule checks', function() {
|
||||
var node = iD.Entity({ type: 'node', tags: { power: 'tower' }});
|
||||
rules.forEach(function(rule, i) {
|
||||
expect(rule.matches(entities[i])).to.be.true;
|
||||
expect(rule.matches(node)).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#ruleChecks', function() {
|
||||
describe('equals', function() {
|
||||
it('is true when entity.tags intersects selector.equals', function() {
|
||||
var pseudoSelector = { equals: {'amenity': 'school'} };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var school = iD.Entity({ type: 'node', tags: { amenity: 'school' }});
|
||||
expect(pseudoRule.ruleChecks.equals(school.tags)).to.be.true;
|
||||
});
|
||||
it('is false when entity.tags intersects selector.equals', function() {
|
||||
var pseudoSelector = { equals: { 'man_made': 'water_tap'} };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var school = iD.Entity({ type: 'node', tags: { amenity: 'school' } } );
|
||||
expect(pseudoRule.ruleChecks.equals(school.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('notEquals', function() {
|
||||
it('is true when entity.tags does not intersect selector.notEquals', function() {
|
||||
var pseudoSelector = { notEquals: { 'man_made': 'water_tap'} };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var school = iD.Entity({ type: 'node', tags: { amenity: 'school' } } );
|
||||
expect(pseudoRule.ruleChecks.notEquals(school.tags)).to.be.true;
|
||||
});
|
||||
it('is false when entity.tags does not intersect selector.notEquals', function() {
|
||||
var pseudoSelector = { notEquals: { 'amenity': 'school'} };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var school = iD.Entity({ type: 'node', tags: { amenity: 'school' } } );
|
||||
expect(pseudoRule.ruleChecks.notEquals(school.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('presence', function() {
|
||||
it('is true when entity.tags\' key s include selector.presence', function() {
|
||||
var pseudoSelector = { presence: 'name' };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var kHouse = iD.Entity({ type: 'node', tags: { amenity: 'marketplace', name: 'Kensington Square' }});
|
||||
expect(pseudoRule.ruleChecks.presence(kHouse.tags)).to.be.true;
|
||||
});
|
||||
it('is false when entity tags\' keys do not include selector.presence', function() {
|
||||
var pseudoSelector = { presence: 'name' };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var notKHouse = iD.Entity({ type: 'node', tags: { amenity: 'marketplace' }});
|
||||
expect(pseudoRule.ruleChecks.presence(notKHouse.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('absence', function() {
|
||||
it('is true when entity.tags\' keys do not include selector.absence', function() {
|
||||
var pseudoSelector = { absence: 'name' };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var notKHouse = iD.Entity({ type: 'node', tags: { amenity: 'marketplace' }});
|
||||
expect(pseudoRule.ruleChecks.absence(notKHouse.tags)).to.be.true;
|
||||
});
|
||||
it('is false when entity.tags\' keys include selector.absence', function() {
|
||||
var pseudoSelector = { absence: 'name' };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var kHouse = iD.Entity({ type: 'node', tags: { amenity: 'marketplace', name: 'Kensington Square' }});
|
||||
expect(pseudoRule.ruleChecks.presence(kHouse.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('greaterThan', function() {
|
||||
it('is true when entity.tags\' equivalent value is greater than selector.greaterThan', function() {
|
||||
var pseudoSelector = { greaterThan: { height: 10 }};
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var tallSchool = iD.Entity({ type: 'node', tags: { amenity: 'school', height: 9000 }});
|
||||
expect(pseudoRule.ruleChecks.greaterThan(tallSchool.tags)).to.be.true;
|
||||
});
|
||||
it('is false when entity.tags\' equivalent value is less than or equal to selector.greaterThan', function() {
|
||||
var pseudoSelector = { greaterThan: { height: 10 }};
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var smallSchool = iD.Entity({ type: 'node', tags: { amenity: 'school', height: 9 }});
|
||||
expect(pseudoRule.ruleChecks.greaterThan(smallSchool.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('greaterThanEqual', function() {
|
||||
it('is true when entity.tags\' equivalent value is greater than or equal to selector.greaterThanEqual', function() {
|
||||
var pseudoSelector = { greaterThanEqual: { height: 10 } };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var okHeightSchool = iD.Entity({ type: 'node', tags: { amenity: 'school', height: 10 }});
|
||||
expect(pseudoRule.ruleChecks.greaterThanEqual(okHeightSchool.tags)).to.be.true;
|
||||
});
|
||||
it('is false when entity.tags\' equivalent value is less than to selector.greaterThanEqual', function() {
|
||||
var pseudoSelector = { greaterThanEqual: { height: 10 }};
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var smallSchool = iD.Entity({ type: 'node', tags: { amenity: 'school', height: 9 }});
|
||||
expect(pseudoRule.ruleChecks.greaterThanEqual(smallSchool.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('lessThan', function() {
|
||||
it('is true when entity.tags\' equivalent value is less than to selector.lessThan', function() {
|
||||
var pseudoSelector = { lessThan: { height: 10 } };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var smallSchool = iD.Entity({ type: 'node', tags: { amenity: 'school', height: 3 }});
|
||||
expect(pseudoRule.ruleChecks.lessThan(smallSchool.tags)).to.be.tru;
|
||||
});
|
||||
it('is false when entity.tags\' equivalent value is greater than or equal to selector.lessThan', function() {
|
||||
var pseudoSelector = { lessThan: { height: 10 } };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var notOkHeightSchool = iD.Entity({ type: 'node', tags: { amenity: 'school', height: 10 }});
|
||||
expect(pseudoRule.ruleChecks.lessThan(notOkHeightSchool.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('lessThanEqual', function() {
|
||||
it('is true when entity.tags\' equivalent value is less than or equal to to selector.lessThan', function() {
|
||||
var pseudoSelector = { lessThanEqual: { height: 10 } };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var okHeightSchool = iD.Entity({ type: 'node', tags: { 'amenity': 'school', 'height': 10 }});
|
||||
expect(pseudoRule.ruleChecks.lessThanEqual(okHeightSchool.tags)).to.be.true;
|
||||
});
|
||||
it('is false when entity.tags\' equivalent value is greater than to selector.lessThan', function() {
|
||||
var pseudoSelector = { lessThanEqual: { height: 10 } };
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var notOkHeightSchool = iD.Entity({ type: 'node', tags: { amenity: 'school', height: 11 }});
|
||||
expect(pseudoRule.ruleChecks.lessThanEqual(notOkHeightSchool.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('positiveRegex', function() {
|
||||
it('is true when entity.tags\' equivalent value matches regular expression built from selector.positiveRegex', function() {
|
||||
var pseudoSelector = { positiveRegex: { amenity: ['^school$', '^healthcare$'] }};
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var okAmenities = [
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'school' }}),
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'healthcare' }})
|
||||
];
|
||||
okAmenities.forEach(function(amenity) {
|
||||
expect(pseudoRule.ruleChecks.positiveRegex(amenity.tags)).to.be.true;
|
||||
});
|
||||
});
|
||||
it('is false when entity.tags\' equivalent value does not match regular expression built from selector.positiveRegex', function() {
|
||||
var pseudoSelector = { positiveRegex: { amenity: ['^school$', '^healthcare$'] }};
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var notOkAmenities = [
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'parking' }}),
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'place_of_worship' }})
|
||||
];
|
||||
notOkAmenities.forEach(function(amenity) {
|
||||
expect(pseudoRule.ruleChecks.positiveRegex(amenity.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('negativeRegex', function() {
|
||||
it('is true when entity.tags\' equivalent value does not match regular exprsesion built from selector.negativeRegex', function() {
|
||||
var pseudoSelector = { negativeRegex: { amenity: ['^school$', '^healthcare$'] }};
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var notOkAmenities = [
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'parking' }}),
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'place_of_worship' }})
|
||||
];
|
||||
notOkAmenities.forEach(function(amenity) {
|
||||
expect(pseudoRule.ruleChecks.negativeRegex(amenity.tags)).to.be.true;
|
||||
});
|
||||
});
|
||||
it('is false when entity.tags\' equivalent value matches regular expression built from selector.negativeRegex', function() {
|
||||
var pseudoSelector = { negativeRegex: { amenity: ['^school$', '^healthcare$'] }};
|
||||
var pseudoRule = iD.utilMapCSSRule(pseudoSelector);
|
||||
var okAmenities = [
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'school' }}),
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'healthcare' }})
|
||||
];
|
||||
okAmenities.forEach(function(amenity) {
|
||||
expect(pseudoRule.ruleChecks.negativeRegex(amenity.tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#findWarnings', function() {
|
||||
it('adds found warnings to warnings array', function() {
|
||||
var graph = iD.Graph([entities]);
|
||||
var warnings = [];
|
||||
|
||||
rules.forEach(function(rule) {
|
||||
entities.forEach(function(entity) {
|
||||
rule.findWarnings(entity, graph, warnings);
|
||||
});
|
||||
});
|
||||
|
||||
warnings.forEach(function(warning) {
|
||||
// console.log(warning);
|
||||
// expect(warning.message).to.not.be.null;
|
||||
// expect(['mapcss_warning', 'mapcss_error'].indexOf(warning.id)).to.be.greaterThan(-1);
|
||||
// expect(warning.entity).to.be.instanceOf(iD.Entity);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user