Skip to content

Commit

Permalink
improve parse testing
Browse files Browse the repository at this point in the history
- use json-to-ast to find a location of test in json file (no more dirty
hacks)
- check for test name duplication (common problem that we can avoid now,
found and fixed few duplicates)
- tests with same name but variations can be grouped in array (for
instance much easier to describe error tests)
  • Loading branch information
lahmatiy committed Feb 2, 2017
1 parent 99725a2 commit 0641df6
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 118 deletions.
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@
"rules": {
"no-duplicate-case": 2,
"no-undef": 2,
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}]
"no-unused-vars": [
2,
{
"vars": "all",
"args": "after-used"
}
]
}
},
"scripts": {
Expand All @@ -58,6 +64,7 @@
"eslint": "^2.13.1",
"istanbul": "^0.4.2",
"jscs": "~3.0.7",
"json-to-ast": "^1.2.14",
"mocha": "^3.0.2",
"uglify-js": "^2.6.1"
},
Expand Down
178 changes: 90 additions & 88 deletions test/fixture/parse/atrule/atrule/media.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
}
}
},
"single media type with negation": {
"single media type with only": {
"source": "@media only screen{}",
"ast": {
"type": "Atrule",
Expand Down Expand Up @@ -153,7 +153,7 @@
"name": "foo",
"value": {
"type": "Identifier",
"value": "bar"
"name": "bar"
}
}
]
Expand All @@ -166,7 +166,7 @@
}
}
},
"media feature with identifier value": {
"media feature with dimension value": {
"source": "@media (foo:2px){}",
"ast": {
"type": "Atrule",
Expand Down Expand Up @@ -287,89 +287,91 @@
}
}
},
"error#1": {
"source": "@media;",
"offset": " ^",
"error": "Identifier or parenthesis is expected"
},
"error#2": {
"source": "@media {}",
"offset": " ^",
"error": "Identifier or parenthesis is expected"
},
"error#3": {
"source": "@media xxx foo {}",
"offset": " ^",
"error": "Unexpected identifier"
},
"error#4": {
"source": "@media and (min-width: 600px) {}",
"offset": " ^",
"error": "Unexpected `and` keyword"
},
"error#5": {
"source": "@media screen (min-width: 600px) {}",
"offset": " ^",
"error": "`and` keyword is expected"
},
"error#6": {
"source": "@media (foo) and and (bar) {}",
"offset": " ^",
"error": "LeftParenthesis is expected"
},
"error#7": {
"source": "@media (foo) xxx (bar) {}",
"offset": " ^",
"error": "Unexpected identifier"
},
"error#8": {
"source": "@media (foo) (bar) {}",
"offset": " ^",
"error": "`and` keyword is expected"
},
"error#9": {
"source": "@media (foo 123) {}",
"offset": " ^",
"error": "Colon is expected"
},
"error#10": {
"source": "@media (foo: bar baz) {}",
"offset": " ^",
"error": "RightParenthesis is expected"
},
"error#11": {
"source": "@media (foo: 1/) {}",
"offset": " ^",
"error": "Number is expected"
},
"error#12": {
"source": "@media (foo: 1.2/1) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
"error#13": {
"source": "@media (foo: 1e2/1) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
"error#14": {
"source": "@media (foo: -1/5) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
"error#15": {
"source": "@media (foo: 1/1.2) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
"error#16": {
"source": "@media (foo: 1/1e2) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
"error#17": {
"source": "@media (foo: 1/-5) {}",
"offset": " ^",
"error": "Positive integer is expected"
}
"error": [
{
"source": "@media;",
"offset": " ^",
"error": "Identifier or parenthesis is expected"
},
{
"source": "@media {}",
"offset": " ^",
"error": "Identifier or parenthesis is expected"
},
{
"source": "@media xxx foo {}",
"offset": " ^",
"error": "Unexpected identifier"
},
{
"source": "@media and (min-width: 600px) {}",
"offset": " ^",
"error": "Unexpected `and` keyword"
},
{
"source": "@media screen (min-width: 600px) {}",
"offset": " ^",
"error": "`and` keyword is expected"
},
{
"source": "@media (foo) and and (bar) {}",
"offset": " ^",
"error": "LeftParenthesis is expected"
},
{
"source": "@media (foo) xxx (bar) {}",
"offset": " ^",
"error": "Unexpected identifier"
},
{
"source": "@media (foo) (bar) {}",
"offset": " ^",
"error": "`and` keyword is expected"
},
{
"source": "@media (foo 123) {}",
"offset": " ^",
"error": "Colon is expected"
},
{
"source": "@media (foo: bar baz) {}",
"offset": " ^",
"error": "RightParenthesis is expected"
},
{
"source": "@media (foo: 1/) {}",
"offset": " ^",
"error": "Number is expected"
},
{
"source": "@media (foo: 1.2/1) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
{
"source": "@media (foo: 1e2/1) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
{
"source": "@media (foo: -1/5) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
{
"source": "@media (foo: 1/1.2) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
{
"source": "@media (foo: 1/1e2) {}",
"offset": " ^",
"error": "Positive integer is expected"
},
{
"source": "@media (foo: 1/-5) {}",
"offset": " ^",
"error": "Positive integer is expected"
}
]
}
54 changes: 34 additions & 20 deletions test/fixture/parse/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,34 +47,48 @@ var tests = fs.readdirSync(__dirname).reduce(function(result, scope) {
function scanDir(dir) {
if (fs.statSync(dir).isDirectory()) {
fs.readdirSync(dir).forEach(function(fn) {
function processTest(test, key, storeKey) {
if (test.error) {
if (typeof test.offset === 'string') {
var offset = test.offset.indexOf('^');
var lines = test.source.substr(0, offset).split(/\r|\r\n|\n|\f/g);
var position = {
offset: offset,
line: lines.length,
column: lines.pop().length + 1
};

test.position = position;
}
errors[storeKey] = test;
} else {
if (test.ast.type.toLowerCase() !== scope.toLowerCase() && wrapper.hasOwnProperty(scope)) {
test.ast = wrapper[scope](test.ast);
}
tests[storeKey] = test;
}
}

var filename = path.join(dir, fn);

if (fs.statSync(filename).isDirectory()) {
return scanDir(filename);
}

var tests = require(filename);
var errors = {};
var locator = new JsonLocator(filename);
var origTests = require(filename);
var tests = {};
var errors = {};

for (var key in tests) {
tests[key].name = locator.get(key);
if (tests[key].error) {
if (typeof tests[key].offset === 'string') {
var offset = tests[key].offset.indexOf('^');
var lines = tests[key].source.substr(0, offset).split(/\r|\r\n|\n|\f/g);
var position = {
offset: offset,
line: lines.length,
column: lines.pop().length + 1
};

tests[key].position = position;
}
errors[key] = tests[key];
delete tests[key];
} else if (tests[key].ast.type.toLowerCase() !== scope.toLowerCase() && wrapper.hasOwnProperty(scope)) {
tests[key].ast = wrapper[scope](tests[key].ast);
for (var key in origTests) {
if (Array.isArray(origTests[key])) {
origTests[key].forEach(function(test, idx) {
test.name = locator.get(key, idx);
processTest(test, key, key + '#' + (idx + 1));
});
} else {
origTests[key].name = locator.get(key);
processTest(origTests[key], key, key);
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/fixture/parse/selectorList/Selector.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"offset": "^",
"error": "Selector is expected"
},
"bad selector #7": {
"bad selector #8": {
"source": "a,{",
"offset": " ^",
"error": "Selector is expected"
Expand Down
2 changes: 1 addition & 1 deletion test/fixture/syntax/keyword.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"bar"
]
},
"with vendor prefix": {
"with vendor prefix ignore": {
"syntax": {
"properties": {
"test": "someExample"
Expand Down
50 changes: 44 additions & 6 deletions test/helpers/JsonLocator.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,56 @@
var fs = require('fs');
var path = require('path');
var parseJSON = require('json-to-ast');

function JsonLocator(filename) {
this.content = fs.readFileSync(filename, 'utf-8');
this.filename = path.relative(__dirname + '/../..', filename);
this.map = Object.create(null);

var ast = parseJSON(fs.readFileSync(filename, 'utf-8'));

if (ast && ast.type === 'object') {
for (var i = 0; i < ast.properties.length; i++) {
var property = ast.properties[i];

if (hasOwnProperty.call(this.map, property.key.value)) {
throw new Error('Duplicate key `' + property.key.value + '` in ' + this.getLocation(property.key.position.start));
}

// use JSON.parse to unescape chars
this.map[JSON.parse('"' + property.key.value + '"')] = {
loc: this.getLocation(property.key.position.start),
value: property.value
};
}
}
}

JsonLocator.prototype.get = function(name) {
var lines = this.content.split('"' + name + '"')[0].split('\n');
JsonLocator.prototype.getLocation = function(loc) {
return [
this.filename,
lines.length,
lines.pop().length + name.length + 5
].join(':') + ' (' + name + ')';
loc.line,
loc.column
].join(':');
};

JsonLocator.prototype.get = function(name, index) {
var loc;

if (hasOwnProperty.call(this.map, name) === false) {
throw new Error('Key `' + name + '` not found in ' + this.filename);
}

if (typeof index === 'number' && this.map[name].value.type === 'array') {
if (index in this.map[name].value.items === false) {
throw new Error('Wrong index `' + index + '` for `' + name + '` in ' + this.filename);
}
loc = this.getLocation(this.map[name].value.items[index].position.start);
name += ' #' + index;
} else {
loc = this.map[name].loc;
}

return loc + ' (' + name + ')';
};

module.exports = JsonLocator;
2 changes: 1 addition & 1 deletion test/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('parse', function() {
});
});

describe('parse errors', function() {
describe('errors', function() {
forEachParseTest(function(name, test, context) {
createParseErrorTest(name, test, {
context: context,
Expand Down

0 comments on commit 0641df6

Please sign in to comment.