diff --git a/.travis.yml b/.travis.yml index e38bcc4..5fba99c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: node_js node_js: - - node + - "stable" + - "lts/*" - 6 - - 4 cache: directories: - node_modules diff --git a/package.json b/package.json index e6c175f..4d9e7db 100644 --- a/package.json +++ b/package.json @@ -1,28 +1,30 @@ { "name": "posthtml-expressions", - "version": "1.1.0", + "version": "1.1.1", "description": "Expressions Plugin for PostHTML", - "engines": { "node": ">=4" }, - "main":"lib", + "engines": { + "node": ">=6" + }, + "main": "lib", "scripts": { "lint": "standard", - "test": "nyc ava -v test/index.js", + "test": "nyc ava", "logs": "standard-changelog -i CHANGELOG.md -w", "docs": "jsdoc2md lib/*.js > INDEX.md", "clean": "rm -rf .nyc_output coverage jsdoc-api dmd", "start": "sudo npm run clean && npm run lint && sudo npm test" }, "dependencies": { - "fclone": "^1.x" + "fclone": "^1.0.11" }, "devDependencies": { - "ava": "^0.19.1", - "coveralls": "^2.13.1", - "jsdoc-to-markdown": "^3.0.0", - "nyc": "^10.3.2", - "posthtml": "^0.9.x", - "standard": "^10.0.2", - "standard-changelog": "^1.0.0" + "ava": "^1.4.1", + "coveralls": "^3.0.3", + "jsdoc-to-markdown": "^5.0.0", + "nyc": "^14.1.1", + "posthtml": "^0.11.4", + "standard": "^12.0.1", + "standard-changelog": "^2.0.11" }, "files": [ "lib", diff --git a/test/expect/loop_conditional_locals.html b/test/expect/loop_conditional_locals.html new file mode 100644 index 0000000..27badd7 --- /dev/null +++ b/test/expect/loop_conditional_locals.html @@ -0,0 +1,12 @@ +

x

+ + + Page 1 + + + Page 2 + + + Page 3 + +

x

diff --git a/test/fixtures/loop_conditional_locals.html b/test/fixtures/loop_conditional_locals.html new file mode 100644 index 0000000..c136ee0 --- /dev/null +++ b/test/fixtures/loop_conditional_locals.html @@ -0,0 +1,10 @@ +

x

+ + + {{ page.title }} + + + {{ page.title }} + + +

x

diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 37aa044..0000000 --- a/test/index.js +++ /dev/null @@ -1,273 +0,0 @@ -const test = require('ava') - -const join = require('path').join -const readSync = require('fs').readFileSync - -const posthtml = require('posthtml') -const expressions = require('../lib') - -const fixture = (file) => { - return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') -} - -const expect = (file) => { - return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') -} - -function process (t, name, options, log = false) { - return posthtml([ expressions(options) ]) - .process(fixture(name)) - .then((result) => { - log && console.log(result.html) - - return clean(result.html) - }) - .then((html) => { - t.truthy(html === expect(name).trim()) - }) -} - -function error (name, cb) { - return posthtml([ expressions() ]) - .process(fixture(name)) - .catch(cb) -} - -function clean (html) { - return html.replace(/[^\S\r\n]+$/gm, '').trim() -} - -test('Basic', (t) => { - return process(t, 'basic', { locals: { test: 'wow' } }) -}) - -test('Escaped', (t) => { - return process(t, 'escape_html', { locals: { lt: '<', gt: '>' } }) -}) - -test('Unescaped', (t) => { - return process(t, 'unescaped', { - locals: { el: 'wow' } - }) -}) - -test('Delimiters', (t) => { - return process(t, 'custom_delimiters', { - delimiters: ['{%', '%}'], - unescapeDelimiters: ['{{%', '%}}'], - locals: { test: 'wow' } - }) -}) - -test('Expressions - spacing', (t) => { - return process(t, 'expression_spacing', { locals: { foo: 'X' } }) -}) - -test('Expressions - error', (t) => { - return error('expression_error', (err) => { - t.truthy(err.toString() === 'SyntaxError: Unexpected token ILLEGAL') - }) -}) - -test('Conditionals', (t) => { - return process(t, 'conditional', { locals: { foo: 'bar' } }) -}) - -test('Conditionals - only "if" condition', (t) => { - return process(t, 'conditional_if', { locals: { foo: 'bar' } }) -}) - -test('Conditionals - no render', (t) => { - return process(t, 'conditional_norender', {}) -}) - -test('Conditionals - "if" tag missing condition', (t) => { - return error('conditional_if_error', (err) => { - t.truthy(err.toString() === 'Error: the "if" tag must have a "condition" attribute') - }) -}) - -test('Conditionals - "elseif" tag missing condition', (t) => { - return error('conditional_elseif_error', (err) => { - t.truthy(err.toString() === 'Error: the "elseif" tag must have a "condition" attribute') - }) -}) - -test('Conditionals - other tag in middle of statement', (t) => { - return process(t, 'conditional_tag_break', {}) -}) - -test('Conditionals - nested conditionals', (t) => { - return process(t, 'conditional_nested', {}) -}) - -test('conditional - expression error', (t) => { - return error('conditional_expression_error', (err) => { - t.truthy(err.toString() === 'SyntaxError: Unexpected token ILLEGAL') - }) -}) - -test('Conditionals - custom tags', (t) => { - return process(t, 'conditional_customtags', { - conditionalTags: ['zif', 'zelseif', 'zelse'], - locals: { foo: 'bar' } - }) -}) - -test('Conditionals - expression in else/elseif', (t) => { - return process(t, 'conditional_expression', { - locals: { foo: 'bar' } - }) -}) - -test('Switch', (t) => { - return Promise.all([ - process(t, 'switch', { locals: { country: 'germany' } }) - ]) -}) - -test('Switch - default branch', (t) => { - return Promise.all([ - process(t, 'switch_default', { locals: { country: 'venezuela' } }) - ]) -}) - -test('Switch - nested', (t) => { - return Promise.all([ - process(t, 'switch_nested', { - locals: { - country_one: 'venezuela', - country_two: 'russia' - } - }) - ]) -}) - -test('Switch - custom tag', (t) => { - return process(t, 'switch_customtag', { - switchTags: ['s', 'c', 'd'], - locals: { country: 'us' } - }) -}) - -test('Switch - dynamic expression', (t) => { - return Promise.all([ - process(t, 'switch_number', { locals: { items: [1, 2, 3] } }) - ]) -}) - -test('Switch - no switch attribute', (t) => { - return error('switch_no_attr', (err) => { - t.truthy(err.toString() === 'Error: the "switch" tag must have a "expression" attribute') - }) -}) - -test('Switch - no case attribute', (t) => { - return error('switch_no_case_attr', (err) => { - t.truthy(err.toString() === 'Error: the "case" tag must have a "n" attribute') - }) -}) - -test('Switch - bad flow', (t) => { - return error('switch_bad_flow', (err) => { - t.truthy(err.toString() === 'the "switch" tag can contain only "case" tags and one "default" tag') - }) -}) - -test('Loops', (t) => { - return process(t, 'loop', { locals: { items: [1, 2, 3] } }) -}) - -test('Loops - {Object}', (t) => { - return process(t, 'loop_object', { - locals: { items: { a: 'b', c: 'd' } } - }) -}) - -test('Loops - nested', (t) => { - return process(t, 'loop_nested', { - locals: { items: { c1: [1, 2, 3], c2: [4, 5, 6] } } - }) -}) - -test('Loops - locals included', (t) => { - return process(t, 'loop_locals', { - locals: { items: [1, 2, 3], foo: 'bar' } - }) -}) - -test('Loops - conditional included', (t) => { - return process(t, 'loop_conditional', { - locals: { items: [1, 2, 3] } - }) -}) - -test('Loops - conflicting locals', (t) => { - return process(t, 'loop_conflict', { - locals: { items: [1, 2, 3], item: 'bar' } - }) -}) - -test('Loops - custom tag', (t) => { - return process(t, 'loop_customtag', { - loopTags: ['zeach'], - locals: { items: [1, 2, 3] } - }) -}) - -test('Loops - no loop attribute', (t) => { - return error('loop_no_attr', (err) => { - t.truthy(err.toString() === 'Error: the "elseif" tag must have a "loop" attribute') - }) -}) - -test('Loops - no array or object passed', (t) => { - return error('loop_no_collection', (err) => { - t.truthy(err.toString() === 'Error: You must provide an array or object to loop through') - }) -}) - -test('Loops - no loop arguments', (t) => { - return error('loop_no_args', (err) => { - t.truthy(err.toString() === 'Error: You must provide at least one loop argument') - }) -}) - -test('Loops - no "in" keyword', (t) => { - return error('loop_no_in', (err) => { - t.truthy(err.toString() === "Error: Loop statement lacking 'in' keyword") - }) -}) - -test('Loops - expression error', (t) => { - return error('loop_expression_error', (err) => { - t.truthy(err.toString() === 'SyntaxError: Unexpected token ILLEGAL') - }) -}) - -test('Scopes', (t) => { - return process(t, 'scope', { - locals: { - author: { name: 'John', age: 26 }, - name: 'Scope', - key: 'test' - } - }) -}) - -test('Scopes - nested', (t) => { - return process(t, 'scope_nested', { - locals: { - key: 'global', - scope: { - key: 'scope', - one: { - key: 'one' - }, - two: { - key: 'two' - } - } - } - }) -}) diff --git a/test/test-conditionals.js b/test/test-conditionals.js new file mode 100644 index 0000000..f213801 --- /dev/null +++ b/test/test-conditionals.js @@ -0,0 +1,89 @@ +const test = require('ava') + +const join = require('path').join +const readSync = require('fs').readFileSync + +const posthtml = require('posthtml') +const expressions = require('../lib') + +const fixture = (file) => { + return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') +} + +const expect = (file) => { + return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') +} + +function process (t, name, options, log = false) { + return posthtml([ expressions(options) ]) + .process(fixture(name)) + .then((result) => { + log && console.log(result.html) + + return clean(result.html) + }) + .then((html) => { + t.truthy(html === expect(name).trim()) + }) +} + +function error (name, cb) { + return posthtml([ expressions() ]) + .process(fixture(name)) + .catch(cb) +} + +function clean (html) { + return html.replace(/[^\S\r\n]+$/gm, '').trim() +} + +test('Conditionals', (t) => { + return process(t, 'conditional', { locals: { foo: 'bar' } }) +}) + +test('Conditionals - only "if" condition', (t) => { + return process(t, 'conditional_if', { locals: { foo: 'bar' } }) +}) + +test('Conditionals - no render', (t) => { + return process(t, 'conditional_norender', {}) +}) + +test('Conditionals - "if" tag missing condition', (t) => { + return error('conditional_if_error', (err) => { + t.truthy(err.toString() === 'Error: the "if" tag must have a "condition" attribute') + }) +}) + +test('Conditionals - "elseif" tag missing condition', (t) => { + return error('conditional_elseif_error', (err) => { + t.truthy(err.toString() === 'Error: the "elseif" tag must have a "condition" attribute') + }) +}) + +test('Conditionals - other tag in middle of statement', (t) => { + return process(t, 'conditional_tag_break', {}) +}) + +test('Conditionals - nested conditionals', (t) => { + return process(t, 'conditional_nested', {}) +}) + +test('conditional - expression error', (t) => { + return error('conditional_expression_error', (err) => { + t.is(err.name, 'SyntaxError') + }) +}) + +test('Conditionals - custom tags', (t) => { + return process(t, 'conditional_customtags', { + conditionalTags: ['zif', 'zelseif', 'zelse'], + locals: { foo: 'bar' } + }) +}) + +test('Conditionals - expression in else/elseif', (t) => { + return process(t, 'conditional_expression', { + locals: { foo: 'bar' } + }) +}) diff --git a/test/test-core.js b/test/test-core.js new file mode 100644 index 0000000..acdd1dd --- /dev/null +++ b/test/test-core.js @@ -0,0 +1,70 @@ +const test = require('ava') + +const join = require('path').join +const readSync = require('fs').readFileSync + +const posthtml = require('posthtml') +const expressions = require('../lib') + +const fixture = (file) => { + return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') +} + +const expect = (file) => { + return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') +} + +function process (t, name, options, log = false) { + return posthtml([ expressions(options) ]) + .process(fixture(name)) + .then((result) => { + log && console.log(result.html) + + return clean(result.html) + }) + .then((html) => { + t.truthy(html === expect(name).trim()) + }) +} + +function error (name, cb) { + return posthtml([ expressions() ]) + .process(fixture(name)) + .catch(cb) +} + +function clean (html) { + return html.replace(/[^\S\r\n]+$/gm, '').trim() +} + +test('Basic', (t) => { + return process(t, 'basic', { locals: { test: 'wow' } }) +}) + +test('Escaped', (t) => { + return process(t, 'escape_html', { locals: { lt: '<', gt: '>' } }) +}) + +test('Unescaped', (t) => { + return process(t, 'unescaped', { + locals: { el: 'wow' } + }) +}) + +test('Delimiters', (t) => { + return process(t, 'custom_delimiters', { + delimiters: ['{%', '%}'], + unescapeDelimiters: ['{{%', '%}}'], + locals: { test: 'wow' } + }) +}) + +test('Expressions - spacing', (t) => { + return process(t, 'expression_spacing', { locals: { foo: 'X' } }) +}) + +test('Expressions - error', (t) => { + return error('expression_error', (err) => { + t.is(err.message, 'Invalid or unexpected token') + }) +}) diff --git a/test/test-loops.js b/test/test-loops.js new file mode 100644 index 0000000..22c8b19 --- /dev/null +++ b/test/test-loops.js @@ -0,0 +1,122 @@ +const test = require('ava') + +const join = require('path').join +const readSync = require('fs').readFileSync + +const posthtml = require('posthtml') +const expressions = require('../lib') + +const fixture = (file) => { + return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') +} + +const expect = (file) => { + return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') +} + +function process (t, name, options, log = false) { + return posthtml([ expressions(options) ]) + .process(fixture(name)) + .then((result) => { + log && console.log(result.html) + + return clean(result.html) + }) + .then((html) => { + t.is(html, expect(name).trim()) + }) +} + +function error (name, cb) { + return posthtml([ expressions() ]) + .process(fixture(name)) + .catch(cb) +} + +function clean (html) { + return html.replace(/[^\S\r\n]+$/gm, '').trim() +} + +test('Loops', (t) => { + return process(t, 'loop', { locals: { items: [1, 2, 3] } }) +}) + +test('Loops - {Object}', (t) => { + return process(t, 'loop_object', { + locals: { items: { a: 'b', c: 'd' } } + }) +}) + +test('Loops - nested', (t) => { + return process(t, 'loop_nested', { + locals: { items: { c1: [1, 2, 3], c2: [4, 5, 6] } } + }) +}) + +test('Loops - locals included', (t) => { + return process(t, 'loop_locals', { + locals: { items: [1, 2, 3], foo: 'bar' } + }) +}) + +test('Loops - conditional included', (t) => { + return process(t, 'loop_conditional', { + locals: { items: [1, 2, 3] } + }) +}) + +test('Loops - conditional and locals included', (t) => { + return process(t, 'loop_conditional_locals', { + locals: { + pages: [ + { path: '/page1', title: 'Page 1' }, + { path: '/page2', title: 'Page 2' }, + { path: '/page3', title: 'Page 3' } + ], + current_path: '/page1' + } + }) +}) + +test('Loops - conflicting locals', (t) => { + return process(t, 'loop_conflict', { + locals: { items: [1, 2, 3], item: 'bar' } + }) +}) + +test('Loops - custom tag', (t) => { + return process(t, 'loop_customtag', { + loopTags: ['zeach'], + locals: { items: [1, 2, 3] } + }) +}) + +test('Loops - no loop attribute', (t) => { + return error('loop_no_attr', (err) => { + t.is(err.message, 'the "each" tag must have a "loop" attribute') + }) +}) + +test('Loops - no array or object passed', (t) => { + return error('loop_no_collection', (err) => { + t.truthy(err.toString() === 'Error: You must provide an array or object to loop through') + }) +}) + +test('Loops - no loop arguments', (t) => { + return error('loop_no_args', (err) => { + t.truthy(err.toString() === 'Error: You must provide at least one loop argument') + }) +}) + +test('Loops - no "in" keyword', (t) => { + return error('loop_no_in', (err) => { + t.truthy(err.toString() === "Error: Loop statement lacking 'in' keyword") + }) +}) + +test('Loops - expression error', (t) => { + return error('loop_expression_error', (err) => { + t.is(err.message, 'Invalid or unexpected token') + }) +}) diff --git a/test/test-scopes.js b/test/test-scopes.js new file mode 100644 index 0000000..e863210 --- /dev/null +++ b/test/test-scopes.js @@ -0,0 +1,59 @@ +const test = require('ava') + +const join = require('path').join +const readSync = require('fs').readFileSync + +const posthtml = require('posthtml') +const expressions = require('../lib') + +const fixture = (file) => { + return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') +} + +const expect = (file) => { + return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') +} + +function process (t, name, options, log = false) { + return posthtml([ expressions(options) ]) + .process(fixture(name)) + .then((result) => { + log && console.log(result.html) + + return clean(result.html) + }) + .then((html) => { + t.truthy(html === expect(name).trim()) + }) +} + +function clean (html) { + return html.replace(/[^\S\r\n]+$/gm, '').trim() +} + +test('Scopes', (t) => { + return process(t, 'scope', { + locals: { + author: { name: 'John', age: 26 }, + name: 'Scope', + key: 'test' + } + }) +}) + +test('Scopes - nested', (t) => { + return process(t, 'scope_nested', { + locals: { + key: 'global', + scope: { + key: 'scope', + one: { + key: 'one' + }, + two: { + key: 'two' + } + } + } + }) +}) diff --git a/test/test-switch.js b/test/test-switch.js new file mode 100644 index 0000000..68aba85 --- /dev/null +++ b/test/test-switch.js @@ -0,0 +1,92 @@ +const test = require('ava') + +const join = require('path').join +const readSync = require('fs').readFileSync + +const posthtml = require('posthtml') +const expressions = require('../lib') + +const fixture = (file) => { + return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') +} + +const expect = (file) => { + return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') +} + +function process (t, name, options, log = false) { + return posthtml([ expressions(options) ]) + .process(fixture(name)) + .then((result) => { + log && console.log(result.html) + + return clean(result.html) + }) + .then((html) => { + t.truthy(html === expect(name).trim()) + }) +} + +function error (name, cb) { + return posthtml([ expressions() ]) + .process(fixture(name)) + .catch(cb) +} + +function clean (html) { + return html.replace(/[^\S\r\n]+$/gm, '').trim() +} + +test('Switch', (t) => { + return Promise.all([ + process(t, 'switch', { locals: { country: 'germany' } }) + ]) +}) + +test('Switch - default branch', (t) => { + return Promise.all([ + process(t, 'switch_default', { locals: { country: 'venezuela' } }) + ]) +}) + +test('Switch - nested', (t) => { + return Promise.all([ + process(t, 'switch_nested', { + locals: { + country_one: 'venezuela', + country_two: 'russia' + } + }) + ]) +}) + +test('Switch - custom tag', (t) => { + return process(t, 'switch_customtag', { + switchTags: ['s', 'c', 'd'], + locals: { country: 'us' } + }) +}) + +test('Switch - dynamic expression', (t) => { + return Promise.all([ + process(t, 'switch_number', { locals: { items: [1, 2, 3] } }) + ]) +}) + +test('Switch - no switch attribute', (t) => { + return error('switch_no_attr', (err) => { + t.truthy(err.toString() === 'Error: the "switch" tag must have a "expression" attribute') + }) +}) + +test('Switch - no case attribute', (t) => { + return error('switch_no_case_attr', (err) => { + t.is(err.message, 'the "switch" tag must have a "expression" attribute') + }) +}) + +test('Switch - bad flow', (t) => { + return error('switch_bad_flow', (err) => { + t.is(err.message, 'the "switch" tag can contain only "case" tags and one "default" tag') + }) +})