mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 17:23:02 +00:00
- preset data is no longer bundled into iD.js - some code pathways commented out re: external presets - many changes so that tests can run without presets at start, or async - still need to make sure fallbacks are always there (point, line, area, etc)
572 lines
25 KiB
JavaScript
572 lines
25 KiB
JavaScript
describe('maprules', function() {
|
|
var _ruleChecks, _savedAreaKeys, validationRules;
|
|
|
|
before(function() {
|
|
_savedAreaKeys = iD.osmAreaKeys;
|
|
iD.osmSetAreaKeys({ building: {}, amenity: {} });
|
|
|
|
iD.services.maprules = iD.serviceMapRules;
|
|
iD.serviceMapRules.init();
|
|
_ruleChecks = iD.serviceMapRules.ruleChecks();
|
|
});
|
|
|
|
after(function() {
|
|
iD.osmSetAreaKeys(_savedAreaKeys);
|
|
delete iD.services.maprules;
|
|
});
|
|
|
|
describe('#filterRuleChecks', function() {
|
|
it('returns shortlist of mapcss checks relevant to provided selector', function() {
|
|
var selector = {
|
|
geometry: 'closedway',
|
|
equals: {amenity: 'marketplace'},
|
|
absence: 'name',
|
|
error: '\'Marketplace\' preset must be coupled with name'
|
|
};
|
|
var filteredChecks = iD.serviceMapRules.filterRuleChecks(selector);
|
|
var equalsCheck = filteredChecks[0];
|
|
var absenceCheck = filteredChecks[1];
|
|
var entityTags = { amenity: 'marketplace' };
|
|
|
|
expect(filteredChecks.length).eql(2);
|
|
expect(equalsCheck(entityTags)).to.be.true;
|
|
expect(absenceCheck(entityTags)).to.be.true;
|
|
});
|
|
});
|
|
|
|
describe('#buildTagMap', function() {
|
|
it('builds a map of tag keys/values found in mapcss selector', function() {
|
|
[
|
|
{
|
|
t: {
|
|
equals: {
|
|
man_made: 'tower',
|
|
'tower:type': 'communication'
|
|
}
|
|
},
|
|
r: {
|
|
man_made: ['tower'],
|
|
'tower:type': ['communication']
|
|
}
|
|
},
|
|
{
|
|
t: {
|
|
equals: {
|
|
building: 'yes',
|
|
amenity: 'school'
|
|
},
|
|
positiveRegex: {
|
|
opening_hours: [
|
|
'24/7',
|
|
'sunrise_sundown'
|
|
]
|
|
},
|
|
negativeRegex: {
|
|
source: [
|
|
'missing_maps',
|
|
'american_red_cross'
|
|
]
|
|
},
|
|
greaterThanEqual: { floors: 2 },
|
|
lessThanEqual: { floors: 4 }
|
|
|
|
},
|
|
r: {
|
|
building: ['yes'],
|
|
amenity: ['school'],
|
|
opening_hours: ['24/7', 'sunrise_sundown'],
|
|
source: ['missing_maps', 'american_red_cross'],
|
|
floors: [4, 2]
|
|
}
|
|
},
|
|
{
|
|
t: {
|
|
equals: { highway: 'yes' },
|
|
greaterThan: { lanes: 1 },
|
|
lessThan: { lanes: 4 }
|
|
},
|
|
r: {
|
|
highway: ['yes'],
|
|
lanes: [4, 1]
|
|
}
|
|
}
|
|
].forEach(function(test) {
|
|
expect(iD.serviceMapRules.buildTagMap(test.t)).to.eql(test.r);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#inferGeometry', function() {
|
|
it('infers geometry using selector keys', function() {
|
|
|
|
var amenityDerivedArea = {
|
|
geometry: 'closedway',
|
|
presence: 'amenity',
|
|
positiveRegex: { amenity: ['^school$', '^healthcare$'] },
|
|
error: 'amenity cannot be healthcare or school!'
|
|
};
|
|
|
|
var areaDerivedArea = {
|
|
geometry: 'closedway',
|
|
equals: { area: 'yes' },
|
|
};
|
|
|
|
var badAreaDerivedLine = {
|
|
geometry: 'closedway',
|
|
equals: { 'area': 'no' }
|
|
};
|
|
|
|
var roundHouseRailwayDerivedArea = {
|
|
geometry: 'closedway',
|
|
equals: { 'railway': 'roundhouse' }
|
|
};
|
|
|
|
var justClosedWayDerivedLine = {
|
|
geometry: 'closedway'
|
|
};
|
|
|
|
var tagMap, geom;
|
|
tagMap = iD.serviceMapRules.buildTagMap(amenityDerivedArea);
|
|
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
|
expect(geom).to.be.eql('area');
|
|
|
|
tagMap = iD.serviceMapRules.buildTagMap(areaDerivedArea);
|
|
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
|
expect(geom).to.be.eql('area');
|
|
|
|
tagMap = iD.serviceMapRules.buildTagMap(badAreaDerivedLine);
|
|
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
|
expect(geom).to.be.eql('line');
|
|
|
|
tagMap = iD.serviceMapRules.buildTagMap(roundHouseRailwayDerivedArea);
|
|
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
|
expect(geom).to.be.eql('area');
|
|
|
|
tagMap = iD.serviceMapRules.buildTagMap(justClosedWayDerivedLine);
|
|
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
|
expect(geom).to.be.eql('line');
|
|
});
|
|
});
|
|
|
|
describe('#addRule', function() {
|
|
it('builds a rule from provided selector and adds it to _validationRules', function () {
|
|
var selector = {
|
|
geometry:'node',
|
|
equals: {amenity:'marketplace'},
|
|
absence:'name',
|
|
warning:'\'Marketplace\' preset must be coupled with name'
|
|
};
|
|
expect(iD.serviceMapRules.validationRules()).to.be.empty;
|
|
iD.serviceMapRules.addRule(selector);
|
|
expect(iD.serviceMapRules.validationRules().length).to.eql(1);
|
|
});
|
|
});
|
|
describe('#clearRules', function() {
|
|
it('clears _validationRules array', function() {
|
|
expect(iD.serviceMapRules.validationRules().length).to.eql(1);
|
|
iD.serviceMapRules.clearRules();
|
|
expect(iD.serviceMapRules.validationRules()).to.be.empty;
|
|
});
|
|
});
|
|
|
|
describe('#validationRules', function() {
|
|
it('returns _validationRules array', function() {
|
|
var selector = {
|
|
geometry: 'closedway',
|
|
equals: {amenity: 'marketplace'},
|
|
absence: 'name',
|
|
error: '\'Marketplace\' preset must be coupled with name'
|
|
};
|
|
iD.serviceMapRules.addRule(selector);
|
|
var rules = iD.serviceMapRules.validationRules();
|
|
expect(rules).instanceof(Array);
|
|
expect(rules.length).to.eql(1);
|
|
});
|
|
});
|
|
|
|
describe('_ruleChecks', function () {
|
|
describe('#equals', function() {
|
|
it('is true when two tag maps intersect', function() {
|
|
var a = { amenity: 'school' };
|
|
var b = { amenity: 'school' };
|
|
expect(_ruleChecks.equals(a)(b)).to.be.true;
|
|
});
|
|
it('is false when two tag maps intersect', function() {
|
|
var a = { man_made: 'water_tap' };
|
|
var b = { amenity: 'school' };
|
|
expect(_ruleChecks.equals(a)(b)).to.be.false;
|
|
});
|
|
});
|
|
describe('#notEquals', function() {
|
|
it('is true when two tag maps do not intersect', function() {
|
|
var a = { man_made: 'water_tap' };
|
|
var b = { amenity: 'school' };
|
|
expect(_ruleChecks.notEquals(a)(b)).to.be.true;
|
|
});
|
|
it('is not true when two tag maps intersect', function() {
|
|
var a = { amenity: 'school' };
|
|
var b = { amenity: 'school', opening_hours: '9-5' };
|
|
expect(_ruleChecks.notEquals(a)(b)).to.be.false;
|
|
});
|
|
});
|
|
describe('absence', function() {
|
|
it('is true when tag map keys does not include key in question', function() {
|
|
var key = 'amenity';
|
|
var map = { building: 'yes' };
|
|
expect(_ruleChecks.absence(key)(map)).to.be.true;
|
|
});
|
|
it('is false when tag map keys does include key in question', function() {
|
|
var key = 'amenity';
|
|
var map = { amenity: 'school' };
|
|
expect(_ruleChecks.absence(key)(map)).to.be.false;
|
|
});
|
|
});
|
|
describe('presence', function() {
|
|
it('is true when tag map keys includes key in question', function() {
|
|
var key = 'amenity';
|
|
var map = { amenity: 'school'};
|
|
expect(_ruleChecks.presence(key)(map)).to.be.true;
|
|
});
|
|
it('is false when tag map keys do not include key in question', function() {
|
|
var key = 'amenity';
|
|
var map = { building: 'yes'};
|
|
expect(_ruleChecks.presence(key)(map)).to.be.false;
|
|
});
|
|
});
|
|
describe('greaterThan', function() {
|
|
it ('is true when a tag value is greater than the selector value', function() {
|
|
var selectorTags = { lanes: 5 };
|
|
var tags = { lanes : 6 };
|
|
expect(_ruleChecks.greaterThan(selectorTags)(tags)).to.be.true;
|
|
});
|
|
it ('is false when a tag value is less than or equal to the selector value', function() {
|
|
var selectorTags = { lanes: 5 };
|
|
[4, 5].forEach(function(val) {
|
|
expect(_ruleChecks.greaterThan(selectorTags)({ lanes: val })).to.be.false;
|
|
});
|
|
});
|
|
});
|
|
describe('greaterThanEqual', function() {
|
|
it ('is true when a tag value is greater than or equal to the selector value', function() {
|
|
var selectorTags = { lanes: 5 };
|
|
[5, 6].forEach(function(val) {
|
|
expect(_ruleChecks.greaterThanEqual(selectorTags)({ lanes: val })).to.be.true;
|
|
});
|
|
});
|
|
it ('is false when a tag value is less than the selector value', function () {
|
|
var selectorTags = { lanes: 5 };
|
|
var tags = { lanes: 4 };
|
|
expect(_ruleChecks.greaterThanEqual(selectorTags)(tags)).to.be.false;
|
|
});
|
|
});
|
|
describe('lessThan', function() {
|
|
it ('is true when a tag value is less than the selector value', function() {
|
|
var selectorTags = { lanes: 5 };
|
|
var tags = { lanes: 4 };
|
|
expect(_ruleChecks.lessThan(selectorTags)(tags)).to.be.true;
|
|
});
|
|
it ('is false when a tag value is greater than or equal to the selector value', function() {
|
|
var selectorTags = { lanes: 5 };
|
|
[6, 7].forEach(function(val) {
|
|
expect(_ruleChecks.lessThan(selectorTags)({ lanes: val })).to.be.false;
|
|
});
|
|
});
|
|
});
|
|
describe('lessThanEqual', function() {
|
|
it ('is true when a tag value is less than or equal to the selector value', function() {
|
|
var selectorTags = { lanes: 5 };
|
|
[4, 5].forEach(function(val) {
|
|
expect(_ruleChecks.lessThanEqual(selectorTags)({ lanes: val })).to.be.true;
|
|
});
|
|
});
|
|
it ('is false when a tag value is greater than the selector value', function() {
|
|
var selectorTags = { lanes: 5 };
|
|
var tags = { lanes: 6 };
|
|
expect(_ruleChecks.lessThanEqual(selectorTags)(tags)).to.be.false;
|
|
});
|
|
});
|
|
describe('positiveRegex', function() {
|
|
var positiveRegex = { amenity: ['^hospital$','^clinic$']};
|
|
it ('is true when tag value matches positiveRegex', function() {
|
|
var tags = { amenity: 'hospital' };
|
|
expect(_ruleChecks.positiveRegex(positiveRegex)(tags)).to.be.true;
|
|
});
|
|
it ('is false when tag value does not match negative regex', function() {
|
|
var tags = { amenity: 'school' };
|
|
expect(_ruleChecks.positiveRegex(positiveRegex)(tags)).to.be.false;
|
|
});
|
|
});
|
|
describe('negativeRegex', function() {
|
|
var negativeRegex = { bicycle: [ 'use_path', 'designated' ] };
|
|
it ('is true when tag value does not match negativeRegex', function() {
|
|
var tags = { bicycle: 'yes' };
|
|
expect(_ruleChecks.negativeRegex(negativeRegex)(tags)).to.be.true;
|
|
});
|
|
it ('is false when tag value matches negativeRegex', function() {
|
|
var tags = { bicycle: 'designated' };
|
|
expect(_ruleChecks.negativeRegex(negativeRegex)(tags)).to.be.false;
|
|
});
|
|
});
|
|
});
|
|
describe('rule', function() {
|
|
var selectors;
|
|
before(function() {
|
|
selectors = [
|
|
{
|
|
geometry:'node',
|
|
equals: {amenity:'marketplace'},
|
|
absence:'name',
|
|
error:'\'Marketplace\' preset must be coupled with name'
|
|
},
|
|
{
|
|
geometry: 'closedway',
|
|
notEquals: { building: 'yes', amenity: 'clinic' },
|
|
error: '\'Clinic\' preset must be coupled with building=yes'
|
|
},
|
|
{
|
|
geometry:'node',
|
|
equals: {man_made: 'tower', 'tower:type': 'communication'},
|
|
presence: 'height',
|
|
error:'\'Communication Tower\' preset must not be coupled with height'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
lessThanEqual: { height: 6 },
|
|
error: '\'Tower\' preset height must be greater than 6'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
greaterThanEqual: { height: 9 },
|
|
error: '\'Tower\' preset height must be less than 9'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
lessThan: { height: 6 },
|
|
error: '\'Tower\' preset height must be greater than or equal to 6'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
greaterThan: { height: 9 },
|
|
error: '\'Tower\' preset height must be greater less than or equal to 9'
|
|
},
|
|
{
|
|
geometry: 'closedway',
|
|
equals: { amenity: 'clinic' },
|
|
negativeRegex: { emergency: ['yes', 'no'] },
|
|
error: '\'Clinic\' preset\'s emergency tag must be equal to \'yes\' or \'no\''
|
|
},
|
|
{
|
|
geometry: 'way',
|
|
equals: { highway: 'residential' },
|
|
positiveRegex: { structure: ['bridge', 'tunnel'] },
|
|
error: '\'suburban road\' structure tag cannot be \'bridge\' or \'tunnel\''
|
|
}
|
|
];
|
|
|
|
iD.serviceMapRules.clearRules();
|
|
selectors.forEach(function(selector) { iD.serviceMapRules.addRule(selector); });
|
|
validationRules = iD.serviceMapRules.validationRules();
|
|
});
|
|
describe('#matches', function() {
|
|
var selectors, entities;
|
|
before(function() {
|
|
selectors = [
|
|
{
|
|
geometry:'node',
|
|
equals: {amenity:'marketplace'},
|
|
absence:'name',
|
|
error:'\'Marketplace\' preset must be coupled with name'
|
|
},
|
|
{
|
|
geometry: 'closedway',
|
|
notEquals: { building: 'yes', amenity: 'clinic' },
|
|
error: '\'Clinic\' preset must be coupled with building=yes'
|
|
},
|
|
{
|
|
geometry:'node',
|
|
equals: {man_made: 'tower', 'tower:type': 'communication'},
|
|
presence: 'height',
|
|
error:'\'Communication Tower\' preset must not be coupled with height'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
lessThanEqual: { height: 6 },
|
|
error: '\'Tower\' preset height must be greater than 6'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
greaterThanEqual: { height: 9 },
|
|
error: '\'Tower\' preset height must be less than 9'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
lessThan: { height: 6 },
|
|
error: '\'Tower\' preset height must be greater than or equal to 6'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
greaterThan: { height: 9 },
|
|
error: '\'Tower\' preset height must be greater less than or equal to 9'
|
|
},
|
|
{
|
|
geometry: 'closedway',
|
|
equals: { amenity: 'clinic' },
|
|
negativeRegex: { emergency: ['yes', 'no'] },
|
|
error: '\'Clinic\' preset\'s emergency tag must be equal to \'yes\' or \'no\''
|
|
},
|
|
{
|
|
geometry: 'way',
|
|
equals: { highway: 'residential' },
|
|
positiveRegex: { structure: ['bridge', 'tunnel'] },
|
|
error: '\'suburban road\' structure tag cannot be \'bridge\' or \'tunnel\''
|
|
}
|
|
];
|
|
entities = [
|
|
iD.osmEntity({ type: 'node', tags: { amenity: 'marketplace' }}),
|
|
iD.osmWay({ tags: { building: 'house', amenity: 'clinic' }, nodes: [ 'a', 'b', 'c', 'a' ]}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', 'tower:type': 'communication', height: 5 }}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', height: 6 }}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', height: 9 }}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', height: 5 }}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', height: 10 }}),
|
|
iD.osmWay({ tags: { amenity: 'clinic', emergency: 'definitely' }, nodes: [ 'd', 'e', 'f', 'd' ]}),
|
|
iD.osmWay({ tags: { highway: 'residential', structure: 'bridge' }}),
|
|
];
|
|
|
|
iD.serviceMapRules.clearRules();
|
|
selectors.forEach(function(selector) { iD.serviceMapRules.addRule(selector); });
|
|
validationRules = iD.serviceMapRules.validationRules();
|
|
});
|
|
it('is true when each rule check is \'true\'', function() {
|
|
validationRules.forEach(function(rule, i) {
|
|
expect(rule.matches(entities[i])).to.be.true;
|
|
});
|
|
});
|
|
it ('is true when at least one rule check is \'false\'', function() {
|
|
var selector = {
|
|
geometry: 'way',
|
|
equals: { highway: 'residential' },
|
|
positiveRegex: { structure: ['embarkment', 'bridge'] },
|
|
error: '\'suburban road\' structure tag cannot be \'bridge\' or \'tunnel\''
|
|
};
|
|
var entity = iD.osmWay({ tags: { highway: 'residential', structure: 'tunnel' }});
|
|
iD.serviceMapRules.clearRules();
|
|
iD.serviceMapRules.addRule(selector);
|
|
var rule = iD.serviceMapRules.validationRules()[0];
|
|
|
|
expect(rule.matches(entity)).to.be.false;
|
|
});
|
|
});
|
|
describe('#findIssues', function() {
|
|
var selectors, entities, _graph;
|
|
|
|
before(function() {
|
|
selectors = [
|
|
{
|
|
geometry:'node',
|
|
equals: {amenity:'marketplace'},
|
|
absence:'name',
|
|
error:'\'Marketplace\' preset must be coupled with name'
|
|
},
|
|
{
|
|
geometry: 'closedway',
|
|
notEquals: { building: 'yes', amenity: 'clinic' },
|
|
error: '\'Clinic\' preset must be coupled with building=yes'
|
|
},
|
|
{
|
|
geometry:'node',
|
|
equals: {man_made: 'tower', 'tower:type': 'communication'},
|
|
presence: 'height',
|
|
error:'\'Communication Tower\' preset must not be coupled with height'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
lessThanEqual: { height: 6 },
|
|
error: '\'Tower\' preset height must be greater than 6'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
greaterThanEqual: { height: 9 },
|
|
error: '\'Tower\' preset height must be less than 9'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
lessThan: { height: 6 },
|
|
error: '\'Tower\' preset height must be greater than or equal to 6'
|
|
},
|
|
{
|
|
geometry: 'node',
|
|
equals: { man_made: 'tower' },
|
|
greaterThan: { height: 9 },
|
|
error: '\'Tower\' preset height must be greater less than or equal to 9'
|
|
},
|
|
{
|
|
geometry: 'closedway',
|
|
equals: { amenity: 'clinic' },
|
|
negativeRegex: { emergency: ['yes', 'no'] },
|
|
error: '\'Clinic\' preset\'s emergency tag must be equal to \'yes\' or \'no\''
|
|
},
|
|
{
|
|
geometry: 'way',
|
|
equals: { highway: 'residential' },
|
|
positiveRegex: { structure: ['bridge', 'tunnel'] },
|
|
error: '\'suburban road\' structure tag cannot be \'bridge\' or \'tunnel\''
|
|
}
|
|
];
|
|
entities = [
|
|
iD.osmEntity({ type: 'node', tags: { amenity: 'marketplace' }}),
|
|
iD.osmWay({ tags: { building: 'house', amenity: 'clinic' }, nodes: [ 'a', 'b', 'c', 'a' ]}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', 'tower:type': 'communication', height: 5 }}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', height: 6 }}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', height: 9 }}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', height: 5 }}),
|
|
iD.osmEntity({ type: 'node', tags: { man_made: 'tower', height: 10 }}),
|
|
iD.osmWay({ tags: { amenity: 'clinic', emergency: 'definitely' }, nodes: [ 'd', 'e', 'f', 'd' ]}),
|
|
iD.osmWay({ tags: { highway: 'residential', structure: 'bridge' }}),
|
|
];
|
|
|
|
var wayNodes = [
|
|
iD.osmNode({ id: 'a' }),
|
|
iD.osmNode({ id: 'b' }),
|
|
iD.osmNode({ id: 'c' }),
|
|
iD.osmNode({ id: 'd' }),
|
|
iD.osmNode({ id: 'e' }),
|
|
iD.osmNode({ id: 'f' }),
|
|
];
|
|
_graph = iD.coreGraph(entities.concat(wayNodes));
|
|
iD.serviceMapRules.clearRules();
|
|
selectors.forEach(function(selector) { iD.serviceMapRules.addRule(selector); });
|
|
validationRules = iD.serviceMapRules.validationRules();
|
|
});
|
|
it('finds issues', function() {
|
|
validationRules.forEach(function(rule, i) {
|
|
var issues = [];
|
|
var entity = entities[i];
|
|
var selector = selectors[i];
|
|
|
|
rule.findIssues(entity, _graph, issues);
|
|
|
|
var issue = issues[0];
|
|
var type = Object.keys(selector).indexOf('error') ? 'error' : 'warning';
|
|
|
|
expect(issues.length).to.eql(1);
|
|
expect(issue.entityIds).to.eql([entity.id]);
|
|
expect(issue.message(context)).to.eql(selector[type]);
|
|
expect(type).to.eql(issue.severity);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|