Compare commits

..

4 Commits

Author SHA1 Message Date
Francisco López
f2e49864d9 Merge pull request #33 from CartoDB/SMOKES-reference-errors
Smokes reference errors
2017-05-19 09:32:19 +02:00
Francisco López
813446edd2 Merge branch 'tangram-release' into SMOKES-reference-errors 2017-05-19 09:31:46 +02:00
Francisco López
d2fce406c9 Release carto strict mode 2017-05-19 09:27:27 +02:00
Francisco López
81c87d044f Dist files for tangram 2017-04-17 15:27:05 +02:00
16 changed files with 131 additions and 2415 deletions

View File

@ -1,9 +1,7 @@
language: node_js
node_js:
- '6'
- '8'
- '10'
- "0.11"
- "0.10"
script:
- npm test

View File

@ -1,9 +0,0 @@
## CARTO's Changelog
## 0.15.1-cdb5
2018-11-20
* Support Node.js 6, 8 and, 10
* Drop support for Node.js 0.10 and 0.11
* Add package-lock.json
* Add CHANGELOG.carto.md

6
dist/carto.js vendored

File diff suppressed because one or more lines are too long

View File

@ -268,7 +268,7 @@ var carto = {
if (typeof(extract[2]) === 'string') {
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
}
error = options.indent + error.join('\n' + options.indent) + '\x1B[0m\n';
error = options.indent + error.join('\n' + options.indent) + '\033[0m\n';
message = options.indent + message + stylize(ctx.message, 'red');
if (ctx.filename) (message += stylize(' in ', 'red') + ctx.filename);
@ -326,8 +326,8 @@ function stylize(str, style) {
'red' : [31, 39],
'grey' : [90, 39]
};
return '\x1B[' + styles[style][0] + 'm' + str +
'\x1B[' + styles[style][1] + 'm';
return '\033[' + styles[style][0] + 'm' + str +
'\033[' + styles[style][1] + 'm';
}
}).call(this,require('_process'),"/lib/carto")
@ -1769,8 +1769,6 @@ CartoCSS.prototype = {
// serach the max index to know rendering order
lyr.index = _.max(props[v].map(function(a) { return a.index; }).concat(lyr.index));
lyr.constant = !_.any(props[v].map(function(a) { return !a.constant; }));
// True when the property is filtered.
lyr.filtered = props[v][0].filtered;
}
}
@ -4231,37 +4229,48 @@ tree.Definition.prototype.toXML = function(env, existing) {
tree.Definition.prototype.toJS = function(env) {
var shaderAttrs = {};
// merge conditions from filters with zoom condition of the
// definition
var zoom = "(" + this.zoom + " & (1 << ctx.zoom))";
var frame_offset = this.frame_offset;
var zoomFilter = "(" + this.zoom + " & (1 << ctx.zoom))";
var filters = [zoomFilter];
var originalFilters = this.filters.toJS(env);
// Ignore default zoom for filtering (https://github.com/CartoDB/carto/issues/40)
var zoomFiltered = this.zoom !== tree.Zoom.all;
if (originalFilters) {
filters.push(originalFilters);
}
if (frame_offset) {
filters.push('ctx["frame-offset"] === ' + frame_offset);
}
var _if = this.filters.toJS(env);
var filters = [zoom];
if(_if) filters.push(_if);
if(frame_offset) filters.push('ctx["frame-offset"] === ' + frame_offset);
_if = filters.join(" && ");
_.each(this.rules, function(rule) {
var exportedRule = {};
if(rule instanceof tree.Rule) {
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
if (!rule instanceof tree.Rule) {
throw new Error("Ruleset not supported");
var r = {
index: rule.index,
symbolizer: rule.symbolizer
};
if (_if) {
r.js = "if(" + _if + "){" + rule.value.toJS(env) + "}"
} else {
r.js = rule.value.toJS(env);
}
exportedRule.index = rule.index;
exportedRule.symbolizer = rule.symbolizer;
exportedRule.js = "if(" + filters.join(" && ") + "){" + rule.value.toJS(env) + "}";
exportedRule.constant = rule.value.ev(env).is !== 'field';
exportedRule.filtered = zoomFiltered || (originalFilters !== '');
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
shaderAttrs[rule.name].push(exportedRule);
});
r.constant = rule.value.ev(env).is !== 'field';
r.filtered = !!_if;
shaderAttrs[rule.name].push(r);
} else {
throw new Error("Ruleset not supported");
//if (rule instanceof tree.Ruleset) {
//var sh = rule.toJS(env);
//for(var v in sh) {
//shaderAttrs[v] = shaderAttrs[v] || [];
//for(var attr in sh[v]) {
//shaderAttrs[v].push(sh[v][attr]);
//}
//}
//}
}
});
return shaderAttrs;
};
@ -4617,9 +4626,6 @@ tree.Filterset.prototype.toJS = function(env) {
val = filter._val.toString(true);
}
var attrs = "data";
if (op === '=~') {
return "(" + attrs + "['" + filter.key.value + "'] + '').match(" + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'") + "'" : val) + ")";
}
return attrs + "['" + filter.key.value + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'") + "'" : val);
}).join(' && ');
};
@ -5800,9 +5806,7 @@ tree.Value.prototype = {
v = "'" + v + "'";
} else if (val.is === 'field') {
// replace [variable] by ctx['variable']
v = v.replace(/\[([^\]]*)\]/g, function(matched) {
return matched.replace(/\[(.*)\]/g, "data['$1']");
});
v = v.replace(/\[(.*)\]/g, "data['$1']");
}else if (val.is === 'call') {
v = JSON.stringify({
name: val.name,
@ -7313,7 +7317,7 @@ function hasOwnProperty(obj, prop) {
},{"./support/isBuffer":42,"_process":40,"inherits":41}],44:[function(require,module,exports){
module.exports={
"name": "carto",
"version": "0.15.1-cdb4",
"version": "0.15.1-cdb1",
"description": "CartoCSS Stylesheet Compiler",
"url": "https://github.com/cartodb/carto",
"repository": {
@ -7366,7 +7370,6 @@ module.exports={
"scripts": {
"pretest": "npm install",
"test": "mocha -R spec",
"tdd" : "env HIDE_LOGS=true mocha -w -R spec",
"coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info"
}
}

View File

@ -53,7 +53,7 @@ var carto = {
if (typeof(extract[2]) === 'string') {
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
}
error = options.indent + error.join('\n' + options.indent) + '\x1B[0m\n';
error = options.indent + error.join('\n' + options.indent) + '\033[0m\n';
message = options.indent + message + stylize(ctx.message, 'red');
if (ctx.filename) (message += stylize(' in ', 'red') + ctx.filename);
@ -111,6 +111,6 @@ function stylize(str, style) {
'red' : [31, 39],
'grey' : [90, 39]
};
return '\x1B[' + styles[style][0] + 'm' + str +
'\x1B[' + styles[style][1] + 'm';
return '\033[' + styles[style][0] + 'm' + str +
'\033[' + styles[style][1] + 'm';
}

View File

@ -229,8 +229,6 @@ CartoCSS.prototype = {
// serach the max index to know rendering order
lyr.index = _.max(props[v].map(function(a) { return a.index; }).concat(lyr.index));
lyr.constant = !_.any(props[v].map(function(a) { return !a.constant; }));
// True when the property is filtered.
lyr.filtered = props[v][0].filtered;
}
}

View File

@ -838,18 +838,6 @@ var _mapnik_reference_latest = {
"default-value": "point",
"doc": "How this shield should be placed. Point placement attempts to place it on top of points, line places along lines multiple times per feature, vertex places on the vertexes of polygons, and interior attempts to place inside of polygons."
},
"placement-type": {
"css": "shield-placement-type",
"doc": "Re-position and/or re-size shield to avoid overlaps. \"simple\" for basic algorithm (using shield-placements string,) \"dummy\" to turn this feature off.",
"type": [
"dummy",
"simple",
"list"
],
"expression":true,
"default-meaning": "Alternative placements will not be enabled.",
"default-value": "dummy"
},
"avoid-edges": {
"css": "shield-avoid-edges",
"doc": "Tell positioning algorithm to avoid labeling near intersection edges.",

View File

@ -210,37 +210,48 @@ tree.Definition.prototype.toXML = function(env, existing) {
tree.Definition.prototype.toJS = function(env) {
var shaderAttrs = {};
// merge conditions from filters with zoom condition of the
// definition
var zoom = "(" + this.zoom + " & (1 << ctx.zoom))";
var frame_offset = this.frame_offset;
var zoomFilter = "(" + this.zoom + " & (1 << ctx.zoom))";
var filters = [zoomFilter];
var originalFilters = this.filters.toJS(env);
// Ignore default zoom for filtering (https://github.com/CartoDB/carto/issues/40)
var zoomFiltered = this.zoom !== tree.Zoom.all;
if (originalFilters) {
filters.push(originalFilters);
}
if (frame_offset) {
filters.push('ctx["frame-offset"] === ' + frame_offset);
}
var _if = this.filters.toJS(env);
var filters = [zoom];
if(_if) filters.push(_if);
if(frame_offset) filters.push('ctx["frame-offset"] === ' + frame_offset);
_if = filters.join(" && ");
_.each(this.rules, function(rule) {
var exportedRule = {};
if(rule instanceof tree.Rule) {
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
if (!rule instanceof tree.Rule) {
throw new Error("Ruleset not supported");
var r = {
index: rule.index,
symbolizer: rule.symbolizer
};
if (_if) {
r.js = "if(" + _if + "){" + rule.value.toJS(env) + "}"
} else {
r.js = rule.value.toJS(env);
}
exportedRule.index = rule.index;
exportedRule.symbolizer = rule.symbolizer;
exportedRule.js = "if(" + filters.join(" && ") + "){" + rule.value.toJS(env) + "}";
exportedRule.constant = rule.value.ev(env).is !== 'field';
exportedRule.filtered = zoomFiltered || (originalFilters !== '');
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
shaderAttrs[rule.name].push(exportedRule);
});
r.constant = rule.value.ev(env).is !== 'field';
r.filtered = !!_if;
shaderAttrs[rule.name].push(r);
} else {
throw new Error("Ruleset not supported");
//if (rule instanceof tree.Ruleset) {
//var sh = rule.toJS(env);
//for(var v in sh) {
//shaderAttrs[v] = shaderAttrs[v] || [];
//for(var attr in sh[v]) {
//shaderAttrs[v].push(sh[v][attr]);
//}
//}
//}
}
});
return shaderAttrs;
};

View File

@ -92,10 +92,7 @@ tree.Filterset.prototype.toJS = function(env) {
val = filter._val.toString(true);
}
var attrs = "data";
if (op === '=~') {
return "(" + attrs + "['" + filter.key.value + "'] + '').match(" + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&amp;/g, '&') + "'" : val) + ")";
}
return attrs + "['" + filter.key.value + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&amp;/g, '&') + "'" : val);
return attrs + "['" + filter.key.value + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'") + "'" : val);
}).join(' && ');
};

View File

@ -33,18 +33,10 @@ tree.Value.prototype = {
var val = this.ev(env);
var v = val.toString();
if(val.is === "color" || val.is === 'uri' || val.is === 'string' || val.is === 'keyword') {
v = "'" + v.replace(/&amp;/g, '&') + "'";
} else if (Array.isArray(this.value) && this.value.length > 1) {
// This covers something like `line-dasharray: 5, 10;`
// where the return _value has more than one element.
// Without this the generated code will look like:
// _value = 5, 10; which will ignore the 10.
v = '[' + this.value.join(',') + ']';
v = "'" + v + "'";
} else if (val.is === 'field') {
// replace [variable] by ctx['variable']
v = v.replace(/\[([^\]]*)\]/g, function(matched) {
return matched.replace(/\[(.*)\]/g, "data['$1']");
});
v = v.replace(/\[(.*)\]/g, "data['$1']");
}else if (val.is === 'call') {
v = JSON.stringify({
name: val.name,

2110
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "carto",
"version": "0.15.1-cdb5",
"version": "0.15.1-cdb1",
"description": "CartoCSS Stylesheet Compiler",
"url": "https://github.com/cartodb/carto",
"repository": {
@ -53,11 +53,6 @@
"scripts": {
"pretest": "npm install",
"test": "mocha -R spec",
"tdd": "env HIDE_LOGS=true mocha -w -R spec",
"coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info",
"bump": "npm version patch",
"bump:major": "npm version major",
"bump:minor": "npm version minor",
"postversion": "git push origin master --follow-tags"
"coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info"
}
}

View File

@ -0,0 +1,8 @@
var ref = require('./reference.json');
var carto = require('../../lib/carto/index.js');
var ccss = new carto.RendererJS({
reference: ref,
strict: true
}).render("#layer { polygon-fill: #abcdef; }");

View File

@ -1,102 +0,0 @@
/**
* Test the filtered field.
*
* When compiled, a rule provides metainformation fields like index, constant...etc
* one of this fields is the "filtered field".
*
* This field gives information about whether a property is filtered or not.
*
* A property is filtered if it was activated inside a filter. In the following cartocss
* code marker-color.filtered will be true because it's inside a population filter.
*
* #layer {
* maker-width: 20;
* [population > 100] {
* marker-color: red; // this property is filtered
* }
* }
*
* "zoom" is a special case, and it only should be considered when its value is not the default.
*/
var assert = require('assert');
var Carto = require('../lib/carto/index.js');
var renderer = new Carto.RendererJS({ strict: true });
describe('property.filtered', function () {
it('should be false when the property is not filtered', function () {
var style = [
'#layer {',
' marker-fill: red;',
'}'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(!layers['marker-fill'].filtered);
});
it('should be true when the property is filtered', function () {
var style = [
'#layer {',
' [foo > 30] {',
' marker-fill: red;',
' }',
'}'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(layers['marker-fill'].filtered);
});
it('should be true when the property is filtered at first level', function () {
var style = [
'#layer [foo > 30] {',
' marker-fill: red;',
'}`'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(layers['marker-fill'].filtered);
});
it('should be false when the property is not filterd but there is another filtered properties', function () {
var style = [
'#layer {',
' marker-fill: red;',
' [bar < 200]{',
' marker-allow-overlap: false;',
' }',
'}`'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(!layers['marker-fill'].filtered);
assert(layers['marker-allow-overlap'].filtered);
});
it('should be true when the property is filtered and have a default value', function () {
var style = [
'#layer {',
' marker-fill: red;',
' [bar < 200]{',
' marker-fill: blue;',
' }',
'}`'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(layers['marker-fill'].filtered);
});
it('should be true when filtering by zoom', function () {
var style = [
'#layer {',
' [zoom < 5]{',
' marker-fill: blue;',
' }',
'}`'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(layers['marker-fill'].filtered);
});
});

View File

@ -134,9 +134,9 @@ describe('RendererJS Strict Mode', function() {
new carto.RendererJS({reference: reference, mapnik_version: '1.0.0' })
));
it('should pass if a feature is not supported but strict is turned off', function () {
it('should pass if a feature is not supported but strict is turned off', rendererStrictModeOffTest(
new carto.RendererJS({reference: reference, mapnik_version: '1.0.0', strict: false })
});
));
it('should pass if a feature is supported and strict is turned on', function () {
var RendererJS = new carto.RendererJS({reference: reference, mapnik_version: '1.0.0', strict: true });

View File

@ -1,4 +1,4 @@
var SHOW_LOGS = (process.env.HIDE_LOGS !== 'true');
var assert = require('assert');
var carto = require('../lib/carto');
describe('RenderingJS', function() {
@ -21,7 +21,7 @@ describe('RenderingJS', function() {
].join('\n');
beforeEach(function() {
shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
shader = (new carto.RendererJS({ debug: true })).render(style);
});
it ("shold render layers", function() {
@ -45,7 +45,7 @@ describe('RenderingJS', function() {
it ("shold render variables", function() {
var style = '#test { marker-width: [testing]; }';
shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
shader = (new carto.RendererJS({ debug: true })).render(style);
var layer = shader.getLayers()[0];
var props = layer.getStyle({testing: 2}, { 'zoom': 0, 'frame-offset': 10 });
assert( props['marker-width'] === 2);
@ -53,7 +53,7 @@ describe('RenderingJS', function() {
it ("should allow filter based rendering", function() {
var style = '#test { marker-width: 10; [zoom = 1] { marker-width: 1; } }';
shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
shader = (new carto.RendererJS({ debug: true })).render(style);
var layer = shader.getLayers()[0];
var props = layer.getStyle({}, { 'zoom': 0, 'frame-offset': 10 });
assert( props['marker-width'] === 10);
@ -64,7 +64,7 @@ describe('RenderingJS', function() {
it ("symbolizers should be in rendering order", function() {
var style = '#test { polygon-fill: red; line-color: red; }';
style += '#test2 { line-color: red;polygon-fill: red; line-width: 10; }';
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var shader = (new carto.RendererJS({ debug: true })).render(style);
var layer0 = shader.getLayers()[0];
assert(layer0.getSymbolizers()[0] === 'polygon');
assert(layer0.getSymbolizers()[1] === 'line');
@ -76,7 +76,7 @@ describe('RenderingJS', function() {
it ("colorize should return a list of colours in same order", function() {
var style = '#test { image-filters: colorize-alpha(blue, cyan, green, yellow, orange, red); }';
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var shader = (new carto.RendererJS({ debug: true })).render(style);
var layer0 = shader.getLayers()[0];
var st = layer0.getStyle({ value: 1 }, {"frame-offset": 0, "zoom": 3});
var expectedColours = [[0, 0, 255], [0, 255, 255], [0, 128, 0], [255, 255, 0], [255, 165, 0], [255, 0, 0]];
@ -106,7 +106,7 @@ describe('RenderingJS', function() {
' [frame-offset = 2] { marker-width: 15; marker-fill-opacity: 0.02;}',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(css);
var shader = (new carto.RendererJS({ debug: true })).render(css);
var markerURLs = shader.getImageURLs();
var against = ["http://localhost:8081/gal.svg", "http://upload.wikimedia.org/wikipedia/commons/4/43/Flag_of_the_Galactic_Empire.svg", "http://upload.wikimedia.org/wikipedia/commons/c/c9/Flag_of_Syldavia.svg"];
for(var i = 0; i<against.length; i++){
@ -116,24 +116,24 @@ describe('RenderingJS', function() {
it ("should return variable for styles that change", function() {
var style = '#test { marker-width: [prop]; }';
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var shader = (new carto.RendererJS({ debug: true })).render(style);
var layer0 = shader.getLayers()[0];
assert(layer0.isVariable());
style = '#test { marker-width: 1; }';
shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
shader = (new carto.RendererJS({ debug: true })).render(style);
layer0 = shader.getLayers()[0];
assert(!layer0.isVariable());
style = '#test { marker-width: [prop]; marker-fill: red; }';
shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
shader = (new carto.RendererJS({ debug: true })).render(style);
layer0 = shader.getLayers()[0];
assert(layer0.isVariable());
});
it("should parse styles with string", function() {
var style = '#test { [column = "test\'ing"] { marker-width: 10; } }';
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var shader = (new carto.RendererJS({ debug: true })).render(style);
var layer = shader.getLayers()[0];
var props = layer.getStyle({column: 'test\'ing'}, { 'zoom': 0, 'frame-offset': 10 });
assert(props['marker-width'] === 10);
@ -141,7 +141,7 @@ describe('RenderingJS', function() {
it("should parse styles with filters not supported by dot notation", function() {
var style = '#test["mapnik::geometry_type"=1] { marker-width: 10; }';
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var shader = (new carto.RendererJS({ debug: true })).render(style);
var layer = shader.getLayers()[0];
var props = layer.getStyle({"mapnik::geometry_type": 1}, { 'zoom': 0 });
assert.equal(props['marker-width'], 10);
@ -155,7 +155,7 @@ describe('RenderingJS', function() {
' marker-width: ramp([cartodb_id], (#fff, #bbb), jenks);',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(css);
var shader = (new carto.RendererJS({ debug: true })).render(css);
var layer = shader.getLayers()[0];
var st = layer.shader['marker-width'].style({}, {zoom: 1})
assert.equal(st.name, "ramp")
@ -172,7 +172,7 @@ describe('RenderingJS', function() {
' marker-width: ramp([cartodb_id], cartocolor(Bold), category(10));',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(css);
var shader = (new carto.RendererJS({ debug: true })).render(css);
var layer = shader.getLayers()[0];
var st = layer.shader['marker-width'].style({}, {zoom: 1});
assert.equal(st.name, "ramp");
@ -183,57 +183,4 @@ describe('RenderingJS', function() {
assert.equal(st.args[2].args[0].value, 10);
});
it("should work with multiple operands", function(){
var css = [
'#layer {',
' marker-width: [value] * [value] * 0.5;',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: false })).render(css);
var layer = shader.getLayers()[0];
var width = layer.shader['marker-width'].style({value: 4}, {zoom: 1});
assert.equal(width, 8);
});
it("should work with numbers", function(){
var css = [
'#layer {',
' line-dasharray: 5, 10;',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: false })).render(css);
var layer = shader.getLayers()[0];
var dasharray = layer.shader['line-dasharray'].style({value: 4}, {zoom: 1});
assert.deepEqual(dasharray, [5, 10]);
});
it("should not throw `ReferenceError` with `=~` operator", function(){
var css = [
'#layer[name=~".*wadus*"] {',
' marker-width: 14;',
'}'
].join('\n');
assert.doesNotThrow(function () {
var shader = (new carto.RendererJS({})).render(css);
var layer = shader.getLayers()[0];
var value = layer.shader['marker-width'].style({ name: 'wadus' }, { zoom: 1 });
assert.equal(value, 14);
}, ReferenceError);
});
it("`=~` operator should support numbers", function(){
var css = [
'#layer[value=~"^10"] {',
' marker-width: 14;',
'}'
].join('\n');
assert.doesNotThrow(function () {
var shader = (new carto.RendererJS({})).render(css);
var layer = shader.getLayers()[0];
var value = layer.shader['marker-width'].style({ value: 10 }, { zoom: 1 });
assert.equal(value, 14);
}, Error);
});
});